$crit, 'recommended_count' => 0, 'ran_at' => '2026-04-28 03:30:00', 'tests' => array( 'wpdo_schema_drift' => array( 'status' => 'critical', 'description' => 'Missing tables: wpdo_audit', ), 'wpdo_error_budget' => array( 'status' => 'good', 'description' => 'OK', ), ), ); } public function test_default_disabled(): void { $this->assertFalse( WPDO_Email_Notifier::is_enabled() ); } public function test_recipient_falls_back_to_admin_email(): void { update_option( 'admin_email', 'admin@example.com', false ); $this->assertSame( 'admin@example.com', WPDO_Email_Notifier::recipient() ); update_option( 'wpdo_alert_email', 'alerts@example.com', false ); $this->assertSame( 'alerts@example.com', WPDO_Email_Notifier::recipient() ); } public function test_throttle_hours_clamps_to_range(): void { update_option( 'wpdo_alert_throttle_hours', 0, false ); $this->assertSame( 1, WPDO_Email_Notifier::throttle_hours() ); update_option( 'wpdo_alert_throttle_hours', 999, false ); $this->assertSame( 168, WPDO_Email_Notifier::throttle_hours() ); update_option( 'wpdo_alert_throttle_hours', 12, false ); $this->assertSame( 12, WPDO_Email_Notifier::throttle_hours() ); } public function test_maybe_send_skips_when_disabled(): void { $result = WPDO_Email_Notifier::maybe_send( $this->sample_summary() ); $this->assertFalse( $result ); $this->assertEmpty( $GLOBALS['_wpdo_mails'] ); } public function test_maybe_send_sends_when_enabled(): void { update_option( 'wpdo_email_alerts_enabled', '1', false ); update_option( 'wpdo_alert_email', 'ops@example.com', false ); $result = WPDO_Email_Notifier::maybe_send( $this->sample_summary() ); $this->assertTrue( $result ); $this->assertCount( 1, $GLOBALS['_wpdo_mails'] ); $mail = $GLOBALS['_wpdo_mails'][0]; $this->assertSame( 'ops@example.com', $mail['to'] ); $this->assertStringContainsString( 'wpdo_schema_drift', $mail['body'] ); $this->assertStringContainsString( 'WPDO 警告', $mail['subject'] ); } public function test_maybe_send_throttle_dedupes_same_fingerprint(): void { update_option( 'wpdo_email_alerts_enabled', '1', false ); update_option( 'wpdo_alert_email', 'ops@example.com', false ); $summary = $this->sample_summary(); $first = WPDO_Email_Notifier::maybe_send( $summary ); $second = WPDO_Email_Notifier::maybe_send( $summary ); $this->assertTrue( $first ); $this->assertFalse( $second, '同 fingerprint 第 2 次應 throttle' ); $this->assertCount( 1, $GLOBALS['_wpdo_mails'] ); } public function test_maybe_send_skips_invalid_email(): void { update_option( 'wpdo_email_alerts_enabled', '1', false ); update_option( 'wpdo_alert_email', 'not-an-email', false ); $result = WPDO_Email_Notifier::maybe_send( $this->sample_summary() ); $this->assertFalse( $result ); } public function test_fingerprint_changes_with_critical_count(): void { update_option( 'wpdo_email_alerts_enabled', '1', false ); update_option( 'wpdo_alert_email', 'ops@example.com', false ); $first = WPDO_Email_Notifier::maybe_send( $this->sample_summary( 1 ) ); // Different critical_count → different fingerprint → not throttled. $second = WPDO_Email_Notifier::maybe_send( $this->sample_summary( 5 ) ); $this->assertTrue( $first ); $this->assertTrue( $second, '不同 critical_count 應視為不同警告' ); $this->assertCount( 2, $GLOBALS['_wpdo_mails'] ); } }