From 88a4ae9a4e0576e6fd7e4834125b3a591483d6c8 Mon Sep 17 00:00:00 2001 From: peptiderepo Date: Tue, 14 Apr 2026 19:09:49 +0700 Subject: [PATCH] fix: QA P1 findings on auto-schema-upgrade (v4.4.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the three P1 findings from QA baseline review of a8a0ce2: 1. uninstall.php: delete psa_db_version option on plugin removal so no orphaned data is left behind — satisfies AGENT-OPERATING-STANDARD teardown/uninstall DoD. 2. Extract psa_upgrade_needed( $stored, $current ) as a pure function and add tests/unit/UpgradeTest.php covering fresh-install, same-version, future-version, and older-version scenarios. The impure wrapper psa_maybe_run_upgrades() now delegates the decision to the pure helper. 3. Document the psa_db_version option and the admin_init-triggered upgrade mechanism in ARCHITECTURE.md (Options section), including why admin_init, downgrade behavior, cleanup, and testability. No behavioral change for end users — same upgrade semantics, just testable and documented, with proper teardown. Agent-Session: cowork-qa-p1-fixes-20260414 --- ARCHITECTURE.md | 16 +++++++ peptide-search-ai.php | 18 +++++++- tests/unit/UpgradeTest.php | 85 ++++++++++++++++++++++++++++++++++++++ uninstall.php | 1 + 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 tests/unit/UpgradeTest.php diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a0fbaff..dd80a1c 100755 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -356,6 +356,22 @@ $psa_settings = array( ); ``` +### Schema versioning & automatic upgrades + +The plugin stores the schema version it last migrated to in a separate option, `psa_db_version`. This is distinct from `PSA_VERSION` (the running code's semver), and lets the plugin detect whether the database needs to catch up after an in-place file update. + +- **Option name:** `psa_db_version` (string, semver). +- **Default:** `'0.0.0'` when the option has never been written (fresh install observed from the admin side before activation-time migration). +- **Writer:** `psa_maybe_run_upgrades()` in `peptide-search-ai.php`, called on every `admin_init`. The function: + 1. Reads `psa_db_version`. + 2. Calls the pure helper `psa_upgrade_needed( $stored, PSA_VERSION )`, which is a thin wrapper over `version_compare` (stored < current → upgrade). + 3. If an upgrade is needed, re-runs `PSA_Cost_Tracker::create_table()` so `dbDelta` picks up any additive schema changes (new columns, new indexes). + 4. Writes `update_option( 'psa_db_version', PSA_VERSION, false )` so the upgrade doesn't re-run on the next request. +- **Why on `admin_init`:** dbDelta is safe-to-repeat but not free. Running it once when an admin visits after an update — rather than on every frontend page load — keeps the cost near zero without requiring users to deactivate/reactivate the plugin. +- **Downgrade / rollback:** if the running code is older than `psa_db_version` (e.g., deploy rolled back), the function is a no-op. The newer schema is left intact; old code continues to work against it as long as changes are additive. +- **Cleanup:** `uninstall.php` deletes `psa_db_version` alongside the other plugin options. +- **Testability:** `psa_upgrade_needed()` is a pure function (no WordPress calls) and is covered by `tests/unit/UpgradeTest.php` across fresh-install, same-version, future-version, and older-version scenarios. + ## Frontend Architecture ### JavaScript (jQuery-based) diff --git a/peptide-search-ai.php b/peptide-search-ai.php index e6da0bb..6ab562c 100755 --- a/peptide-search-ai.php +++ b/peptide-search-ai.php @@ -52,6 +52,19 @@ function psa_admin_init() { } add_action( 'admin_init', 'psa_admin_init' ); +/** + * Decide whether a schema upgrade is needed given a stored version and the + * currently-installed code version. Pure function — no side effects, testable + * in isolation. + * + * @param string $stored_version Version string recorded in psa_db_version. + * @param string $current_version Version string of the running code. + * @return bool True if an upgrade must run; false otherwise. + */ +function psa_upgrade_needed( $stored_version, $current_version ) { + return version_compare( $stored_version, $current_version, '<' ); +} + /** * Run database upgrades when the installed code version is newer than the * stored `psa_db_version` option. Keeps users from needing to @@ -60,11 +73,14 @@ add_action( 'admin_init', 'psa_admin_init' ); * Triggered on admin_init so dbDelta runs once per upgrade, not on every * frontend page load. * + * Side effects: reads psa_db_version option; on upgrade, calls + * PSA_Cost_Tracker::create_table() and writes psa_db_version. + * * @return void */ function psa_maybe_run_upgrades() { $stored = get_option( 'psa_db_version', '0.0.0' ); - if ( version_compare( $stored, PSA_VERSION, '>=' ) ) { + if ( ! psa_upgrade_needed( $stored, PSA_VERSION ) ) { return; } diff --git a/tests/unit/UpgradeTest.php b/tests/unit/UpgradeTest.php new file mode 100644 index 0000000..7219e34 --- /dev/null +++ b/tests/unit/UpgradeTest.php @@ -0,0 +1,85 @@ +assertTrue( + psa_upgrade_needed( '0.0.0', '4.4.3' ), + 'Fresh install (stored = 0.0.0) must trigger an upgrade against any released version.' + ); + } + + /** + * Already-upgraded: stored version equals current. No upgrade needed. + */ + public function test_same_version_skips_upgrade(): void { + $this->assertFalse( + psa_upgrade_needed( '4.4.3', '4.4.3' ), + 'Stored version equal to current version must not trigger an upgrade.' + ); + } + + /** + * Future-version: stored version is ahead of the running code (e.g., a + * downgrade, or a user running multiple sites out of sync). Do not run + * upgrade — it could apply an older schema over a newer one. + */ + public function test_future_stored_version_skips_upgrade(): void { + $this->assertFalse( + psa_upgrade_needed( '5.0.0', '4.4.3' ), + 'Stored version greater than current version must not trigger an upgrade.' + ); + } + + /** + * Belt-and-braces: a strictly newer code version against an older + * stored version must trigger an upgrade. Guards against an accidental + * operator flip in version_compare() (e.g., '>=' vs '<'). + */ + public function test_older_stored_version_triggers_upgrade(): void { + $this->assertTrue( + psa_upgrade_needed( '4.4.2', '4.4.3' ), + 'Stored version older than current version must trigger an upgrade.' + ); + } +} diff --git a/uninstall.php b/uninstall.php index 6381564..8b81cd9 100755 --- a/uninstall.php +++ b/uninstall.php @@ -42,6 +42,7 @@ if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) { // 3. Delete plugin options. delete_option( 'psa_settings' ); delete_option( 'psa_search_cache_gen' ); +delete_option( 'psa_db_version' ); // 4. Delete all plugin transients (validation cache, rate limits). $wpdb->query(