chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Integration test: WPDO_Crypto::migrate_v1_to_v2() (v2.15.0).
|
||||
*
|
||||
* Verifies the bulk migration path against a real wp_itest_options table:
|
||||
* - Mixed format input (v1 / v2 / plaintext / empty) all classified correctly
|
||||
* - Counts returned accurately
|
||||
* - Idempotency: second run is no-op (all v2)
|
||||
* - Non-wpdo prefix excluded from sweep
|
||||
*/
|
||||
class CryptoMigrationTest extends TestCase {
|
||||
|
||||
private const TEST_PREFIX = 'wp_itest_';
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->prefix = self::TEST_PREFIX;
|
||||
$wpdb->options = self::TEST_PREFIX . 'options';
|
||||
|
||||
$wpdb->query(
|
||||
'CREATE TABLE IF NOT EXISTS `' . self::TEST_PREFIX . 'options` (
|
||||
option_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
option_name varchar(191) NOT NULL DEFAULT "",
|
||||
option_value longtext NOT NULL,
|
||||
autoload varchar(20) NOT NULL DEFAULT "yes",
|
||||
PRIMARY KEY (option_id),
|
||||
UNIQUE KEY option_name (option_name)
|
||||
) DEFAULT CHARACTER SET utf8mb4'
|
||||
);
|
||||
|
||||
// Define WP auth constants for stable key derivation.
|
||||
if ( ! defined( 'AUTH_KEY' ) ) {
|
||||
define( 'AUTH_KEY', 'integration_auth_key_long_enough_xxxxxxxxxxxxxxxxxxxxxx' );
|
||||
}
|
||||
if ( ! defined( 'SECURE_AUTH_SALT' ) ) {
|
||||
define( 'SECURE_AUTH_SALT', 'integration_secure_auth_salt_long_xxxxxxxxxxxxxxxxxxxx' );
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
global $wpdb;
|
||||
// Clear all wpdo_* options before each test for isolation.
|
||||
$wpdb->query( "DELETE FROM `" . self::TEST_PREFIX . "options` WHERE option_name LIKE 'wpdo_%' OR option_name LIKE 'unrelated_%'" );
|
||||
}
|
||||
|
||||
public function test_migrate_mixed_format_inputs(): void {
|
||||
// Set up: 2 v1 blobs, 1 v2 blob, 1 plaintext, 1 unrelated (non-wpdo).
|
||||
$plain1 = 'https://hooks.slack.com/services/legacy1';
|
||||
$plain2 = 'https://discord.com/api/webhooks/legacy2';
|
||||
$this->insert_v1_option( 'wpdo_legacy_slack', $plain1 );
|
||||
$this->insert_v1_option( 'wpdo_legacy_discord', $plain2 );
|
||||
|
||||
// Already v2.
|
||||
$this->set_option_raw( 'wpdo_already_v2', WPDO_Crypto::encrypt( 'already encrypted' ) );
|
||||
|
||||
// Plaintext.
|
||||
$this->set_option_raw( 'wpdo_plaintext_secret', 'just text' );
|
||||
|
||||
// Unrelated prefix — must NOT be touched.
|
||||
$this->set_option_raw( 'unrelated_secret', 'should be ignored' );
|
||||
|
||||
$counts = WPDO_Crypto::migrate_v1_to_v2( 'wpdo_' );
|
||||
|
||||
// Scanned 4 wpdo_* options (unrelated_ excluded).
|
||||
$this->assertSame( 4, $counts['scanned'] );
|
||||
$this->assertSame( 2, $counts['migrated'] );
|
||||
$this->assertSame( 1, $counts['already_v2'] );
|
||||
$this->assertSame( 1, $counts['plaintext'] );
|
||||
$this->assertSame( 0, $counts['failed'] );
|
||||
|
||||
// Verify v1 blobs were upgraded to v2 and decrypt correctly.
|
||||
$this->assertSame( 'v2', WPDO_Crypto::format_version( 'wpdo_legacy_slack' ) );
|
||||
$this->assertSame( 'v2', WPDO_Crypto::format_version( 'wpdo_legacy_discord' ) );
|
||||
$this->assertSame( $plain1, WPDO_Crypto::get_option( 'wpdo_legacy_slack' ) );
|
||||
$this->assertSame( $plain2, WPDO_Crypto::get_option( 'wpdo_legacy_discord' ) );
|
||||
|
||||
// Plaintext untouched.
|
||||
$this->assertSame( 'plaintext', WPDO_Crypto::format_version( 'wpdo_plaintext_secret' ) );
|
||||
|
||||
// Unrelated option untouched.
|
||||
global $wpdb;
|
||||
$unrelated_value = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_value FROM `" . self::TEST_PREFIX . "options` WHERE option_name = %s",
|
||||
'unrelated_secret'
|
||||
)
|
||||
);
|
||||
$this->assertSame( 'should be ignored', $unrelated_value );
|
||||
}
|
||||
|
||||
public function test_migrate_idempotent_second_run_is_noop(): void {
|
||||
$plain = 'a value';
|
||||
$this->insert_v1_option( 'wpdo_test_idempotent', $plain );
|
||||
|
||||
$first = WPDO_Crypto::migrate_v1_to_v2( 'wpdo_' );
|
||||
$second = WPDO_Crypto::migrate_v1_to_v2( 'wpdo_' );
|
||||
|
||||
// First run migrates 1, second run sees it as already_v2.
|
||||
$this->assertSame( 1, $first['migrated'] );
|
||||
$this->assertSame( 0, $second['migrated'] );
|
||||
$this->assertSame( 1, $second['already_v2'] );
|
||||
|
||||
// Value still decrypts correctly after both runs.
|
||||
$this->assertSame( $plain, WPDO_Crypto::get_option( 'wpdo_test_idempotent' ) );
|
||||
}
|
||||
|
||||
public function test_migrate_empty_set(): void {
|
||||
$counts = WPDO_Crypto::migrate_v1_to_v2( 'nonexistent_prefix_' );
|
||||
|
||||
$this->assertSame( 0, $counts['scanned'] );
|
||||
$this->assertSame( 0, $counts['migrated'] );
|
||||
$this->assertSame( 0, $counts['failed'] );
|
||||
}
|
||||
|
||||
public function test_migrate_preserves_value_semantics(): void {
|
||||
// Realistic test: write a webhook-shaped string that includes URL chars
|
||||
// + special padding to make sure no encoding artifacts surface.
|
||||
$plain = 'https://hooks.slack.com/services/T01/B02/=+&%/special?chars=true';
|
||||
$this->insert_v1_option( 'wpdo_realistic_webhook', $plain );
|
||||
|
||||
WPDO_Crypto::migrate_v1_to_v2( 'wpdo_' );
|
||||
|
||||
$this->assertSame( $plain, WPDO_Crypto::get_option( 'wpdo_realistic_webhook' ) );
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Insert an option containing a hand-crafted v1 (CBC) ciphertext.
|
||||
*/
|
||||
private function insert_v1_option( string $name, string $plaintext ): void {
|
||||
$key = substr( hash_hmac( 'sha256', 'wpdo_notifier_secrets_v1', AUTH_KEY . SECURE_AUTH_SALT, true ), 0, 32 );
|
||||
$iv = random_bytes( 16 );
|
||||
$ct = openssl_encrypt( $plaintext, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv );
|
||||
$blob = WPDO_Crypto::PREFIX_V1 . base64_encode( $iv . $ct );
|
||||
$this->set_option_raw( $name, $blob );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a raw option value directly (bypasses WPDO_Crypto::set_option).
|
||||
*/
|
||||
private function set_option_raw( string $name, string $value ): void {
|
||||
global $wpdb;
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'REPLACE INTO `' . self::TEST_PREFIX . 'options` (option_name, option_value, autoload) VALUES (%s, %s, %s)',
|
||||
$name,
|
||||
$value,
|
||||
'no'
|
||||
)
|
||||
);
|
||||
$GLOBALS['_wp_options'][ $name ] = $value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user