PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.22
ووسلام – همگام سازی ووکامرس و باسلام v1.10.22
1.10.22 1.10.21 1.10.19 1.10.20 1.10.18 1.10.17 1.10.15 1.10.14 1.10.13 1.10.12 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.2 1.9.1 1.9.0 1.8.8 All 55 releases
sync-basalam / tests / JobsRunnerTest.php

JobsRunnerTest.php in ووسلام – همگام سازی ووکامرس و باسلام 1.10.22, at tests/JobsRunnerTest.php

359 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SyncBasalam\Tests;
4
5 use PHPUnit\Framework\TestCase;
6 use SyncBasalam\JobsRunner;
7
8 class JobsRunnerTest extends TestCase
9 {
10 private const ASYNC_ACTION = 'sync_basalam_run_jobs_async';
11 private const DISPATCH_LOCK = 'sync_basalam_jobs_runner_async_dispatch_lock';
12 private const IDLE_PROBE_LOCK = 'sync_basalam_jobs_runner_idle_probe_lock';
13
14 private $originalRequest;
15 private $originalCookie;
16 private $originalWpdb;
17 private $hadOriginalWpdb;
18
19 protected function setUp(): void
20 {
21 $this->originalRequest = $_REQUEST;
22 $this->originalCookie = $_COOKIE;
23 $this->hadOriginalWpdb = array_key_exists('wpdb', $GLOBALS);
24 $this->originalWpdb = $GLOBALS['wpdb'] ?? null;
25
26 $_REQUEST = [];
27 $_COOKIE = [];
28
29 $GLOBALS['sync_basalam_jobs_runner_test_state'] = [
30 'actions' => [],
31 'did_actions' => [],
32 'current_filter' => '',
33 'doing_ajax' => false,
34 'transients' => [],
35 'transient_reads' => [],
36 'transient_writes' => [],
37 'filter_values' => [],
38 'events' => [],
39 'remote_requests' => [],
40 ];
41 }
42
43 protected function tearDown(): void
44 {
45 $_REQUEST = $this->originalRequest;
46 $_COOKIE = $this->originalCookie;
47
48 if ($this->hadOriginalWpdb) {
49 $GLOBALS['wpdb'] = $this->originalWpdb;
50 } else {
51 unset($GLOBALS['wpdb']);
52 }
53
54 unset($GLOBALS['sync_basalam_jobs_runner_test_state']);
55 }
56
57 public function testRegistersDispatcherOnShutdownAndNeverOnInit(): void
58 {
59 $runner = $this->newRunner();
60 $actions = $this->state()['actions'];
61
62 self::assertArrayHasKey('shutdown', $actions);
63 self::assertSame([$runner, 'maybeDispatchAsyncRequest'], $actions['shutdown'][0]['callback']);
64 self::assertSame(2, $actions['shutdown'][0]['priority']);
65 self::assertSame(1, $actions['shutdown'][0]['accepted_args']);
66 self::assertArrayNotHasKey('init', $actions);
67
68 self::assertSame(
69 [$runner, 'maybeDispatchAsyncRequest'],
70 $actions['sync_basalam_job_created'][0]['callback']
71 );
72 self::assertSame(0, $actions['sync_basalam_job_created'][0]['accepted_args']);
73 self::assertSame([$runner, 'handleAsyncRequest'], $actions['wp_ajax_' . self::ASYNC_ACTION][0]['callback']);
74 self::assertSame(
75 [$runner, 'handleAsyncRequest'],
76 $actions['wp_ajax_nopriv_' . self::ASYNC_ACTION][0]['callback']
77 );
78 }
79
80 public function testDispatchLeaseIsWrittenOnlyWhenQueueHasWork(): void
81 {
82 $jobManager = new FakeJobManager([true]);
83 $runner = $this->newRunner($jobManager);
84
85 $_COOKIE = ['wordpress_test_cookie' => 'cookie-value'];
86 $runner->maybeDispatchAsyncRequest();
87
88 self::assertSame(
89 ['get_transient', 'get_transient', 'has_pending_jobs', 'get_transient', 'set_transient', 'remote_post'],
90 $this->state()['events']
91 );
92 self::assertSame([120], $jobManager->timeouts);
93 self::assertSame(
94 [[
95 'name' => self::DISPATCH_LOCK,
96 'value' => 1,
97 'expiration' => 25,
98 ]],
99 $this->state()['transient_writes']
100 );
101
102 $requests = $this->state()['remote_requests'];
103 self::assertCount(1, $requests);
104 self::assertSame(
105 'https://example.test/wp-admin/admin-ajax.php?action=' . self::ASYNC_ACTION,
106 $requests[0]['url']
107 );
108 self::assertSame(0.01, $requests[0]['args']['timeout']);
109 self::assertFalse($requests[0]['args']['blocking']);
110 self::assertSame(self::ASYNC_ACTION, $requests[0]['args']['body']['action']);
111 self::assertSame('test-nonce-for-' . self::ASYNC_ACTION, $requests[0]['args']['body']['nonce']);
112 self::assertSame($_COOKIE, $requests[0]['args']['cookies']);
113 }
114
115 public function testShutdownRepairsDatabaseBeforeUsingQueueApis(): void
116 {
117 $GLOBALS['sync_basalam_jobs_runner_test_state']['current_filter'] = 'shutdown';
118 $GLOBALS['wpdb'] = new FakeWpdb();
119
120 $jobManager = new FakeJobManager([false]);
121 $runner = $this->newRunner($jobManager);
122
123 $runner->maybeDispatchAsyncRequest();
124
125 self::assertSame(
126 [
127 'fastcgi_finish_request',
128 'db_flush',
129 'db_check_connection',
130 'get_transient',
131 'get_transient',
132 'has_pending_jobs',
133 'set_transient',
134 ],
135 $this->state()['events']
136 );
137 self::assertSame([false], $GLOBALS['wpdb']->allowBailValues);
138 }
139
140 public function testEmptyQueueReservesOnlyShortIdleProbeLease(): void
141 {
142 $jobManager = new FakeJobManager([false]);
143 $runner = $this->newRunner($jobManager);
144
145 $runner->maybeDispatchAsyncRequest();
146
147 self::assertSame(['get_transient', 'get_transient', 'has_pending_jobs', 'set_transient'], $this->state()['events']);
148 self::assertSame([120], $jobManager->timeouts);
149 self::assertSame(
150 [[
151 'name' => self::IDLE_PROBE_LOCK,
152 'value' => 1,
153 'expiration' => 5,
154 ]],
155 $this->state()['transient_writes']
156 );
157 self::assertSame([], $this->state()['remote_requests']);
158 }
159
160 public function testNewJobBypassesIdleProbeLeaseAndDispatchesImmediately(): void
161 {
162 $jobManager = new FakeJobManager([false, true]);
163 $runner = $this->newRunner($jobManager);
164
165 $runner->maybeDispatchAsyncRequest();
166 self::assertCount(1, $this->state()['transient_writes']);
167
168 $GLOBALS['sync_basalam_jobs_runner_test_state']['current_filter'] = 'sync_basalam_job_created';
169 $runner->maybeDispatchAsyncRequest();
170
171 self::assertSame([120, 120], $jobManager->timeouts);
172 self::assertSame(self::DISPATCH_LOCK, $this->state()['transient_writes'][1]['name']);
173 self::assertCount(1, $this->state()['remote_requests']);
174 }
175
176 public function testExistingLockSkipsQueueCheckAndDispatch(): void
177 {
178 $GLOBALS['sync_basalam_jobs_runner_test_state']['transients'][self::DISPATCH_LOCK] = 1;
179 $jobManager = new FakeJobManager([true]);
180 $runner = $this->newRunner($jobManager);
181
182 $runner->maybeDispatchAsyncRequest();
183
184 self::assertSame(['get_transient', 'get_transient'], $this->state()['events']);
185 self::assertSame([], $jobManager->timeouts);
186 self::assertSame([], $this->state()['transient_writes']);
187 self::assertSame([], $this->state()['remote_requests']);
188 }
189
190 public function testSelfAsyncRequestReturnsBeforeQueueAndLockChecks(): void
191 {
192 $GLOBALS['sync_basalam_jobs_runner_test_state']['doing_ajax'] = true;
193 $_REQUEST['action'] = self::ASYNC_ACTION;
194
195 $jobManager = new FakeJobManager([true]);
196 $httpBlockService = new FakeHttpBlockService(false);
197 $runner = $this->newRunner($jobManager, $httpBlockService);
198
199 $runner->maybeDispatchAsyncRequest();
200
201 self::assertSame(0, $httpBlockService->calls);
202 $this->assertNoQueueLockOrDispatchActivity($jobManager);
203 }
204
205 public function testHttpBlockReturnsBeforeQueueAndLockChecks(): void
206 {
207 $jobManager = new FakeJobManager([true]);
208 $httpBlockService = new FakeHttpBlockService(true);
209 $runner = $this->newRunner($jobManager, $httpBlockService);
210
211 $runner->maybeDispatchAsyncRequest();
212
213 self::assertSame(1, $httpBlockService->calls);
214 $this->assertNoQueueLockOrDispatchActivity($jobManager);
215 }
216
217 public function testAsyncBatchExitsImmediatelyWhenAnotherRunnerOwnsTheGlobalLock(): void
218 {
219 $jobManager = new FakeJobManager([true]);
220 $jobExecutor = new FakeJobExecutor(false);
221 $runner = $this->newRunner($jobManager, null, $jobExecutor);
222
223 self::assertSame(0, $this->invokeRunAsyncBatch($runner));
224 self::assertSame(1, $jobExecutor->acquireCalls);
225 self::assertSame(0, $jobExecutor->releaseCalls);
226 self::assertSame([], $jobManager->timeouts);
227 }
228
229 public function testAsyncBatchReleasesTheGlobalLockWhenTheQueueIsEmpty(): void
230 {
231 $jobManager = new FakeJobManager([false]);
232 $jobExecutor = new FakeJobExecutor(true);
233 $runner = $this->newRunner($jobManager, null, $jobExecutor);
234
235 self::assertSame(0, $this->invokeRunAsyncBatch($runner));
236 self::assertSame(1, $jobExecutor->acquireCalls);
237 self::assertSame(1, $jobExecutor->releaseCalls);
238 self::assertSame([120], $jobManager->timeouts);
239 }
240
241 private function newRunner(
242 ?FakeJobManager $jobManager = null,
243 ?FakeHttpBlockService $httpBlockService = null,
244 $jobExecutor = null
245 ): JobsRunner {
246 return new JobsRunner(
247 $jobManager ?? new FakeJobManager(),
248 $jobExecutor ?? new FakeJobExecutor(),
249 new \stdClass(),
250 $httpBlockService ?? new FakeHttpBlockService(false)
251 );
252 }
253
254 private function invokeRunAsyncBatch(JobsRunner $runner): int
255 {
256 $method = new \ReflectionMethod($runner, 'runAsyncBatch');
257 $method->setAccessible(true);
258
259 return $method->invoke($runner);
260 }
261
262 private function assertNoQueueLockOrDispatchActivity(FakeJobManager $jobManager): void
263 {
264 self::assertSame([], $jobManager->timeouts);
265 self::assertSame([], $this->state()['transient_reads']);
266 self::assertSame([], $this->state()['transient_writes']);
267 self::assertSame([], $this->state()['remote_requests']);
268 }
269
270 private function state(): array
271 {
272 return $GLOBALS['sync_basalam_jobs_runner_test_state'];
273 }
274 }
275
276 class FakeJobManager
277 {
278 public $timeouts = [];
279
280 private $pendingResults;
281
282 public function __construct(array $pendingResults = [])
283 {
284 $this->pendingResults = $pendingResults;
285 }
286
287 public function hasPendingOrStaleProcessingJobs(int $timeout): bool
288 {
289 $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'has_pending_jobs';
290 $this->timeouts[] = $timeout;
291
292 return (bool) array_shift($this->pendingResults);
293 }
294 }
295
296 class FakeWpdb
297 {
298 public $allowBailValues = [];
299
300 public function flush(): void
301 {
302 $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'db_flush';
303 }
304
305 public function check_connection($allowBail = true): bool
306 {
307 $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'db_check_connection';
308 $this->allowBailValues[] = $allowBail;
309
310 return true;
311 }
312 }
313
314 class FakeHttpBlockService
315 {
316 public $calls = 0;
317
318 private $blocked;
319
320 public function __construct(bool $blocked)
321 {
322 $this->blocked = $blocked;
323 }
324
325 public function SyncBasalamHttpBlock()
326 {
327 $this->calls++;
328
329 return $this->blocked;
330 }
331 }
332
333 class FakeJobExecutor
334 {
335 public $acquireCalls = 0;
336 public $releaseCalls = 0;
337
338 private $canAcquire;
339
340 public function __construct(bool $canAcquire = true)
341 {
342 $this->canAcquire = $canAcquire;
343 }
344
345 public function acquireGlobalJobsLock(int $timeout = 0): bool
346 {
347 $this->acquireCalls++;
348
349 return $this->canAcquire;
350 }
351
352 public function releaseGlobalJobsLock(): bool
353 {
354 $this->releaseCalls++;
355
356 return true;
357 }
358 }
359