Files
wpdev 151a14a7e8 fix(zone-warm): warm 表 UNIQUE KEY + set() 改 upsert + 原子 increment(B1/B3)
- CREATE TABLE wpdo_warm 的 meta_key varchar(255)→varchar(191),
  KEY idx_post_meta → UNIQUE KEY ui_post_meta:沒有這個唯一鍵,
  ON DUPLICATE KEY UPDATE 不會觸發,會不斷 INSERT 重複列。
  (既有站台的 ALTER 遷移原本就在 installer:1329,只有 CREATE 落後)
- Zone_Warm::set() 由 SELECT→update/insert 改為 TMDO_DB::upsert()
- 新增 Zone_Warm::increment():CAST(COALESCE(meta_value,0) AS SIGNED)+N
  原子計數,取代 get()+set() 的 read-modify-write race(A v3.3.3)
- ZoneWarmTest stub 與 ZoneWarmIntegrationTest 建表同步適配

unit 379 / integration 398 GREEN

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 05:31:34 +08:00

170 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* Integration tests for WPDO_Zone_Warm against real MariaDB.
*
* Creates a dedicated test table (wp_itest_wpdo_warm) so the real
* production warm table is never touched.
*/
class ZoneWarmIntegrationTest extends TestCase {
private const TABLE = 'wp_itest_wpdo_warm';
// ── Fixture lifecycle ─────────────────────────────────────────────────
public static function setUpBeforeClass(): void {
global $wpdb;
// DROP + CREATE ensures clean schema even after interrupted prior runs.
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' );
$wpdb->query(
'CREATE TABLE `' . self::TABLE . '` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`post_id` bigint(20) unsigned NOT NULL DEFAULT 0,
`meta_key` varchar(255) NOT NULL DEFAULT \'\',
`meta_value` longtext DEFAULT NULL,
`expires_at` datetime DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT \'0000-00-00 00:00:00\',
PRIMARY KEY (`id`),
UNIQUE KEY `ui_post_meta` (`post_id`, `meta_key`),
KEY `expires_at` (`expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
);
}
public static function tearDownAfterClass(): void {
global $wpdb;
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' );
}
protected function setUp(): void {
global $wpdb;
$wpdb->query( 'TRUNCATE TABLE `' . self::TABLE . '`' );
}
// ── set / get ────────────────────────────────────────────────────────
public function test_set_and_get_basic(): void {
WPDO_Zone_Warm::set( 1, 'test_key', 'hello' );
$this->assertSame( 'hello', WPDO_Zone_Warm::get( 1, 'test_key' ) );
}
public function test_get_returns_null_for_missing_key(): void {
$this->assertNull( WPDO_Zone_Warm::get( 9999, 'no_such_key' ) );
}
public function test_set_overwrites_existing_value(): void {
WPDO_Zone_Warm::set( 2, 'counter', '1' );
WPDO_Zone_Warm::set( 2, 'counter', '5' );
$this->assertSame( '5', WPDO_Zone_Warm::get( 2, 'counter' ) );
}
// ── TTL / expiry ─────────────────────────────────────────────────────
public function test_set_with_future_ttl_is_readable(): void {
WPDO_Zone_Warm::set( 3, 'flag', 'active', 3600 ); // expires in 1 hour
$this->assertSame( 'active', WPDO_Zone_Warm::get( 3, 'flag' ) );
}
public function test_expired_entry_returns_null(): void {
global $wpdb;
// Insert directly with a past expiry timestamp.
$wpdb->query(
"INSERT INTO `" . self::TABLE . "` (post_id, meta_key, meta_value, expires_at, created_at)
VALUES (4, 'old_flag', 'gone', '2000-01-01 00:00:00', '2000-01-01 00:00:00')"
);
$this->assertNull( WPDO_Zone_Warm::get( 4, 'old_flag' ) );
}
public function test_null_ttl_entry_never_expires(): void {
WPDO_Zone_Warm::set( 5, 'permanent', 'stays', null );
$this->assertSame( 'stays', WPDO_Zone_Warm::get( 5, 'permanent' ) );
}
// ── delete ───────────────────────────────────────────────────────────
public function test_delete_removes_specific_key(): void {
WPDO_Zone_Warm::set( 6, 'key_a', 'alpha' );
WPDO_Zone_Warm::set( 6, 'key_b', 'beta' );
WPDO_Zone_Warm::delete( 6, 'key_a' );
$this->assertNull( WPDO_Zone_Warm::get( 6, 'key_a' ) );
$this->assertSame( 'beta', WPDO_Zone_Warm::get( 6, 'key_b' ) );
}
public function test_delete_all_removes_all_keys_for_post(): void {
WPDO_Zone_Warm::set( 7, 'x', '1' );
WPDO_Zone_Warm::set( 7, 'y', '2' );
WPDO_Zone_Warm::set( 7, 'z', '3' );
WPDO_Zone_Warm::delete_all( 7 );
$this->assertNull( WPDO_Zone_Warm::get( 7, 'x' ) );
$this->assertNull( WPDO_Zone_Warm::get( 7, 'y' ) );
$this->assertNull( WPDO_Zone_Warm::get( 7, 'z' ) );
}
public function test_delete_all_does_not_affect_other_posts(): void {
WPDO_Zone_Warm::set( 8, 'shared_key', 'post_8' );
WPDO_Zone_Warm::set( 9, 'shared_key', 'post_9' );
WPDO_Zone_Warm::delete_all( 8 );
$this->assertNull( WPDO_Zone_Warm::get( 8, 'shared_key' ) );
$this->assertSame( 'post_9', WPDO_Zone_Warm::get( 9, 'shared_key' ) );
}
// ── purge_expired ────────────────────────────────────────────────────
public function test_purge_expired_removes_stale_entries(): void {
global $wpdb;
// One expired entry.
$wpdb->query(
"INSERT INTO `" . self::TABLE . "` (post_id, meta_key, meta_value, expires_at, created_at)
VALUES (10, 'stale', 'gone', '2000-01-01 00:00:00', '2000-01-01 00:00:00')"
);
// One valid entry.
WPDO_Zone_Warm::set( 10, 'fresh', 'keep', 3600 );
$deleted = WPDO_Zone_Warm::purge_expired();
$this->assertSame( 1, $deleted );
$this->assertNull( WPDO_Zone_Warm::get( 10, 'stale' ) );
$this->assertSame( 'keep', WPDO_Zone_Warm::get( 10, 'fresh' ) );
}
public function test_purge_expired_returns_zero_when_nothing_stale(): void {
WPDO_Zone_Warm::set( 11, 'live', 'value', 3600 );
$this->assertSame( 0, WPDO_Zone_Warm::purge_expired() );
}
// ── get_all ──────────────────────────────────────────────────────────
public function test_get_all_returns_all_valid_keys_for_post(): void {
WPDO_Zone_Warm::set( 12, 'ka', 'va' );
WPDO_Zone_Warm::set( 12, 'kb', 'vb' );
$all = WPDO_Zone_Warm::get_all( 12 );
$this->assertArrayHasKey( 'ka', $all );
$this->assertArrayHasKey( 'kb', $all );
$this->assertSame( 'va', $all['ka'] );
$this->assertSame( 'vb', $all['kb'] );
}
public function test_get_all_excludes_expired_entries(): void {
global $wpdb;
WPDO_Zone_Warm::set( 13, 'live', 'yes' );
$wpdb->query(
"INSERT INTO `" . self::TABLE . "` (post_id, meta_key, meta_value, expires_at, created_at)
VALUES (13, 'dead', 'no', '2000-01-01 00:00:00', '2000-01-01 00:00:00')"
);
$all = WPDO_Zone_Warm::get_all( 13 );
$this->assertArrayHasKey( 'live', $all );
$this->assertArrayNotHasKey( 'dead', $all );
}
}