PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.6.7
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.6.7
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 / FeedCacheUpdateServiceTest.php
reviews-feed / tests / Unit Last commit date
Doubles 2 months ago Providers 2 months ago BulkReviewsUpdateStuckStateTest.php 2 months ago ClearCacheRelayResetTest.php 2 months ago DeleteSourceRelayFailureTest.php 2 months ago ErrorHandlerFalsyOptionTest.php 2 months ago FeedCacheUpdateServiceTest.php 2 months ago FeedMalformedPayloadTest.php 2 months ago ForceKeylessRefetchTest.php 2 months ago LicenseDeactivateStaleStateTest.php 2 months ago MediaFinderMemoTest.php 2 months ago MultiSourceAggregationTest.php 2 months ago ReconcileMigratedLicenseRoutineTest.php 2 months ago ReconcileRemovalTest.php 2 months ago RegisterWebsiteRoutineTest.php 2 months ago RemoteRequestMemoTest.php 2 months ago ReviewAlertHeaderTotalsTest.php 2 months ago ReviewAlertPageTargetingTest.php 2 months ago ReviewAlertStarFillTest.php 2 months ago ShortcodeNeutralizationTest.php 2 months ago SiteMigrationRecoveryTest.php 2 months ago Smash1583HeaderParityTest.php 2 months ago SourceIdLookupTest.php 2 months ago WpmlGetCurrentLanguageTest.php 2 months ago WpmlLanguageMappingTest.php 2 months ago
FeedCacheUpdateServiceTest.php
166 lines
1 <?php
2
3 namespace SmashBalloon\Reviews\Tests\Unit;
4
5 require_once __DIR__ . '/Doubles/FeedCacheWpdbDouble.php';
6
7 use PHPUnit\Framework\TestCase;
8 use SmashBalloon\Reviews\Common\Services\FeedCacheUpdateService;
9 use SmashBalloon\Reviews\Tests\Unit\Doubles\FeedCacheWpdbDouble;
10
11 /**
12 * Regression coverage for the cron feed-cache batch query.
13 *
14 * Pins the dedupe + oldest-first behavior introduced for SMASH-1360
15 * (cron Google Places API cost overhead). The same `feed_caches_query()`
16 * was previously a `SELECT * ... ORDER BY last_updated ASC LIMIT N` which
17 * returned one row per `cache_type` per feed — so a feed with both `posts`
18 * and `header` rows ≥12h-stale contributed two rows to the batch and got
19 * fully re-fetched twice in a single cron tick. The fix collapses the
20 * batch to one row per `feed_id` so the same `LIMIT N` budget now yields
21 * N unique feeds (instead of as few as N/2 when both rows were stale).
22 *
23 * The query is exercised through a `wpdb` test double that captures the
24 * prepared SQL + bound args without requiring a live database.
25 */
26 class FeedCacheUpdateServiceTest extends TestCase
27 {
28 protected function setUp(): void
29 {
30 parent::setUp();
31 global $wpdb;
32 $wpdb = new FeedCacheWpdbDouble();
33 }
34
35 /** GROUP BY feed_id is present and on `feed_id` (not `cache_type`). */
36 public function test_query_groups_by_feed_id(): void
37 {
38 $service = new FeedCacheUpdateService();
39 $service->feed_caches_query(['cron_update' => true]);
40
41 global $wpdb;
42 $sql = $wpdb->last_prepared_sql;
43
44 $this->assertStringContainsString('GROUP BY feed_id', $sql);
45 $this->assertStringNotContainsString('GROUP BY cache_type', $sql);
46 }
47
48 /** Oldest-stale-first prioritization is preserved through the GROUP BY. */
49 public function test_query_orders_by_last_updated_ascending(): void
50 {
51 $service = new FeedCacheUpdateService();
52 $service->feed_caches_query(['cron_update' => true]);
53
54 global $wpdb;
55 $sql = $wpdb->last_prepared_sql;
56
57 $this->assertStringContainsString('ORDER BY last_updated ASC', $sql);
58 }
59
60 /**
61 * MIN(id) / MIN(last_updated) keep the query strict-mode safe under
62 * `ONLY_FULL_GROUP_BY` (every non-aggregate column must appear in
63 * GROUP BY or be wrapped in an aggregate function).
64 */
65 public function test_query_uses_aggregates_for_strict_sql_mode_safety(): void
66 {
67 $service = new FeedCacheUpdateService();
68 $service->feed_caches_query(['cron_update' => true]);
69
70 global $wpdb;
71 $sql = $wpdb->last_prepared_sql;
72
73 $this->assertStringContainsString('MIN(id)', $sql);
74 $this->assertStringContainsString('MIN(last_updated)', $sql);
75 }
76
77 /** SELECT * is gone — the previous shape is what produced the duplicates. */
78 public function test_query_no_longer_uses_select_star(): void
79 {
80 $service = new FeedCacheUpdateService();
81 $service->feed_caches_query(['cron_update' => true]);
82
83 global $wpdb;
84 $sql = $wpdb->last_prepared_sql;
85
86 $this->assertStringNotContainsString('SELECT *', $sql);
87 }
88
89 /** 12h staleness gate (`last_updated < NOW - 12h`) is still bound. */
90 public function test_query_includes_twelve_hour_staleness_filter(): void
91 {
92 $service = new FeedCacheUpdateService();
93 $service->feed_caches_query(['cron_update' => true]);
94
95 global $wpdb;
96
97 $this->assertStringContainsString('last_updated < %s', $wpdb->last_prepared_sql);
98 // First bound arg is the 12h-ago timestamp string (gmdate format).
99 $this->assertMatchesRegularExpression(
100 '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/',
101 (string) $wpdb->last_prepared_args[0]
102 );
103 }
104
105 /** LIMIT is bound to RESULTS_PER_CRON_UPDATE — not a hard-coded literal. */
106 public function test_query_limit_is_bound_to_results_per_cron_update(): void
107 {
108 $service = new FeedCacheUpdateService();
109 $service->feed_caches_query(['cron_update' => true]);
110
111 global $wpdb;
112
113 $this->assertStringContainsString('LIMIT %d', $wpdb->last_prepared_sql);
114 $this->assertSame(
115 FeedCacheUpdateService::RESULTS_PER_CRON_UPDATE,
116 $wpdb->last_prepared_args[1]
117 );
118 }
119
120 /** `cron_update = 'yes'` filter is preserved (already passing pre-fix, regression-pin). */
121 public function test_query_filters_to_cron_update_yes_rows(): void
122 {
123 $service = new FeedCacheUpdateService();
124 $service->feed_caches_query(['cron_update' => true]);
125
126 global $wpdb;
127
128 $this->assertStringContainsString("cron_update = 'yes'", $wpdb->last_prepared_sql);
129 }
130
131 /**
132 * End-to-end behavior: when wpdb returns the post-GROUP-BY shape (one
133 * row per feed_id with MIN-aliased columns), the service hands the
134 * same array back unchanged. FeedCacheUpdater::do_updates only reads
135 * `feed_id` from each row, so the alias projection is invisible to it.
136 */
137 public function test_query_returns_aggregated_rows_unchanged(): void
138 {
139 global $wpdb;
140 $wpdb->next_results = [
141 ['id' => 17, 'feed_id' => 4, 'last_updated' => '2026-05-04 02:00:00'],
142 ['id' => 23, 'feed_id' => 7, 'last_updated' => '2026-05-04 03:30:00'],
143 ];
144
145 $service = new FeedCacheUpdateService();
146 $rows = $service->feed_caches_query(['cron_update' => true]);
147
148 $this->assertCount(2, $rows);
149 $this->assertSame(4, $rows[0]['feed_id']);
150 $this->assertSame(7, $rows[1]['feed_id']);
151 }
152
153 /** When `cron_update` arg is omitted the bare `SELECT *` is still issued (legacy shape). */
154 public function test_query_without_cron_update_arg_keeps_select_star_legacy_shape(): void
155 {
156 $service = new FeedCacheUpdateService();
157 $service->feed_caches_query([]);
158
159 global $wpdb;
160
161 // Legacy shape (no `cron_update` arg) does NOT go through prepare —
162 // the test double records the raw SQL on `last_get_results_sql`.
163 $this->assertStringContainsString('SELECT * FROM', $wpdb->last_get_results_sql);
164 }
165 }
166