PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.11.0
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.11.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 week ago Providers 1 week ago BulkRearmOnGrowthTest.php 1 week ago BulkReviewsUpdateStuckStateTest.php 1 week ago ClearCacheRelayResetTest.php 1 week ago DeleteSourceRelayFailureTest.php 1 week ago ErrorHandlerFalsyOptionTest.php 1 week ago FeedCacheUpdateServiceTest.php 1 week ago FeedMalformedPayloadTest.php 1 week ago ForceKeylessRefetchTest.php 1 week ago LicenseDeactivateStaleStateTest.php 1 week ago MediaFinderMemoTest.php 1 week ago MultiSourceAggregationTest.php 1 week ago ReconcileMigratedLicenseRoutineTest.php 1 week ago ReconcileRemovalTest.php 1 week ago RegisterWebsiteRoutineTest.php 1 week ago RelaySlowEndpointsTest.php 1 week ago RemoteRequestMemoTest.php 1 week ago ReviewAlertHeaderTotalsTest.php 1 week ago ReviewAlertPageTargetingTest.php 1 week ago ReviewAlertStarFillTest.php 1 week ago ShortcodeNeutralizationTest.php 1 week ago SiteMigrationRecoveryTest.php 1 week ago Smash1130UsageTrackingHardeningTest.php 1 week ago Smash1130UsageTrackingHooksTest.php 1 week ago Smash1583HeaderParityTest.php 1 week ago Smash1631MultiLanguageBulkTest.php 1 week ago Smash1631UpdateSingleLangScopeTest.php 1 week ago Smash1706TripAdvisorPlaceIdTest.php 1 week ago Smash1756SchemaServiceTest.php 1 week ago Smash1785AvatarLocalUrlGuardTest.php 1 week ago Smash1785AvatarReHealTest.php 1 week ago Smash1795ReviewTextXssTest.php 1 week ago Smash1835TripAdvisorKeyShapeTest.php 1 week ago Smash1973WordpressOrgPlaceIdNullTest.php 1 week ago Smash1987NestedSourceErrorShapeTest.php 1 week ago Smash782BookingHeaderRatingTest.php 1 week ago Smash782CountryFlagEmojiTest.php 1 week ago Smash782ExternalRefreshCronTest.php 1 week ago Smash782ExtrasTemplateTest.php 1 week ago Smash782ReviewAlertProviderDataTest.php 1 week ago SourceIdLookupTest.php 1 week ago WpmlGetCurrentLanguageTest.php 1 week ago WpmlLanguageMappingTest.php 1 week 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