PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.9.0
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.9.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 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
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