PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.8.0
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.8.0
2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.7 2.6.8 2.6.5 2.6.4 2.6.3 2.6.2 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 trunk 1.0 1.0.1 1.0.2 1.0.3 1.1 1.1.1 1.1.2 1.2.0 2.0 2.1.0 2.1.1 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1
reviews-feed / tests / Unit / ForceKeylessRefetchTest.php
reviews-feed / tests / Unit Last commit date
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 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
ForceKeylessRefetchTest.php
223 lines
1 <?php
2
3 namespace SmashBalloon\Reviews\Tests\Unit;
4
5 use PHPUnit\Framework\TestCase;
6 use SmashBalloon\Reviews\Common\Utils\FreeRetriever as CommonFreeRetriever;
7 use SmashBalloon\Reviews\Pro\Utils\FreeRetriever as ProFreeRetriever;
8
9 /**
10 * Pins the keyless "force one-time refetch on Clear All Caches" behaviour.
11 *
12 * Background: a keyless Google/Yelp/TripAdvisor/Trustpilot source that already
13 * has cached reviews is gated by the plugin's local "already fetched" belt
14 * (FreeRetriever::limit_review_api_call -> SBR_Sources::already_fetched /
15 * already_fetched_week). Clear All Caches resets the relay's SERVER-side weekly
16 * window but keeps the cached rows, so before this fix the immediate refresh
17 * still skipped the keyless relay call and the feed could never refetch without
18 * manual row deletion.
19 *
20 * The fix is a short-lived, TTL-bounded transient flag the belt honours (a
21 * time window, NOT a single-use token — it is not consumed per call; the
22 * relay's own weekly window is the real per-source cap). It is NON-destructive
23 * (cached reviews stay, so a failed refetch keeps the existing feed) and BC-safe
24 * (when the flag is unset, the belt behaves exactly as before).
25 *
26 * Behaviour tests exercise should_force_refetch() directly (it only reads the
27 * transient, so no $wpdb is needed). The full limit_review_api_call() path hits
28 * SBR_Sources static DB queries that the plain-PHPUnit suite can't provide, so
29 * the wiring of `&& ! self::should_force_refetch()` into both belts — and the
30 * flag-set in clear_all_caches() — is protected by source-level guards, matching
31 * the ClearCacheRelayResetTest convention.
32 */
33 final class ForceKeylessRefetchTest extends TestCase
34 {
35 protected function setUp(): void
36 {
37 parent::setUp();
38 // Isolate the transient mock between cases.
39 global $wp_transients_mock;
40 $wp_transients_mock = [];
41 }
42
43 protected function tearDown(): void
44 {
45 global $wp_transients_mock;
46 $wp_transients_mock = [];
47 parent::tearDown();
48 }
49
50 private static function invokeShouldForceRefetch(string $class): bool
51 {
52 $m = new \ReflectionMethod($class, 'should_force_refetch');
53 $m->setAccessible(true);
54
55 return (bool) $m->invoke(null);
56 }
57
58 private static function commonSource(): string
59 {
60 $path = __DIR__ . '/../../class/Common/Utils/FreeRetriever.php';
61 self::assertFileExists($path);
62
63 return (string) file_get_contents($path);
64 }
65
66 private static function proSource(): string
67 {
68 $path = __DIR__ . '/../../class/Pro/Utils/FreeRetriever.php';
69 self::assertFileExists($path);
70
71 return (string) file_get_contents($path);
72 }
73
74 private static function saverSource(): string
75 {
76 $path = __DIR__ . '/../../class/Common/Builder/SBR_Feed_Saver_Manager.php';
77 self::assertFileExists($path);
78
79 return (string) file_get_contents($path);
80 }
81
82 // ---- Behaviour: should_force_refetch() reflects the transient ----
83
84 public function test_should_force_refetch_is_false_when_flag_unset(): void
85 {
86 $this->assertFalse(
87 self::invokeShouldForceRefetch(CommonFreeRetriever::class),
88 'With no flag set the belt must stay closed (normal/BC behaviour).'
89 );
90 }
91
92 public function test_should_force_refetch_is_true_when_flag_set(): void
93 {
94 set_transient(CommonFreeRetriever::FORCE_REFETCH_FLAG, 1, 300);
95
96 $this->assertTrue(
97 self::invokeShouldForceRefetch(CommonFreeRetriever::class),
98 'Setting the flag must open the belt for a one-time refetch.'
99 );
100 }
101
102 public function test_should_force_refetch_is_false_after_flag_deleted(): void
103 {
104 set_transient(CommonFreeRetriever::FORCE_REFETCH_FLAG, 1, 300);
105 delete_transient(CommonFreeRetriever::FORCE_REFETCH_FLAG);
106
107 $this->assertFalse(
108 self::invokeShouldForceRefetch(CommonFreeRetriever::class),
109 'Once the flag expires or is cleared the belt must close again — no permanent bypass.'
110 );
111 }
112
113 public function test_flag_stays_open_for_multiple_reads_within_window(): void
114 {
115 // The flag is a TTL window, not a single-use token: it is NOT consumed on
116 // read, so every source in a multi-source refresh within the window gets
117 // the bypass. (Cost is bounded by the relay's per-source weekly re-close,
118 // not by consuming this flag.) Pin that contract so a future "make it
119 // one-shot" change can't silently break multi-source refetch.
120 set_transient(CommonFreeRetriever::FORCE_REFETCH_FLAG, 1, 300);
121
122 $this->assertTrue(self::invokeShouldForceRefetch(CommonFreeRetriever::class), 'first read');
123 $this->assertTrue(self::invokeShouldForceRefetch(CommonFreeRetriever::class), 'second read');
124 $this->assertTrue(self::invokeShouldForceRefetch(ProFreeRetriever::class), 'third read (Pro)');
125 }
126
127 public function test_pro_inherits_should_force_refetch_and_the_same_flag(): void
128 {
129 // Pro extends Common; self::should_force_refetch() / self::FORCE_REFETCH_FLAG
130 // in the Pro belt must resolve to the inherited member, not a separate one.
131 set_transient(CommonFreeRetriever::FORCE_REFETCH_FLAG, 1, 300);
132
133 $this->assertSame(
134 CommonFreeRetriever::FORCE_REFETCH_FLAG,
135 ProFreeRetriever::FORCE_REFETCH_FLAG,
136 'Pro must share the exact same flag constant as Common.'
137 );
138 $this->assertTrue(
139 self::invokeShouldForceRefetch(ProFreeRetriever::class),
140 'Pro must honour the same flag through the inherited helper.'
141 );
142 }
143
144 // ---- Guard: the flag name survives clear_plugin_cache()'s transient purge ----
145
146 public function test_flag_uses_sbreviews_prefix_to_survive_cache_purge(): void
147 {
148 // clear_plugin_cache() deletes `_transient_sbr_%`. A `sbr_`-prefixed flag
149 // would be swept immediately; the `sbreviews_` prefix dodges that LIKE.
150 $flag = CommonFreeRetriever::FORCE_REFETCH_FLAG;
151 $this->assertStringStartsWith('sbreviews_', $flag);
152 $this->assertFalse(
153 str_starts_with($flag, 'sbr_'),
154 'Flag must not match the `_transient_sbr_%` purge in clear_plugin_cache().'
155 );
156 }
157
158 // ---- Guards: the belts honour the flag ----
159
160 public function test_common_belt_honours_force_flag(): void
161 {
162 $this->assertMatchesRegularExpression(
163 '/already_fetched\([^)]*\);\s*\n\s*if \(\$limit_current && ! self::should_force_refetch\(\)\)/',
164 self::commonSource(),
165 'Common limit_review_api_call() must bypass already_fetched when the flag is set.'
166 );
167 }
168
169 public function test_pro_belt_honours_force_flag(): void
170 {
171 $this->assertMatchesRegularExpression(
172 '/already_fetched_week\([^)]*\);\s*\n\s*if \(\$already_fetched && ! self::should_force_refetch\(\)\)/',
173 self::proSource(),
174 'Pro limit_review_api_call() must bypass already_fetched_week when the flag is set.'
175 );
176 }
177
178 // ---- Guards: clear_all_caches() sets the flag, filtered, after the purge, bounded ----
179
180 public function test_clear_all_caches_sets_the_force_flag(): void
181 {
182 $this->assertStringContainsString(
183 'FreeRetriever::FORCE_REFETCH_FLAG',
184 self::saverSource(),
185 'Clear All Caches must set the keyless force-refetch flag.'
186 );
187 }
188
189 public function test_force_flag_set_is_filterable(): void
190 {
191 $this->assertStringContainsString(
192 "apply_filters('sbr_force_keyless_refetch_on_clear', true)",
193 self::saverSource(),
194 'The force-refetch flag must be filterable so support can disable it without a redeploy.'
195 );
196 }
197
198 public function test_force_flag_is_set_after_clear_plugin_cache(): void
199 {
200 $src = self::saverSource();
201 $purgePos = strpos($src, 'self::clear_plugin_cache()');
202 $flagPos = strpos($src, 'FreeRetriever::FORCE_REFETCH_FLAG');
203 $this->assertNotFalse($purgePos);
204 $this->assertNotFalse($flagPos);
205 $this->assertLessThan(
206 $flagPos,
207 $purgePos,
208 'The flag must be set AFTER clear_plugin_cache() so its transient purge does not remove it.'
209 );
210 }
211
212 public function test_force_flag_has_a_bounded_short_ttl(): void
213 {
214 // A permanent (TTL 0 / no expiry) flag would keep the belt open and let
215 // every render re-call the relay — a cost/loop risk. Pin a bounded TTL.
216 $this->assertMatchesRegularExpression(
217 '/set_transient\(\s*\\\\SmashBalloon\\\\Reviews\\\\Common\\\\Utils\\\\FreeRetriever::FORCE_REFETCH_FLAG,\s*1,\s*\d+\s*\*\s*MINUTE_IN_SECONDS\s*\)/',
218 self::saverSource(),
219 'The force-refetch flag must use a bounded MINUTE_IN_SECONDS TTL (one-shot, not permanent).'
220 );
221 }
222 }
223