fix(tests): rewrite unit tests to plain-PHP bootstrap pattern; extract compute_status() from scanner
This commit is contained in:
@@ -45,20 +45,8 @@ class PR_Core_Verification_Scanner {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute days since verification.
|
||||
$days_since = ( time() - strtotime( $last_verified ) ) / DAY_IN_SECONDS;
|
||||
|
||||
// Determine threshold based on velocity.
|
||||
$threshold = match ( $velocity ) {
|
||||
'high' => (int) get_option( 'pr_core_high_velocity_threshold', self::HIGH_VELOCITY_THRESHOLD ),
|
||||
'low' => self::LOW_VELOCITY_THRESHOLD,
|
||||
default => (int) get_option( 'pr_core_default_threshold', self::DEFAULT_THRESHOLD_DAYS ),
|
||||
};
|
||||
|
||||
// Compute status: current if < 90%, due if < 100%, overdue if >= 100%.
|
||||
$status = $days_since < ( $threshold * 0.9 )
|
||||
? 'current'
|
||||
: ( $days_since < $threshold ? 'due' : 'overdue' );
|
||||
// Delegate to compute_status() — keeps logic testable without DB calls.
|
||||
$status = self::compute_status( $last_verified, $velocity );
|
||||
|
||||
update_post_meta( $dto->id, '_pr_verification_status', $status );
|
||||
|
||||
@@ -78,6 +66,33 @@ class PR_Core_Verification_Scanner {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute verification status for a single peptide.
|
||||
*
|
||||
* Extracted for direct unit-testability without database or WP-cron dependencies.
|
||||
*
|
||||
* @param string $last_verified YYYY-MM-DD date of last source verification. Empty = overdue.
|
||||
* @param string $velocity Velocity tier: high | medium | low.
|
||||
* @return string Status: current | due | overdue.
|
||||
*/
|
||||
public static function compute_status( string $last_verified, string $velocity ): string {
|
||||
if ( empty( $last_verified ) ) {
|
||||
return 'overdue';
|
||||
}
|
||||
|
||||
$days_since = ( time() - strtotime( $last_verified ) ) / DAY_IN_SECONDS;
|
||||
$threshold = match ( $velocity ) {
|
||||
'high' => (int) get_option( 'pr_core_high_velocity_threshold', self::HIGH_VELOCITY_THRESHOLD ),
|
||||
'low' => self::LOW_VELOCITY_THRESHOLD,
|
||||
default => (int) get_option( 'pr_core_default_threshold', self::DEFAULT_THRESHOLD_DAYS ),
|
||||
};
|
||||
|
||||
return $days_since < ( $threshold * 0.9 )
|
||||
? 'current'
|
||||
: ( $days_since < $threshold ? 'due' : 'overdue' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log scan summary to WP option, keeping last N entries.
|
||||
*
|
||||
|
||||
@@ -1,134 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* Unit tests for PR_Core_Verification_Scanner::compute_status().
|
||||
*
|
||||
* Run: php tests/unit/test-verification-scanner.php
|
||||
* Exit code 0 = all pass, 1 = any failure.
|
||||
*
|
||||
* What's covered:
|
||||
* - Empty last_verified → overdue.
|
||||
* - Days < 90% of threshold → current.
|
||||
* - Days >= 90% and < 100% of threshold → due.
|
||||
* - Days >= threshold → overdue.
|
||||
* - Boundary: exactly at 90% → due (not current).
|
||||
* - high velocity applies 60-day threshold from option.
|
||||
* - low velocity applies 365-day constant (not option).
|
||||
* - medium/default applies 180-day threshold from option.
|
||||
*
|
||||
* @package PeptideRepoCore
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Tests for PR_Core_Verification_Scanner.
|
||||
*
|
||||
* What: Unit tests for status computation logic.
|
||||
* Dependencies: PHPUnit, WordPress test utilities.
|
||||
*
|
||||
* Mock strategy: Zero real DB calls — mock get_option() and update_post_meta().
|
||||
*/
|
||||
class Test_Verification_Scanner extends \WP_UnitTestCase {
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
/**
|
||||
* Test current status when days_since < threshold * 0.9.
|
||||
*/
|
||||
public function test_status_current(): void {
|
||||
// Days since = 160, threshold = 180, 90% = 162.
|
||||
// 160 < 162 => current.
|
||||
$last_verified = gmdate( 'Y-m-d H:i:s', time() - ( 160 * DAY_IN_SECONDS ) );
|
||||
// ── Additional stubs needed by the scanner ──────────────────────────────
|
||||
|
||||
// Mock the repository and post meta.
|
||||
$dto = $this->create_mock_dto( 1, $last_verified, 'medium' );
|
||||
if ( ! defined( 'DAY_IN_SECONDS' ) ) {
|
||||
define( 'DAY_IN_SECONDS', 86400 );
|
||||
}
|
||||
|
||||
$status = $this->compute_status( $dto, 180, 'medium' );
|
||||
$this->assertEquals( 'current', $status );
|
||||
}
|
||||
/** @var array<string, mixed> $GLOBALS['pr_core_options'] Mocked option store. */
|
||||
$GLOBALS['pr_core_options'] = [];
|
||||
|
||||
/**
|
||||
* Test due status when threshold * 0.9 <= days_since < threshold.
|
||||
*/
|
||||
public function test_status_due(): void {
|
||||
// Days since = 170, threshold = 180, 90% = 162.
|
||||
// 162 <= 170 < 180 => due.
|
||||
$last_verified = gmdate( 'Y-m-d H:i:s', time() - ( 170 * DAY_IN_SECONDS ) );
|
||||
|
||||
$dto = $this->create_mock_dto( 2, $last_verified, 'medium' );
|
||||
$status = $this->compute_status( $dto, 180, 'medium' );
|
||||
$this->assertEquals( 'due', $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test overdue status when days_since >= threshold.
|
||||
*/
|
||||
public function test_status_overdue(): void {
|
||||
// Days since = 190, threshold = 180.
|
||||
// 190 >= 180 => overdue.
|
||||
$last_verified = gmdate( 'Y-m-d H:i:s', time() - ( 190 * DAY_IN_SECONDS ) );
|
||||
|
||||
$dto = $this->create_mock_dto( 3, $last_verified, 'medium' );
|
||||
$status = $this->compute_status( $dto, 180, 'medium' );
|
||||
$this->assertEquals( 'overdue', $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty verified date => overdue.
|
||||
*/
|
||||
public function test_empty_verified_date_is_overdue(): void {
|
||||
$dto = $this->create_mock_dto( 4, '', 'medium' );
|
||||
$status = $this->compute_status( $dto, 180, 'medium', true );
|
||||
$this->assertEquals( 'overdue', $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test high velocity threshold.
|
||||
*/
|
||||
public function test_high_velocity_threshold(): void {
|
||||
// Days since = 70, high threshold = 60, 90% = 54.
|
||||
// 54 <= 70 => due (not current even with high velocity).
|
||||
$last_verified = gmdate( 'Y-m-d H:i:s', time() - ( 70 * DAY_IN_SECONDS ) );
|
||||
|
||||
$dto = $this->create_mock_dto( 5, $last_verified, 'high' );
|
||||
$status = $this->compute_status( $dto, 60, 'high' );
|
||||
$this->assertEquals( 'due', $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test low velocity threshold.
|
||||
*/
|
||||
public function test_low_velocity_threshold(): void {
|
||||
// Days since = 350, low threshold = 365, 90% = 328.5.
|
||||
// 328.5 < 350 < 365 => due.
|
||||
$last_verified = gmdate( 'Y-m-d H:i:s', time() - ( 350 * DAY_IN_SECONDS ) );
|
||||
|
||||
$dto = $this->create_mock_dto( 6, $last_verified, 'low' );
|
||||
$status = $this->compute_status( $dto, 365, 'low' );
|
||||
$this->assertEquals( 'due', $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock DTO.
|
||||
*
|
||||
* @param int $id Post ID.
|
||||
* @param string $last_verified Verified date (or empty).
|
||||
* @param string $velocity Velocity level.
|
||||
* @return object Mock DTO.
|
||||
*/
|
||||
private function create_mock_dto( int $id, string $last_verified, string $velocity ): object {
|
||||
$dto = new \stdClass();
|
||||
$dto->id = $id;
|
||||
$dto->title = "Test Peptide $id";
|
||||
$dto->excerpt = '';
|
||||
$dto->content = '';
|
||||
|
||||
// Store in post meta for the computation.
|
||||
update_post_meta( $id, '_pr_last_source_verified', $last_verified );
|
||||
update_post_meta( $id, '_pr_verification_velocity', $velocity );
|
||||
|
||||
return $dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute status using the scanner logic inline (no DB calls beyond meta).
|
||||
*
|
||||
* @param object $dto Peptide DTO.
|
||||
* @param int $threshold Threshold in days.
|
||||
* @param string $velocity Velocity level.
|
||||
* @param bool $empty_date Whether date is empty.
|
||||
* @return string Status.
|
||||
*/
|
||||
private function compute_status( object $dto, int $threshold, string $velocity, bool $empty_date = false ): string {
|
||||
$last_verified = get_post_meta( $dto->id, '_pr_last_source_verified', true );
|
||||
|
||||
if ( $empty_date || empty( $last_verified ) ) {
|
||||
return 'overdue';
|
||||
}
|
||||
|
||||
$days_since = ( time() - strtotime( $last_verified ) ) / DAY_IN_SECONDS;
|
||||
|
||||
return $days_since < ( $threshold * 0.9 )
|
||||
? 'current'
|
||||
: ( $days_since < $threshold ? 'due' : 'overdue' );
|
||||
if ( ! function_exists( 'get_option' ) ) {
|
||||
function get_option( string $key, $default = false ) {
|
||||
return $GLOBALS['pr_core_options'][ $key ] ?? $default;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Load the scanner class ──────────────────────────────────────────────
|
||||
|
||||
require_once __DIR__ . '/../../includes/scanner/class-pr-core-verification-scanner.php';
|
||||
|
||||
// ── Helper: build a date N days ago ────────────────────────────────────
|
||||
|
||||
function days_ago( int $days ): string {
|
||||
return gmdate( 'Y-m-d', time() - ( $days * DAY_IN_SECONDS ) );
|
||||
}
|
||||
|
||||
echo "== PR_Core_Verification_Scanner::compute_status() unit tests ==\n\n";
|
||||
|
||||
// ── Empty last_verified ─────────────────────────────────────────────────
|
||||
|
||||
echo "Empty last_verified:\n";
|
||||
pr_assert_equals(
|
||||
'overdue',
|
||||
PR_Core_Verification_Scanner::compute_status( '', 'medium' ),
|
||||
'empty string → overdue'
|
||||
);
|
||||
|
||||
// ── Medium velocity (default 180-day threshold) ─────────────────────────
|
||||
|
||||
echo "\nMedium velocity (threshold=180, option default):\n";
|
||||
$GLOBALS['pr_core_options'] = [];
|
||||
|
||||
// 160 days ago → 160 < 162 (90% of 180) → current.
|
||||
pr_assert_equals( 'current', PR_Core_Verification_Scanner::compute_status( days_ago( 160 ), 'medium' ), '160 days → current' );
|
||||
|
||||
// 163 days ago → 163 >= 162 and < 180 → due.
|
||||
pr_assert_equals( 'due', PR_Core_Verification_Scanner::compute_status( days_ago( 163 ), 'medium' ), '163 days → due' );
|
||||
|
||||
// 180 days ago → 180 >= 180 → overdue.
|
||||
pr_assert_equals( 'overdue', PR_Core_Verification_Scanner::compute_status( days_ago( 180 ), 'medium' ), '180 days → overdue' );
|
||||
|
||||
// 200 days ago → overdue.
|
||||
pr_assert_equals( 'overdue', PR_Core_Verification_Scanner::compute_status( days_ago( 200 ), 'medium' ), '200 days → overdue' );
|
||||
|
||||
// ── 90% boundary exactness ──────────────────────────────────────────────
|
||||
|
||||
echo "\n90% boundary (threshold=100):\n";
|
||||
$GLOBALS['pr_core_options'] = [ 'pr_core_default_threshold' => 100 ];
|
||||
|
||||
// 89 days → 89 < 90 (90% of 100) → current.
|
||||
pr_assert_equals( 'current', PR_Core_Verification_Scanner::compute_status( days_ago( 89 ), 'medium' ), '89 days (threshold 100) → current' );
|
||||
|
||||
// 90 days → 90 >= 90 and < 100 → due.
|
||||
pr_assert_equals( 'due', PR_Core_Verification_Scanner::compute_status( days_ago( 90 ), 'medium' ), '90 days (threshold 100) → due (at boundary)' );
|
||||
|
||||
// 100 days → 100 >= 100 → overdue.
|
||||
pr_assert_equals( 'overdue', PR_Core_Verification_Scanner::compute_status( days_ago( 100 ), 'medium' ), '100 days (threshold 100) → overdue' );
|
||||
|
||||
// ── High velocity (60-day threshold from option) ────────────────────────
|
||||
|
||||
echo "\nHigh velocity (threshold=60 from option):\n";
|
||||
$GLOBALS['pr_core_options'] = [ 'pr_core_high_velocity_threshold' => 60 ];
|
||||
|
||||
// 50 days → 50 < 54 (90% of 60) → current.
|
||||
pr_assert_equals( 'current', PR_Core_Verification_Scanner::compute_status( days_ago( 50 ), 'high' ), 'high: 50 days → current' );
|
||||
|
||||
// 55 days → 55 >= 54, < 60 → due.
|
||||
pr_assert_equals( 'due', PR_Core_Verification_Scanner::compute_status( days_ago( 55 ), 'high' ), 'high: 55 days → due' );
|
||||
|
||||
// 61 days → overdue.
|
||||
pr_assert_equals( 'overdue', PR_Core_Verification_Scanner::compute_status( days_ago( 61 ), 'high' ), 'high: 61 days → overdue' );
|
||||
|
||||
// ── Low velocity (365-day constant — ignores option) ────────────────────
|
||||
|
||||
echo "\nLow velocity (threshold=365, constant, ignores option):\n";
|
||||
$GLOBALS['pr_core_options'] = [ 'pr_core_default_threshold' => 999 ]; // Should be ignored for low.
|
||||
|
||||
// 300 days → 300 < 328.5 (90% of 365) → current.
|
||||
pr_assert_equals( 'current', PR_Core_Verification_Scanner::compute_status( days_ago( 300 ), 'low' ), 'low: 300 days → current' );
|
||||
|
||||
// 340 days → 340 >= 328.5, < 365 → due.
|
||||
pr_assert_equals( 'due', PR_Core_Verification_Scanner::compute_status( days_ago( 340 ), 'low' ), 'low: 340 days → due' );
|
||||
|
||||
// 366 days → overdue.
|
||||
pr_assert_equals( 'overdue', PR_Core_Verification_Scanner::compute_status( days_ago( 366 ), 'low' ), 'low: 366 days → overdue' );
|
||||
|
||||
// ── Unknown velocity falls through to medium/default ────────────────────
|
||||
|
||||
echo "\nUnknown velocity (falls to default/medium):\n";
|
||||
$GLOBALS['pr_core_options'] = [ 'pr_core_default_threshold' => 180 ];
|
||||
pr_assert_equals( 'current', PR_Core_Verification_Scanner::compute_status( days_ago( 100 ), 'unknown' ), 'unknown velocity: 100 days → current (uses default 180)' );
|
||||
|
||||
// ── Summary ─────────────────────────────────────────────────────────────
|
||||
|
||||
exit( pr_test_summary() );
|
||||
|
||||
@@ -1,93 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* Unit tests for PR_Core_Settings verification methods.
|
||||
*
|
||||
* Run: php tests/unit/test-verification-settings.php
|
||||
* Exit code 0 = all pass, 1 = any failure.
|
||||
*
|
||||
* What's covered:
|
||||
* - sanitize_cadence() accepts valid values, rejects invalid → 'weekly'.
|
||||
* - sanitize_emails() filters invalid addresses, preserves valid ones.
|
||||
* - reschedule_cron() no-ops when old === new value.
|
||||
* - reschedule_cron() clears + reschedules when value changes.
|
||||
*
|
||||
* @package PeptideRepoCore
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Tests for PR_Core_Settings cadence and cron scheduling.
|
||||
*
|
||||
* What: Unit tests for settings registration and cron rescheduling.
|
||||
* Dependencies: PHPUnit, WordPress test utilities.
|
||||
*/
|
||||
class Test_Verification_Settings extends \WP_UnitTestCase {
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Clear any existing scheduled hooks.
|
||||
wp_clear_scheduled_hook( 'pr_core_verification_scan' );
|
||||
}
|
||||
// ── Additional stubs needed by the settings class ───────────────────────
|
||||
|
||||
/**
|
||||
* Test that changing cadence triggers reschedule.
|
||||
*/
|
||||
public function test_reschedule_on_cadence_change(): void {
|
||||
// Set initial cadence.
|
||||
update_option( 'pr_core_scan_cadence', 'weekly' );
|
||||
$GLOBALS['pr_core_options'] = [];
|
||||
$GLOBALS['pr_core_cron_calls'] = [];
|
||||
|
||||
// Schedule initial cron (simulate the activation).
|
||||
if ( ! wp_next_scheduled( 'pr_core_verification_scan' ) ) {
|
||||
wp_schedule_event( time(), 'weekly', 'pr_core_verification_scan' );
|
||||
}
|
||||
|
||||
// Verify it's scheduled.
|
||||
$scheduled = wp_next_scheduled( 'pr_core_verification_scan' );
|
||||
$this->assertNotFalse( $scheduled );
|
||||
|
||||
// Change the cadence (triggers reschedule via hook).
|
||||
update_option( 'pr_core_scan_cadence', 'daily' );
|
||||
|
||||
// The reschedule_cron method should have cleared and rescheduled.
|
||||
// (In a real test, this would be tested via the hook — here we verify the logic.)
|
||||
PR_Core_Settings::reschedule_cron( 'weekly', 'daily' );
|
||||
|
||||
// Verify cron still exists (just with new frequency).
|
||||
$scheduled = wp_next_scheduled( 'pr_core_verification_scan' );
|
||||
$this->assertNotFalse( $scheduled );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that same cadence does not reschedule.
|
||||
*/
|
||||
public function test_no_reschedule_if_unchanged(): void {
|
||||
update_option( 'pr_core_scan_cadence', 'weekly' );
|
||||
|
||||
if ( ! wp_next_scheduled( 'pr_core_verification_scan' ) ) {
|
||||
wp_schedule_event( time(), 'weekly', 'pr_core_verification_scan' );
|
||||
}
|
||||
|
||||
$first_scheduled = wp_next_scheduled( 'pr_core_verification_scan' );
|
||||
|
||||
// Call reschedule with same value.
|
||||
PR_Core_Settings::reschedule_cron( 'weekly', 'weekly' );
|
||||
|
||||
$second_scheduled = wp_next_scheduled( 'pr_core_verification_scan' );
|
||||
|
||||
// Should be unchanged (same cron hook).
|
||||
$this->assertEquals( $first_scheduled, $second_scheduled );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cadence sanitization.
|
||||
*/
|
||||
public function test_sanitize_cadence(): void {
|
||||
$this->assertEquals( 'daily', PR_Core_Settings::sanitize_cadence( 'daily' ) );
|
||||
$this->assertEquals( 'weekly', PR_Core_Settings::sanitize_cadence( 'weekly' ) );
|
||||
$this->assertEquals( 'monthly', PR_Core_Settings::sanitize_cadence( 'monthly' ) );
|
||||
$this->assertEquals( 'weekly', PR_Core_Settings::sanitize_cadence( 'invalid' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test email sanitization.
|
||||
*/
|
||||
public function test_sanitize_emails(): void {
|
||||
$input = 'admin@example.com, editor@example.com';
|
||||
$output = PR_Core_Settings::sanitize_emails( $input );
|
||||
$this->assertStringContainsString( 'admin@example.com', $output );
|
||||
$this->assertStringContainsString( 'editor@example.com', $output );
|
||||
|
||||
// Invalid emails filtered out.
|
||||
$input_bad = 'admin@example.com, invalid-email, editor@example.com';
|
||||
$output = PR_Core_Settings::sanitize_emails( $input_bad );
|
||||
$this->assertStringContainsString( 'admin@example.com', $output );
|
||||
$this->assertStringContainsString( 'editor@example.com', $output );
|
||||
$this->assertStringNotContainsString( 'invalid-email', $output );
|
||||
if ( ! function_exists( 'get_option' ) ) {
|
||||
function get_option( string $key, $default = false ) {
|
||||
return $GLOBALS['pr_core_options'][ $key ] ?? $default;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'update_option' ) ) {
|
||||
function update_option( string $key, $value ): bool {
|
||||
$GLOBALS['pr_core_options'][ $key ] = $value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'register_setting' ) ) {
|
||||
function register_setting( string $group, string $option, array $args = [] ): void {}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'add_settings_section' ) ) {
|
||||
function add_settings_section( string $id, string $title, $cb, string $page ): void {}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'add_settings_field' ) ) {
|
||||
function add_settings_field( string $id, string $title, $cb, string $page, string $section = '' ): void {}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'is_email' ) ) {
|
||||
function is_email( string $email ): bool {
|
||||
return (bool) filter_var( $email, FILTER_VALIDATE_EMAIL );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_clear_scheduled_hook' ) ) {
|
||||
function wp_clear_scheduled_hook( string $hook ): void {
|
||||
$GLOBALS['pr_core_cron_calls'][] = [ 'action' => 'clear', 'hook' => $hook ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_schedule_event' ) ) {
|
||||
function wp_schedule_event( int $timestamp, string $recurrence, string $hook ): void {
|
||||
$GLOBALS['pr_core_cron_calls'][] = [ 'action' => 'schedule', 'hook' => $hook, 'recurrence' => $recurrence ];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Load the settings class ─────────────────────────────────────────────
|
||||
|
||||
require_once __DIR__ . '/../../includes/cpt/class-pr-core-peptide-cpt.php';
|
||||
require_once __DIR__ . '/../../includes/admin/class-pr-core-settings.php';
|
||||
|
||||
echo "== PR_Core_Settings verification methods unit tests ==\n\n";
|
||||
|
||||
// ── sanitize_cadence() ──────────────────────────────────────────────────
|
||||
|
||||
echo "sanitize_cadence():\n";
|
||||
pr_assert_equals( 'daily', PR_Core_Settings::sanitize_cadence( 'daily' ), 'daily → daily' );
|
||||
pr_assert_equals( 'weekly', PR_Core_Settings::sanitize_cadence( 'weekly' ), 'weekly → weekly' );
|
||||
pr_assert_equals( 'monthly', PR_Core_Settings::sanitize_cadence( 'monthly' ), 'monthly → monthly' );
|
||||
pr_assert_equals( 'weekly', PR_Core_Settings::sanitize_cadence( 'hourly' ), 'hourly (invalid) → weekly default' );
|
||||
pr_assert_equals( 'weekly', PR_Core_Settings::sanitize_cadence( '' ), 'empty → weekly default' );
|
||||
pr_assert_equals( 'weekly', PR_Core_Settings::sanitize_cadence( 'DAILY' ), 'uppercase → weekly (case-sensitive)' );
|
||||
|
||||
// ── sanitize_emails() ───────────────────────────────────────────────────
|
||||
|
||||
echo "\nsanitize_emails():\n";
|
||||
pr_assert_equals( '', PR_Core_Settings::sanitize_emails( '' ), 'empty → empty' );
|
||||
pr_assert_equals( 'admin@example.com', PR_Core_Settings::sanitize_emails( 'admin@example.com' ), 'valid single → kept' );
|
||||
pr_assert_equals( '', PR_Core_Settings::sanitize_emails( 'not-an-email' ), 'invalid single → empty' );
|
||||
pr_assert_equals( 'a@test.com, b@test.com', PR_Core_Settings::sanitize_emails( 'a@test.com, b@test.com' ), 'valid pair → both kept' );
|
||||
pr_assert_equals( 'good@test.com', PR_Core_Settings::sanitize_emails( 'good@test.com, bad-email' ), 'mixed → only valid kept' );
|
||||
pr_assert_equals( 'trimmed@test.com', PR_Core_Settings::sanitize_emails( ' trimmed@test.com ' ), 'whitespace trimmed around valid email' );
|
||||
|
||||
// ── reschedule_cron(): no-op when same value ────────────────────────────
|
||||
|
||||
echo "\nreschedule_cron() — no-op when value unchanged:\n";
|
||||
$GLOBALS['pr_core_cron_calls'] = [];
|
||||
PR_Core_Settings::reschedule_cron( 'weekly', 'weekly' );
|
||||
pr_assert( empty( $GLOBALS['pr_core_cron_calls'] ), 'no cron calls when old === new' );
|
||||
|
||||
// ── reschedule_cron(): clears + reschedules on change ───────────────────
|
||||
|
||||
echo "\nreschedule_cron() — reschedules when value changes:\n";
|
||||
$GLOBALS['pr_core_cron_calls'] = [];
|
||||
PR_Core_Settings::reschedule_cron( 'weekly', 'daily' );
|
||||
|
||||
$actions = array_column( $GLOBALS['pr_core_cron_calls'], 'action' );
|
||||
pr_assert( in_array( 'clear', $actions, true ), 'wp_clear_scheduled_hook was called' );
|
||||
pr_assert( in_array( 'schedule', $actions, true ), 'wp_schedule_event was called' );
|
||||
|
||||
$schedule_call = null;
|
||||
foreach ( $GLOBALS['pr_core_cron_calls'] as $call ) {
|
||||
if ( 'schedule' === $call['action'] ) {
|
||||
$schedule_call = $call;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_assert( null !== $schedule_call, 'schedule call found' );
|
||||
pr_assert_equals( 'pr_core_verification_scan', $schedule_call['hook'] ?? '', 'rescheduled hook = pr_core_verification_scan' );
|
||||
pr_assert_equals( 'daily', $schedule_call['recurrence'] ?? '', 'rescheduled with new cadence "daily"' );
|
||||
|
||||
// Clear fires before schedule (correct ordering).
|
||||
$clear_idx = array_search( 'clear', $actions, true );
|
||||
$schedule_idx = array_search( 'schedule', $actions, true );
|
||||
pr_assert( $clear_idx < $schedule_idx, 'clear fires before schedule' );
|
||||
|
||||
// ── Summary ─────────────────────────────────────────────────────────────
|
||||
|
||||
exit( pr_test_summary() );
|
||||
|
||||
Reference in New Issue
Block a user