Doubles
1 month ago
Providers
1 month ago
BulkRearmOnGrowthTest.php
1 month ago
BulkReviewsUpdateStuckStateTest.php
1 month ago
ClearCacheRelayResetTest.php
1 month ago
DeleteSourceRelayFailureTest.php
1 month ago
ErrorHandlerFalsyOptionTest.php
1 month ago
FeedCacheUpdateServiceTest.php
1 month ago
FeedMalformedPayloadTest.php
1 month ago
ForceKeylessRefetchTest.php
1 month ago
LicenseDeactivateStaleStateTest.php
1 month ago
MediaFinderMemoTest.php
1 month ago
MultiSourceAggregationTest.php
1 month ago
ReconcileMigratedLicenseRoutineTest.php
1 month ago
ReconcileRemovalTest.php
1 month ago
RegisterWebsiteRoutineTest.php
1 month ago
RemoteRequestMemoTest.php
1 month ago
ReviewAlertHeaderTotalsTest.php
1 month ago
ReviewAlertPageTargetingTest.php
1 month ago
ReviewAlertStarFillTest.php
1 month ago
ShortcodeNeutralizationTest.php
1 month ago
SiteMigrationRecoveryTest.php
1 month ago
Smash1583HeaderParityTest.php
1 month ago
Smash1631MultiLanguageBulkTest.php
1 month ago
Smash1631UpdateSingleLangScopeTest.php
1 month ago
Smash1706TripAdvisorPlaceIdTest.php
1 month ago
Smash782BookingHeaderRatingTest.php
1 month ago
Smash782CountryFlagEmojiTest.php
1 month ago
Smash782ExternalRefreshCronTest.php
1 month ago
Smash782ExtrasTemplateTest.php
1 month ago
Smash782ReviewAlertProviderDataTest.php
1 month ago
SourceIdLookupTest.php
1 month ago
WpmlGetCurrentLanguageTest.php
1 month ago
WpmlLanguageMappingTest.php
1 month ago
ErrorHandlerFalsyOptionTest.php
132 lines
| 1 | <?php |
| 2 | |
| 3 | namespace SmashBalloon\Reviews\Tests\Unit; |
| 4 | |
| 5 | use PHPUnit\Framework\TestCase; |
| 6 | use SmashBalloon\Reviews\Common\Helpers\SBR_Error_Handler; |
| 7 | |
| 8 | /** |
| 9 | * Regression tests for SMASH-1544. |
| 10 | * |
| 11 | * On PHP 8, when the `sbr_errors` option row EXISTS in the DB but holds a |
| 12 | * falsy value (boolean false / '' — corrupted, legacy or third-party write), |
| 13 | * get_option('sbr_errors', []) returns that falsy value, NOT the [] default |
| 14 | * (the default only fires when the row is absent — see |
| 15 | * wp-includes/option.php). The handler then passed that bool to foreach() |
| 16 | * and array_push(), producing: |
| 17 | * |
| 18 | * PHP Warning: foreach() argument must be of type array|object, false given |
| 19 | * PHP Fatal error: Uncaught TypeError: array_push(): Argument #1 ($array) |
| 20 | * must be of type array, false given |
| 21 | * |
| 22 | * which took down the feed builder admin page and the WP-Cron cache update. |
| 23 | * |
| 24 | * These tests drive the falsy-stored-value path. failOnWarning="true" in |
| 25 | * phpunit.xml means even the foreach() warning fails the suite pre-fix. |
| 26 | * |
| 27 | * @group SMASH-1544 |
| 28 | * @covers \SmashBalloon\Reviews\Common\Helpers\SBR_Error_Handler |
| 29 | */ |
| 30 | class ErrorHandlerFalsyOptionTest extends TestCase |
| 31 | { |
| 32 | protected function setUp(): void |
| 33 | { |
| 34 | parent::setUp(); |
| 35 | global $wp_options_mock; |
| 36 | $wp_options_mock = []; |
| 37 | } |
| 38 | |
| 39 | protected function tearDown(): void |
| 40 | { |
| 41 | global $wp_options_mock; |
| 42 | $wp_options_mock = []; |
| 43 | parent::tearDown(); |
| 44 | } |
| 45 | |
| 46 | private function sampleError(): array |
| 47 | { |
| 48 | return [ |
| 49 | 'type' => 'connection', |
| 50 | 'id' => 'feed_1', |
| 51 | 'provider' => 'google', |
| 52 | 'message' => 'API key missing', |
| 53 | ]; |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * The exact customer condition: option row present, value === false. |
| 58 | * Pre-fix this fataled at array_push(false, ...). Post-fix it logs cleanly. |
| 59 | */ |
| 60 | public function test_log_error_does_not_fatal_when_option_stored_as_false(): void |
| 61 | { |
| 62 | global $wp_options_mock; |
| 63 | $wp_options_mock['sbr_errors'] = false; |
| 64 | |
| 65 | SBR_Error_Handler::log_error($this->sampleError()); |
| 66 | |
| 67 | $stored = SBR_Error_Handler::get_errors(); |
| 68 | $this->assertIsArray($stored); |
| 69 | $this->assertCount(1, $stored); |
| 70 | // @phpstan-ignore-next-line — assertCount above guarantees offset 0; get_errors() returns an untyped array. |
| 71 | $this->assertSame('feed_1', $stored[0]['id']); |
| 72 | } |
| 73 | |
| 74 | /** Empty-string is the other falsy shape a corrupted option row can take. */ |
| 75 | public function test_log_error_does_not_fatal_when_option_stored_as_empty_string(): void |
| 76 | { |
| 77 | global $wp_options_mock; |
| 78 | $wp_options_mock['sbr_errors'] = ''; |
| 79 | |
| 80 | SBR_Error_Handler::log_error($this->sampleError()); |
| 81 | |
| 82 | $this->assertCount(1, SBR_Error_Handler::get_errors()); |
| 83 | } |
| 84 | |
| 85 | /** get_errors() must always hand back an array, whatever the stored shape. */ |
| 86 | public function test_get_errors_always_returns_array(): void |
| 87 | { |
| 88 | // `null` is deliberately omitted: the bootstrap get_option() stub uses |
| 89 | // `$wp_options_mock[$option] ?? $default`, so a stored null collapses to |
| 90 | // the [] default (same as "option not set") and would not exercise the |
| 91 | // is_array() coercion. The remaining falsy/non-array shapes do. SMASH-1544. |
| 92 | global $wp_options_mock; |
| 93 | foreach ([false, '', 0, 'not-an-array', 42] as $bad) { |
| 94 | $wp_options_mock['sbr_errors'] = $bad; |
| 95 | $this->assertIsArray( |
| 96 | SBR_Error_Handler::get_errors(), |
| 97 | 'get_errors() must coerce non-array stored value to []' |
| 98 | ); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | /** check_error() must not trip foreach() on a falsy stored value. */ |
| 103 | public function test_check_error_handles_falsy_option(): void |
| 104 | { |
| 105 | global $wp_options_mock; |
| 106 | $wp_options_mock['sbr_errors'] = false; |
| 107 | |
| 108 | $this->assertSame('not_defined', SBR_Error_Handler::check_error($this->sampleError())); |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * BC: the normal path (option absent, then a real array) is unchanged. |
| 113 | * An existing caller relying on append-then-read still works. |
| 114 | */ |
| 115 | public function test_existing_array_path_is_preserved(): void |
| 116 | { |
| 117 | // Option absent -> [] default -> first log creates the array. |
| 118 | SBR_Error_Handler::log_error($this->sampleError()); |
| 119 | |
| 120 | $second = $this->sampleError(); |
| 121 | $second['id'] = 'feed_2'; |
| 122 | SBR_Error_Handler::log_error($second); |
| 123 | |
| 124 | $stored = SBR_Error_Handler::get_errors(); |
| 125 | $this->assertCount(2, $stored); |
| 126 | // @phpstan-ignore-next-line — assertCount above guarantees offset 0; get_errors() returns an untyped array. |
| 127 | $this->assertSame('feed_1', $stored[0]['id']); |
| 128 | // @phpstan-ignore-next-line — assertCount above guarantees offset 1. |
| 129 | $this->assertSame('feed_2', $stored[1]['id']); |
| 130 | } |
| 131 | } |
| 132 |