PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.7.0
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.7.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 / Smash1631UpdateSingleLangScopeTest.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
Smash1631UpdateSingleLangScopeTest.php
230 lines
1 <?php
2
3 namespace SmashBalloon\Reviews\Tests\Unit;
4
5 use PHPUnit\Framework\TestCase;
6 use SmashBalloon\Reviews\Common\SinglePostCache as CommonSinglePostCache;
7 use SmashBalloon\Reviews\Pro\SinglePostCache as ProSinglePostCache;
8 use SmashBalloon\Reviews\Pro\MediaFinder;
9
10 /**
11 * SMASH-1631 regression: SinglePostCache::update_single() must scope its UPDATE by
12 * language.
13 *
14 * A Google review_id carries no language, so on a WPML multi-language site the same
15 * review is cached once per language (unique key: post_id, provider_id, lang). The
16 * update path matched on post_id ALONE, so one language's update overwrote every
17 * sibling-language row for that review — collapsing them all onto the last-written
18 * text (observed: en/fr/ro rows all became the Spanish original after a bulk re-run).
19 * db_record() — the existence check that gates update_single() — already keys on
20 * (post_id, lang, provider_id); these tests pin that update_single() now mirrors the
21 * lang dimension so an update only ever touches the row for its own language.
22 *
23 * Covers BOTH the Pro override (the runtime bulk-cron path) and the Common base.
24 */
25 class Smash1631UpdateSingleLangScopeTest extends TestCase
26 {
27 /** @var mixed */
28 private $previous_wpdb;
29
30 protected function setUp(): void
31 {
32 parent::setUp();
33 $this->previous_wpdb = $GLOBALS['wpdb'] ?? null;
34
35 // $wpdb double that records the arguments handed to update() so we can assert
36 // the WHERE clause. Returns 1 (rows affected) so update_single() sees success.
37 $GLOBALS['wpdb'] = new class {
38 /** @var string */
39 public $prefix = 'wp_';
40 /** @var int */
41 public $insert_id = 0;
42 /** @var array<string,mixed>|null */
43 public $last_update = null;
44
45 public function update($table, $data, $where, $format = null, $where_format = null)
46 {
47 $this->last_update = compact('table', 'data', 'where', 'format', 'where_format');
48 return 1;
49 }
50
51 public function prepare($query, ...$args)
52 {
53 return $query;
54 }
55
56 public function get_results($query, $output = null)
57 {
58 return [];
59 }
60 };
61 }
62
63 protected function tearDown(): void
64 {
65 $GLOBALS['wpdb'] = $this->previous_wpdb;
66 parent::tearDown();
67 }
68
69 /** A minimal Google review payload (Google is the only lang-aware provider). */
70 private function google_review(string $review_id = 'REVIEW_A'): array
71 {
72 return [
73 'review_id' => $review_id,
74 'text' => 'Thank you Roya and Genta.',
75 'rating' => 5,
76 'time' => 1783106917,
77 'reviewer' => ['name' => 'Judi Rawlings'],
78 'provider' => ['name' => 'google'],
79 ];
80 }
81
82 private function make_pro(array $review): ProSinglePostCache
83 {
84 return new ProSinglePostCache($review, new MediaFinder($review));
85 }
86
87 public function test_pro_update_single_scopes_where_by_language(): void
88 {
89 $cache = $this->make_pro($this->google_review());
90 $cache->set_provider_id('place_123');
91 $cache->set_lang('es-419');
92 $cache->update_single();
93
94 $where = $GLOBALS['wpdb']->last_update['where'];
95 $this->assertArrayHasKey('lang', $where, 'update_single() must include lang in its WHERE');
96 $this->assertSame('es-419', $where['lang']);
97 $this->assertSame('REVIEW_A', $where['post_id']);
98
99 // where_format must stay aligned with the where columns or $wpdb->update breaks.
100 $this->assertCount(
101 count($where),
102 $GLOBALS['wpdb']->last_update['where_format'],
103 'where_format count must match the number of WHERE columns'
104 );
105 }
106
107 public function test_common_update_single_scopes_where_by_language(): void
108 {
109 $cache = new CommonSinglePostCache($this->google_review());
110 $cache->set_provider_id('place_123');
111 $cache->set_lang('es-419');
112 $cache->update_single();
113
114 $where = $GLOBALS['wpdb']->last_update['where'];
115 $this->assertSame('es-419', $where['lang']);
116 $this->assertSame('REVIEW_A', $where['post_id']);
117 }
118
119 /**
120 * The core regression: two languages of the SAME review must produce DIFFERENT
121 * WHERE clauses, so updating one language can never match (and overwrite) the other.
122 */
123 public function test_two_languages_of_same_review_do_not_share_a_where(): void
124 {
125 $en = $this->make_pro($this->google_review('REVIEW_A'));
126 $en->set_provider_id('place_123');
127 $en->set_lang('en');
128 $en->update_single();
129 $en_where = $GLOBALS['wpdb']->last_update['where'];
130
131 $es = $this->make_pro($this->google_review('REVIEW_A'));
132 $es->set_provider_id('place_123');
133 $es->set_lang('es-419');
134 $es->update_single();
135 $es_where = $GLOBALS['wpdb']->last_update['where'];
136
137 $this->assertSame($en_where['post_id'], $es_where['post_id'], 'same review id');
138 $this->assertNotSame(
139 $en_where['lang'],
140 $es_where['lang'],
141 'the two languages must scope to different rows'
142 );
143 $this->assertSame('en', $en_where['lang']);
144 $this->assertSame('es-419', $es_where['lang']);
145 }
146
147 /**
148 * Backwards-compat: a non-lang provider never calls set_lang(), so lang stays the
149 * '' default. The WHERE still carries post_id (unchanged) plus lang='' — which
150 * matches the lang='' rows those providers have always stored, so their update
151 * behaviour is preserved.
152 */
153 public function test_non_lang_provider_keeps_empty_lang_where(): void
154 {
155 $review = [
156 'review_id' => 'YELP_1',
157 'text' => 'Great place.',
158 'rating' => 4,
159 'time' => 1783106917,
160 'reviewer' => ['name' => 'Sam'],
161 'provider' => ['name' => 'yelp'],
162 ];
163 $cache = $this->make_pro($review);
164 $cache->set_provider_id('yelp_biz_1');
165 // No set_lang() — mirrors the non-lang provider path.
166 $cache->update_single();
167
168 $where = $GLOBALS['wpdb']->last_update['where'];
169 $this->assertSame('YELP_1', $where['post_id']);
170 $this->assertSame('', $where['lang'], 'non-lang providers scope on the empty-string default');
171 }
172
173 /**
174 * The sibling update() method (used by the media-resize render path,
175 * Pro/Feed::find_and_resize_media) had the identical post_id-only WHERE and leaked
176 * the same cross-language overwrite. It must also scope by language.
177 */
178 public function test_pro_update_scopes_where_by_language(): void
179 {
180 $cache = $this->make_pro($this->google_review());
181 $cache->set_provider_id('place_123');
182 $cache->set_lang('es-419');
183 $cache->update([
184 ['images_done', 1, '%d'],
185 ['post_content', 'Gracias Roya', '%s'],
186 ]);
187
188 $where = $GLOBALS['wpdb']->last_update['where'];
189 $this->assertArrayHasKey('lang', $where, 'update() must include lang in its WHERE');
190 $this->assertSame('es-419', $where['lang']);
191 $this->assertSame('REVIEW_A', $where['post_id']);
192 $this->assertCount(count($where), $GLOBALS['wpdb']->last_update['where_format']);
193 }
194
195 public function test_common_update_scopes_where_by_language(): void
196 {
197 $cache = new CommonSinglePostCache($this->google_review());
198 $cache->set_provider_id('place_123');
199 $cache->set_lang('en');
200 $cache->update([['images_done', 1, '%d']]);
201
202 $where = $GLOBALS['wpdb']->last_update['where'];
203 $this->assertSame('en', $where['lang']);
204 $this->assertSame('REVIEW_A', $where['post_id']);
205 }
206
207 /**
208 * Core regression for the render path: resizing the media of one language's row
209 * must not overwrite a sibling language's row. Two languages of the same review
210 * produce different update() WHERE clauses.
211 */
212 public function test_update_two_languages_do_not_share_a_where(): void
213 {
214 $en = $this->make_pro($this->google_review('REVIEW_A'));
215 $en->set_provider_id('place_123');
216 $en->set_lang('en');
217 $en->update([['images_done', 1, '%d']]);
218 $en_where = $GLOBALS['wpdb']->last_update['where'];
219
220 $es = $this->make_pro($this->google_review('REVIEW_A'));
221 $es->set_provider_id('place_123');
222 $es->set_lang('es-419');
223 $es->update([['images_done', 1, '%d']]);
224 $es_where = $GLOBALS['wpdb']->last_update['where'];
225
226 $this->assertSame($en_where['post_id'], $es_where['post_id']);
227 $this->assertNotSame($en_where['lang'], $es_where['lang']);
228 }
229 }
230