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

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

312 lines 10.0 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 testRegistersDispatcherOnInitAndNeverOnShutdown(): void
46 {
47 $runner = $this->newRunner();
48 $actions = $this->state()['actions'];
49
50 self::assertArrayHasKey('init', $actions);
51 self::assertSame([$runner, 'maybeDispatchAsyncRequest'], $actions['init'][0]['callback']);
52 self::assertSame(10, $actions['init'][0]['priority']);
53 self::assertSame(0, $actions['init'][0]['accepted_args']);
54 self::assertArrayNotHasKey('shutdown', $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 testEligibleQueueIsConfirmedBeforeWritingLockAndDispatching(): 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', 'has_pending_jobs', 'set_transient', '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 testEmptyQueueDoesNotWriteLockOrDispatch(): void
104 {
105 $jobManager = new FakeJobManager([false]);
106 $runner = $this->newRunner($jobManager);
107
108 $runner->maybeDispatchAsyncRequest();
109
110 self::assertSame(['get_transient', 'has_pending_jobs'], $this->state()['events']);
111 self::assertSame([120], $jobManager->timeouts);
112 self::assertSame([], $this->state()['transient_writes']);
113 self::assertSame([], $this->state()['remote_requests']);
114 }
115
116 public function testJobCreatedAfterAnEmptyInitCanStillDispatch(): void
117 {
118 $jobManager = new FakeJobManager([false, true]);
119 $runner = $this->newRunner($jobManager);
120
121 // The init probe sees no work and must not reserve the dispatch lock.
122 $runner->maybeDispatchAsyncRequest();
123 self::assertSame([], $this->state()['transient_writes']);
124
125 // A job created later in the same request must still wake the runner.
126 $runner->maybeDispatchAsyncRequest();
127
128 self::assertSame([120, 120], $jobManager->timeouts);
129 self::assertCount(1, $this->state()['transient_writes']);
130 self::assertCount(1, $this->state()['remote_requests']);
131 }
132
133 public function testExistingLockSkipsQueueCheckAndDispatch(): void
134 {
135 $GLOBALS['sync_basalam_jobs_runner_test_state']['transients'][self::DISPATCH_LOCK] = 1;
136 $jobManager = new FakeJobManager([true]);
137 $runner = $this->newRunner($jobManager);
138
139 $runner->maybeDispatchAsyncRequest();
140
141 self::assertSame(['get_transient'], $this->state()['events']);
142 self::assertSame([], $jobManager->timeouts);
143 self::assertSame([], $this->state()['transient_writes']);
144 self::assertSame([], $this->state()['remote_requests']);
145 }
146
147 public function testSelfAsyncRequestReturnsBeforeQueueAndLockChecks(): void
148 {
149 $GLOBALS['sync_basalam_jobs_runner_test_state']['doing_ajax'] = true;
150 $_REQUEST['action'] = self::ASYNC_ACTION;
151
152 $jobManager = new FakeJobManager([true]);
153 $httpBlockService = new FakeHttpBlockService(false);
154 $runner = $this->newRunner($jobManager, $httpBlockService);
155
156 $runner->maybeDispatchAsyncRequest();
157
158 self::assertSame(0, $httpBlockService->calls);
159 $this->assertNoQueueLockOrDispatchActivity($jobManager);
160 }
161
162 public function testHttpBlockReturnsBeforeQueueAndLockChecks(): void
163 {
164 $jobManager = new FakeJobManager([true]);
165 $httpBlockService = new FakeHttpBlockService(true);
166 $runner = $this->newRunner($jobManager, $httpBlockService);
167
168 $runner->maybeDispatchAsyncRequest();
169
170 self::assertSame(1, $httpBlockService->calls);
171 $this->assertNoQueueLockOrDispatchActivity($jobManager);
172 }
173
174 public function testShutdownGuardPreventsEveryDispatchSideEffect(): void
175 {
176 $GLOBALS['sync_basalam_jobs_runner_test_state']['did_actions']['shutdown'] = 1;
177
178 $jobManager = new FakeJobManager([true]);
179 $httpBlockService = new FakeHttpBlockService(false);
180 $runner = $this->newRunner($jobManager, $httpBlockService);
181
182 $runner->maybeDispatchAsyncRequest();
183
184 self::assertSame(0, $httpBlockService->calls);
185 $this->assertNoQueueLockOrDispatchActivity($jobManager);
186 }
187
188 public function testAsyncBatchExitsImmediatelyWhenAnotherRunnerOwnsTheGlobalLock(): void
189 {
190 $jobManager = new FakeJobManager([true]);
191 $jobExecutor = new FakeJobExecutor(false);
192 $runner = $this->newRunner($jobManager, null, $jobExecutor);
193
194 self::assertSame(0, $this->invokeRunAsyncBatch($runner));
195 self::assertSame(1, $jobExecutor->acquireCalls);
196 self::assertSame(0, $jobExecutor->releaseCalls);
197 self::assertSame([], $jobManager->timeouts);
198 }
199
200 public function testAsyncBatchReleasesTheGlobalLockWhenTheQueueIsEmpty(): void
201 {
202 $jobManager = new FakeJobManager([false]);
203 $jobExecutor = new FakeJobExecutor(true);
204 $runner = $this->newRunner($jobManager, null, $jobExecutor);
205
206 self::assertSame(0, $this->invokeRunAsyncBatch($runner));
207 self::assertSame(1, $jobExecutor->acquireCalls);
208 self::assertSame(1, $jobExecutor->releaseCalls);
209 self::assertSame([120], $jobManager->timeouts);
210 }
211
212 private function newRunner(
213 ?FakeJobManager $jobManager = null,
214 ?FakeHttpBlockService $httpBlockService = null,
215 $jobExecutor = null
216 ): JobsRunner {
217 return new JobsRunner(
218 $jobManager ?? new FakeJobManager(),
219 $jobExecutor ?? new FakeJobExecutor(),
220 new \stdClass(),
221 $httpBlockService ?? new FakeHttpBlockService(false)
222 );
223 }
224
225 private function invokeRunAsyncBatch(JobsRunner $runner): int
226 {
227 $method = new \ReflectionMethod($runner, 'runAsyncBatch');
228 $method->setAccessible(true);
229
230 return $method->invoke($runner);
231 }
232
233 private function assertNoQueueLockOrDispatchActivity(FakeJobManager $jobManager): void
234 {
235 self::assertSame([], $jobManager->timeouts);
236 self::assertSame([], $this->state()['transient_reads']);
237 self::assertSame([], $this->state()['transient_writes']);
238 self::assertSame([], $this->state()['remote_requests']);
239 }
240
241 private function state(): array
242 {
243 return $GLOBALS['sync_basalam_jobs_runner_test_state'];
244 }
245 }
246
247 class FakeJobManager
248 {
249 public $timeouts = [];
250
251 private $pendingResults;
252
253 public function __construct(array $pendingResults = [])
254 {
255 $this->pendingResults = $pendingResults;
256 }
257
258 public function hasPendingOrStaleProcessingJobs(int $timeout): bool
259 {
260 $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'has_pending_jobs';
261 $this->timeouts[] = $timeout;
262
263 return (bool) array_shift($this->pendingResults);
264 }
265 }
266
267 class FakeHttpBlockService
268 {
269 public $calls = 0;
270
271 private $blocked;
272
273 public function __construct(bool $blocked)
274 {
275 $this->blocked = $blocked;
276 }
277
278 public function SyncBasalamHttpBlock()
279 {
280 $this->calls++;
281
282 return $this->blocked;
283 }
284 }
285
286 class FakeJobExecutor
287 {
288 public $acquireCalls = 0;
289 public $releaseCalls = 0;
290
291 private $canAcquire;
292
293 public function __construct(bool $canAcquire = true)
294 {
295 $this->canAcquire = $canAcquire;
296 }
297
298 public function acquireGlobalJobsLock(int $timeout = 0): bool
299 {
300 $this->acquireCalls++;
301
302 return $this->canAcquire;
303 }
304
305 public function releaseGlobalJobsLock(): bool
306 {
307 $this->releaseCalls++;
308
309 return true;
310 }
311 }
312