PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.18
ووسلام – همگام سازی ووکامرس و باسلام v1.10.18
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 1.8.5 1.8.6 1.8.7 1.8.4 All 51 releases
sync-basalam / tests / JobsRunnerTest.php

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

305 lines 9.7 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
13 private $originalRequest;
14 private $originalCookie;
15
16 protected function setUp(): void
17 {
18 $this->originalRequest = $_REQUEST;
19 $this->originalCookie = $_COOKIE;
20
21 $_REQUEST = [];
22 $_COOKIE = [];
23
24 $GLOBALS['sync_basalam_jobs_runner_test_state'] = [
25 'actions' => [],
26 'did_actions' => [],
27 'doing_ajax' => false,
28 'transients' => [],
29 'transient_reads' => [],
30 'transient_writes' => [],
31 'filter_values' => [],
32 'events' => [],
33 'remote_requests' => [],
34 ];
35 }
36
37 protected function tearDown(): void
38 {
39 $_REQUEST = $this->originalRequest;
40 $_COOKIE = $this->originalCookie;
41
42 unset($GLOBALS['sync_basalam_jobs_runner_test_state']);
43 }
44
45 public function testRegistersDispatcherOnShutdownAndNeverOnInit(): void
46 {
47 $runner = $this->newRunner();
48 $actions = $this->state()['actions'];
49
50 self::assertArrayHasKey('shutdown', $actions);
51 self::assertSame([$runner, 'maybeDispatchAsyncRequest'], $actions['shutdown'][0]['callback']);
52 self::assertSame(PHP_INT_MAX, $actions['shutdown'][0]['priority']);
53 self::assertSame(1, $actions['shutdown'][0]['accepted_args']);
54 self::assertArrayNotHasKey('init', $actions);
55
56 self::assertSame(
57 [$runner, 'maybeDispatchAsyncRequest'],
58 $actions['sync_basalam_job_created'][0]['callback']
59 );
60 self::assertSame(0, $actions['sync_basalam_job_created'][0]['accepted_args']);
61 self::assertSame([$runner, 'handleAsyncRequest'], $actions['wp_ajax_' . self::ASYNC_ACTION][0]['callback']);
62 self::assertSame(
63 [$runner, 'handleAsyncRequest'],
64 $actions['wp_ajax_nopriv_' . self::ASYNC_ACTION][0]['callback']
65 );
66 }
67
68 public function testDispatchLeaseIsWrittenBeforeQueueProbeAndDispatching(): void
69 {
70 $jobManager = new FakeJobManager([true]);
71 $runner = $this->newRunner($jobManager);
72
73 $_COOKIE = ['wordpress_test_cookie' => 'cookie-value'];
74 $runner->maybeDispatchAsyncRequest();
75
76 self::assertSame(
77 ['get_transient', 'set_transient', 'has_pending_jobs', 'remote_post'],
78 $this->state()['events']
79 );
80 self::assertSame([120], $jobManager->timeouts);
81 self::assertSame(
82 [[
83 'name' => self::DISPATCH_LOCK,
84 'value' => 1,
85 'expiration' => 25,
86 ]],
87 $this->state()['transient_writes']
88 );
89
90 $requests = $this->state()['remote_requests'];
91 self::assertCount(1, $requests);
92 self::assertSame(
93 'https://example.test/wp-admin/admin-ajax.php?action=' . self::ASYNC_ACTION,
94 $requests[0]['url']
95 );
96 self::assertSame(0.01, $requests[0]['args']['timeout']);
97 self::assertFalse($requests[0]['args']['blocking']);
98 self::assertSame(self::ASYNC_ACTION, $requests[0]['args']['body']['action']);
99 self::assertSame('test-nonce-for-' . self::ASYNC_ACTION, $requests[0]['args']['body']['nonce']);
100 self::assertSame($_COOKIE, $requests[0]['args']['cookies']);
101 }
102
103 public function testEmptyQueueStillReservesDispatchLease(): void
104 {
105 $jobManager = new FakeJobManager([false]);
106 $runner = $this->newRunner($jobManager);
107
108 $runner->maybeDispatchAsyncRequest();
109
110 self::assertSame(['get_transient', 'set_transient', 'has_pending_jobs'], $this->state()['events']);
111 self::assertSame([120], $jobManager->timeouts);
112 self::assertSame(
113 [[
114 'name' => self::DISPATCH_LOCK,
115 'value' => 1,
116 'expiration' => 25,
117 ]],
118 $this->state()['transient_writes']
119 );
120 self::assertSame([], $this->state()['remote_requests']);
121 }
122
123 public function testEmptyQueueLeasePreventsASecondProbeInTheSameRequest(): void
124 {
125 $jobManager = new FakeJobManager([false, true]);
126 $runner = $this->newRunner($jobManager);
127
128 // The shutdown probe reserves the lease even when no work is found.
129 $runner->maybeDispatchAsyncRequest();
130 self::assertCount(1, $this->state()['transient_writes']);
131
132 // A second callback in the same request must observe that lease.
133 $runner->maybeDispatchAsyncRequest();
134
135 self::assertSame([120], $jobManager->timeouts);
136 self::assertCount(1, $this->state()['transient_writes']);
137 self::assertCount(0, $this->state()['remote_requests']);
138 }
139
140 public function testExistingLockSkipsQueueCheckAndDispatch(): void
141 {
142 $GLOBALS['sync_basalam_jobs_runner_test_state']['transients'][self::DISPATCH_LOCK] = 1;
143 $jobManager = new FakeJobManager([true]);
144 $runner = $this->newRunner($jobManager);
145
146 $runner->maybeDispatchAsyncRequest();
147
148 self::assertSame(['get_transient'], $this->state()['events']);
149 self::assertSame([], $jobManager->timeouts);
150 self::assertSame([], $this->state()['transient_writes']);
151 self::assertSame([], $this->state()['remote_requests']);
152 }
153
154 public function testSelfAsyncRequestReturnsBeforeQueueAndLockChecks(): void
155 {
156 $GLOBALS['sync_basalam_jobs_runner_test_state']['doing_ajax'] = true;
157 $_REQUEST['action'] = self::ASYNC_ACTION;
158
159 $jobManager = new FakeJobManager([true]);
160 $httpBlockService = new FakeHttpBlockService(false);
161 $runner = $this->newRunner($jobManager, $httpBlockService);
162
163 $runner->maybeDispatchAsyncRequest();
164
165 self::assertSame(0, $httpBlockService->calls);
166 $this->assertNoQueueLockOrDispatchActivity($jobManager);
167 }
168
169 public function testHttpBlockReturnsBeforeQueueAndLockChecks(): void
170 {
171 $jobManager = new FakeJobManager([true]);
172 $httpBlockService = new FakeHttpBlockService(true);
173 $runner = $this->newRunner($jobManager, $httpBlockService);
174
175 $runner->maybeDispatchAsyncRequest();
176
177 self::assertSame(1, $httpBlockService->calls);
178 $this->assertNoQueueLockOrDispatchActivity($jobManager);
179 }
180
181 public function testAsyncBatchExitsImmediatelyWhenAnotherRunnerOwnsTheGlobalLock(): void
182 {
183 $jobManager = new FakeJobManager([true]);
184 $jobExecutor = new FakeJobExecutor(false);
185 $runner = $this->newRunner($jobManager, null, $jobExecutor);
186
187 self::assertSame(0, $this->invokeRunAsyncBatch($runner));
188 self::assertSame(1, $jobExecutor->acquireCalls);
189 self::assertSame(0, $jobExecutor->releaseCalls);
190 self::assertSame([], $jobManager->timeouts);
191 }
192
193 public function testAsyncBatchReleasesTheGlobalLockWhenTheQueueIsEmpty(): void
194 {
195 $jobManager = new FakeJobManager([false]);
196 $jobExecutor = new FakeJobExecutor(true);
197 $runner = $this->newRunner($jobManager, null, $jobExecutor);
198
199 self::assertSame(0, $this->invokeRunAsyncBatch($runner));
200 self::assertSame(1, $jobExecutor->acquireCalls);
201 self::assertSame(1, $jobExecutor->releaseCalls);
202 self::assertSame([120], $jobManager->timeouts);
203 }
204
205 private function newRunner(
206 ?FakeJobManager $jobManager = null,
207 ?FakeHttpBlockService $httpBlockService = null,
208 $jobExecutor = null
209 ): JobsRunner {
210 return new JobsRunner(
211 $jobManager ?? new FakeJobManager(),
212 $jobExecutor ?? new FakeJobExecutor(),
213 new \stdClass(),
214 $httpBlockService ?? new FakeHttpBlockService(false)
215 );
216 }
217
218 private function invokeRunAsyncBatch(JobsRunner $runner): int
219 {
220 $method = new \ReflectionMethod($runner, 'runAsyncBatch');
221 $method->setAccessible(true);
222
223 return $method->invoke($runner);
224 }
225
226 private function assertNoQueueLockOrDispatchActivity(FakeJobManager $jobManager): void
227 {
228 self::assertSame([], $jobManager->timeouts);
229 self::assertSame([], $this->state()['transient_reads']);
230 self::assertSame([], $this->state()['transient_writes']);
231 self::assertSame([], $this->state()['remote_requests']);
232 }
233
234 private function state(): array
235 {
236 return $GLOBALS['sync_basalam_jobs_runner_test_state'];
237 }
238 }
239
240 class FakeJobManager
241 {
242 public $timeouts = [];
243
244 private $pendingResults;
245
246 public function __construct(array $pendingResults = [])
247 {
248 $this->pendingResults = $pendingResults;
249 }
250
251 public function hasPendingOrStaleProcessingJobs(int $timeout): bool
252 {
253 $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'has_pending_jobs';
254 $this->timeouts[] = $timeout;
255
256 return (bool) array_shift($this->pendingResults);
257 }
258 }
259
260 class FakeHttpBlockService
261 {
262 public $calls = 0;
263
264 private $blocked;
265
266 public function __construct(bool $blocked)
267 {
268 $this->blocked = $blocked;
269 }
270
271 public function SyncBasalamHttpBlock()
272 {
273 $this->calls++;
274
275 return $this->blocked;
276 }
277 }
278
279 class FakeJobExecutor
280 {
281 public $acquireCalls = 0;
282 public $releaseCalls = 0;
283
284 private $canAcquire;
285
286 public function __construct(bool $canAcquire = true)
287 {
288 $this->canAcquire = $canAcquire;
289 }
290
291 public function acquireGlobalJobsLock(int $timeout = 0): bool
292 {
293 $this->acquireCalls++;
294
295 return $this->canAcquire;
296 }
297
298 public function releaseGlobalJobsLock(): bool
299 {
300 $this->releaseCalls++;
301
302 return true;
303 }
304 }
305