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 / RemoteRequestMemoTest.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
RemoteRequestMemoTest.php
186 lines
1 <?php
2
3 namespace SmashBalloon\Reviews\Tests\Unit;
4
5 use PHPUnit\Framework\TestCase;
6 use SmashBalloon\Reviews\Common\RemoteRequest;
7
8 /**
9 * Unit tests for the per-request memoization on RemoteRequest::fetch()
10 * (SMASH-1360 Phase 2).
11 *
12 * Pins the memo-key contract: same `(endpoint, provider, place_id, …)`
13 * regardless of `source_id` collapses to the same memo key. The wp_lhr_log
14 * captured on demo-wp2 2026-05-06 showed the customizer firing duplicate
15 * calls — one with `source_id=N&place_id=X`, one with `place_id=X` only.
16 * Without the memo, both would have hit the relay (and the relay-side cache
17 * absorbs the upstream cost but the WP host still pays the HTTP roundtrip).
18 * With the memo, the second call is served from in-process state.
19 *
20 * @group memo
21 * @group SMASH-1360
22 */
23 class RemoteRequestMemoTest extends TestCase
24 {
25 protected function setUp(): void
26 {
27 parent::setUp();
28 // Static state — flush between tests so prior runs don't leak.
29 RemoteRequest::flush_memo();
30 }
31
32 private function makeRequest(string $place_id, ?int $relay_source_id = null): RemoteRequest
33 {
34 $args = [
35 'business' => $place_id,
36 'info' => $relay_source_id !== null ? ['relay_source_id' => $relay_source_id] : [],
37 ];
38
39 return new RemoteRequest('google', $args, 'reviews');
40 }
41
42 public function test_memo_key_collapses_source_id_and_no_source_id_for_same_place(): void
43 {
44 // The two URL shapes captured in the M4 amplifier evidence:
45 // - /reviews/google?place_id=X&source_id=20
46 // - /reviews/google?place_id=X
47 // Both must produce the SAME memo key — they fetch the same upstream data.
48
49 $with_source_id = $this->makeRequest('CHIJ_TEST', 20);
50 $without_source_id = $this->makeRequest('CHIJ_TEST', null);
51
52 $args_a = ['place_id' => 'CHIJ_TEST', 'source_id' => 20, 'api_key' => 'KEY'];
53 $args_b = ['place_id' => 'CHIJ_TEST', 'api_key' => 'KEY'];
54
55 $key_a = $with_source_id->memo_key($args_a);
56 $key_b = $without_source_id->memo_key($args_b);
57
58 $this->assertNotNull($key_a);
59 $this->assertNotNull($key_b);
60 $this->assertSame($key_a, $key_b, 'source_id presence/absence must collapse to same memo key');
61 }
62
63 public function test_memo_key_differs_for_different_place_ids(): void
64 {
65 $req = $this->makeRequest('CHIJ_AFI');
66
67 $key_afi = $req->memo_key(['place_id' => 'CHIJ_AFI', 'api_key' => 'K']);
68 $key_googleplex = $req->memo_key(['place_id' => 'CHIJ_GOOGLEPLEX', 'api_key' => 'K']);
69
70 $this->assertNotSame($key_afi, $key_googleplex, 'Different place_ids MUST get different memo keys');
71 }
72
73 public function test_memo_key_differs_for_different_endpoints(): void
74 {
75 $reviews = new RemoteRequest('google', ['business' => 'X'], 'reviews');
76 $sources = new RemoteRequest('google', ['business' => 'X'], 'sources');
77
78 $args = ['place_id' => 'X', 'api_key' => 'K'];
79
80 $this->assertNotSame(
81 $reviews->memo_key($args),
82 $sources->memo_key($args),
83 '/reviews/google and /sources/google return different bytes — must NOT share memo'
84 );
85 }
86
87 public function test_memo_key_differs_for_different_providers(): void
88 {
89 $google = new RemoteRequest('google', ['business' => 'X'], 'reviews');
90 $yelp = new RemoteRequest('yelp', ['business' => 'X'], 'reviews');
91
92 $args = ['place_id' => 'X', 'api_key' => 'K'];
93
94 $this->assertNotSame(
95 $google->memo_key($args),
96 $yelp->memo_key($args),
97 'Different providers MUST get different memo keys'
98 );
99 }
100
101 public function test_memo_key_differs_for_different_languages(): void
102 {
103 $req = $this->makeRequest('X');
104
105 $en = $req->memo_key(['place_id' => 'X', 'api_key' => 'K', 'language' => 'en']);
106 $ro = $req->memo_key(['place_id' => 'X', 'api_key' => 'K', 'language' => 'ro']);
107
108 $this->assertNotSame($en, $ro, 'Language is part of upstream response shape — must differentiate');
109 }
110
111 public function test_memo_key_differs_for_different_stars_filters(): void
112 {
113 $req = $this->makeRequest('X');
114
115 $no_filter = $req->memo_key(['place_id' => 'X', 'api_key' => 'K']);
116 $filtered = $req->memo_key(['place_id' => 'X', 'api_key' => 'K', 'starsFilter' => '5']);
117
118 $this->assertNotSame($no_filter, $filtered);
119 }
120
121 public function test_memo_key_canonicalizes_param_order(): void
122 {
123 $req = $this->makeRequest('X');
124
125 $a = $req->memo_key(['place_id' => 'X', 'api_key' => 'K', 'language' => 'en']);
126 $b = $req->memo_key(['language' => 'en', 'api_key' => 'K', 'place_id' => 'X']);
127
128 $this->assertSame($a, $b, 'Memo key derivation MUST be order-independent (ksort applied)');
129 }
130
131 public function test_memo_key_returns_null_for_array_typed_arg(): void
132 {
133 // Defensive: if anywhere in the args we get an array-typed value
134 // (e.g., a future caller passes downloads list as args directly),
135 // refuse to memoize — fall through to a real fetch instead of
136 // hashing unstably.
137 $req = $this->makeRequest('X');
138
139 $key = $req->memo_key([
140 'place_id' => 'X',
141 'api_key' => 'K',
142 'downloads' => [1, 2, 3], // array-typed value
143 ]);
144
145 $this->assertNull($key, 'Array-typed args MUST bypass memo (mirrors relay-side EXCLUDED_PARAMS policy)');
146 }
147
148 public function test_memo_key_excludes_source_id_only_difference(): void
149 {
150 // Direct echo of the wp_lhr_log demo-wp2 evidence — rows 3+5 had
151 // source_id=20, rows 7+9 didn't. They MUST collapse.
152 $req = $this->makeRequest('CHIJ_M4');
153
154 $with_sid = $req->memo_key(['place_id' => 'CHIJ_M4', 'source_id' => 20, 'api_key' => 'K']);
155 $no_sid = $req->memo_key(['place_id' => 'CHIJ_M4', 'api_key' => 'K']);
156
157 $this->assertSame($with_sid, $no_sid);
158 }
159
160 public function test_memo_key_includes_api_key_difference(): void
161 {
162 // Different api_keys MUST produce different memo keys — this is the
163 // tenant isolation primitive on the plugin side. Mirrors the
164 // relay-side `sha256(api_key)` discriminator.
165 $req = $this->makeRequest('X');
166
167 $tenant_a = $req->memo_key(['place_id' => 'X', 'api_key' => 'AIza-tenant-a-key']);
168 $tenant_b = $req->memo_key(['place_id' => 'X', 'api_key' => 'AIza-tenant-b-key']);
169
170 $this->assertNotSame($tenant_a, $tenant_b, 'Different api_keys MUST get different memo keys');
171 }
172
173 public function test_flush_memo_clears_state_for_test_isolation(): void
174 {
175 // The test-only flush helper must actually empty the memo.
176 // Verified indirectly via the protected setUp() — if flush didn't
177 // work, prior-test state would leak and these tests would interact.
178 // This test exists primarily as an explicit contract pin.
179 RemoteRequest::flush_memo();
180 // No fluent state to check from outside; the assertion is just the
181 // observation that subsequent tests pass cleanly when they call
182 // flush_memo() in their own setUp.
183 $this->assertTrue(true);
184 }
185 }
186