From d69f637b06ec56e978d16413b1d2dc60da1ecf36 Mon Sep 17 00:00:00 2001 From: peptiderepo Date: Tue, 14 Apr 2026 19:54:23 +0700 Subject: [PATCH] refactor: extract upgrade logic to PSA_Upgrade class (fixes CI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI on 61cf00c failed PHPUnit with "Call to undefined function plugin_dir_path()" — the test required peptide-search-ai.php, which calls plugin_dir_path() at top level (line 19) before any hook. Stubbing every top-level WP function the plugin file touches was the wrong shape of fix. Proper fix: move the upgrade logic into includes/class-psa-upgrade.php so the test can require just the helper class, matching the pattern every other includes/ module follows. - New: includes/class-psa-upgrade.php with PSA_Upgrade::is_needed() (pure) and PSA_Upgrade::maybe_run() (driver). OPTION_NAME and DEFAULT_STORED_VERSION exposed as constants. - peptide-search-ai.php: psa_admin_init() now calls PSA_Upgrade::maybe_run(); the inline helper functions and docblocks are removed. - tests/bootstrap.php: loads class-psa-upgrade.php alongside the other tested classes. - tests/unit/UpgradeTest.php: rewritten against PSA_Upgrade; adds two extra assertions on the constants for stability. - ARCHITECTURE.md: refreshed to reference the class and its constants. No behavioral change. The cost-tracker schema upgrade still runs once per admin_init when PSA_VERSION advances past psa_db_version. Agent-Session: cowork-qa-p1-fixes-20260414 --- ARCHITECTURE.md | 12 ++--- includes/class-psa-upgrade.php | 81 ++++++++++++++++++++++++++++++++++ peptide-search-ai.php | 42 +----------------- tests/bootstrap.php | 1 + tests/unit/UpgradeTest.php | 64 ++++++++++++--------------- 5 files changed, 119 insertions(+), 81 deletions(-) create mode 100644 includes/class-psa-upgrade.php diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index dd80a1c..2de2717 100755 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -360,17 +360,17 @@ $psa_settings = array( 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: +- **Option name:** `PSA_Upgrade::OPTION_NAME` (`psa_db_version`), string, semver. +- **Default:** `PSA_Upgrade::DEFAULT_STORED_VERSION` (`'0.0.0'`) when the option has never been written — strictly less than any released version so fresh installs always trigger the upgrade path. +- **Driver:** `PSA_Upgrade::maybe_run()` in `includes/class-psa-upgrade.php`, called on every `admin_init` from `psa_admin_init()`. The method: 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). + 2. Calls the pure helper `PSA_Upgrade::is_needed( $stored, PSA_VERSION )`, 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. +- **Downgrade / rollback:** if the running code is older than `psa_db_version` (e.g., deploy rolled back), `is_needed()` returns false and the driver 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. +- **Testability:** `PSA_Upgrade::is_needed()` is a pure static method (no WordPress API calls) and is covered by `tests/unit/UpgradeTest.php` across fresh-install, same-version, future-version, and older-version scenarios, plus constant-stability checks on `OPTION_NAME` and `DEFAULT_STORED_VERSION`. ## Frontend Architecture diff --git a/includes/class-psa-upgrade.php b/includes/class-psa-upgrade.php new file mode 100644 index 0000000..23abe62 --- /dev/null +++ b/includes/class-psa-upgrade.php @@ -0,0 +1,81 @@ +assertTrue( - psa_upgrade_needed( '0.0.0', '4.4.3' ), + PSA_Upgrade::is_needed( '0.0.0', '4.4.3' ), 'Fresh install (stored = 0.0.0) must trigger an upgrade against any released version.' ); } @@ -62,7 +40,7 @@ class UpgradeTest extends TestCase { */ public function test_same_version_skips_upgrade(): void { $this->assertFalse( - psa_upgrade_needed( '4.4.3', '4.4.3' ), + PSA_Upgrade::is_needed( '4.4.3', '4.4.3' ), 'Stored version equal to current version must not trigger an upgrade.' ); } @@ -74,7 +52,7 @@ class UpgradeTest extends TestCase { */ public function test_future_stored_version_skips_upgrade(): void { $this->assertFalse( - psa_upgrade_needed( '5.0.0', '4.4.3' ), + PSA_Upgrade::is_needed( '5.0.0', '4.4.3' ), 'Stored version greater than current version must not trigger an upgrade.' ); } @@ -86,8 +64,24 @@ class UpgradeTest extends TestCase { */ public function test_older_stored_version_triggers_upgrade(): void { $this->assertTrue( - psa_upgrade_needed( '4.4.2', '4.4.3' ), + PSA_Upgrade::is_needed( '4.4.2', '4.4.3' ), 'Stored version older than current version must trigger an upgrade.' ); } + + /** + * Constants used by the driver should be stable across releases so + * existing installs keep pointing at the same option key. + */ + public function test_option_name_constant_is_stable(): void { + $this->assertSame( 'psa_db_version', PSA_Upgrade::OPTION_NAME ); + } + + /** + * Default stored version must be strictly less than any future release + * so fresh-install detection keeps working. + */ + public function test_default_stored_version_is_zero(): void { + $this->assertSame( '0.0.0', PSA_Upgrade::DEFAULT_STORED_VERSION ); + } }