PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.28
ووسلام – همگام سازی ووکامرس و باسلام v1.10.28
1.10.28 1.1.26 1.10.27 1.10.24 1.10.25 1.10.23 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 All 61 releases
sync-basalam / tests / Registrar / ProductSyncOperationsTest.php

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

398 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace {
4 if (!function_exists('get_option')) {
5 function get_option($name, $default = false)
6 {
7 return $GLOBALS['sync_basalam_test_options'][$name] ?? $default;
8 }
9 }
10 if (!function_exists('update_option')) {
11 function update_option($name, $value, $autoload = null)
12 {
13 $GLOBALS['sync_basalam_test_options'][$name] = $value;
14 return true;
15 }
16 }
17 if (!function_exists('wc_get_product')) {
18 function wc_get_product($id)
19 {
20 return $GLOBALS['sync_basalam_test_products'][$id] ?? null;
21 }
22 }
23 if (!function_exists('absint')) {
24 function absint($value) { return abs((int) $value); }
25 }
26 if (!function_exists('sanitize_text_field')) {
27 function sanitize_text_field($value) { return trim(strip_tags((string) $value)); }
28 }
29 if (!function_exists('get_current_user_id')) {
30 function get_current_user_id() { return $GLOBALS['sync_basalam_oauth_test_user_id'] ?? 0; }
31 }
32 }
33
34 namespace SyncBasalam\Registrar\ProductListeners {
35 function syncBasalamContainer() { return $GLOBALS['product_sync_test_container']; }
36 function wc_get_product($id) { return \wc_get_product($id); }
37 function get_post_type($id) { return $GLOBALS['product_sync_test_post_type'] ?? 'product'; }
38 function get_post_status($id) { return $GLOBALS['product_sync_test_post_status'] ?? 'publish'; }
39 function get_post_meta($id, $key, $single = false)
40 {
41 return $GLOBALS['product_sync_test_meta'][$id][$key] ?? '';
42 }
43 }
44
45 namespace SyncBasalam\Jobs\Types {
46 function syncBasalamContainer() { return $GLOBALS['product_sync_test_container']; }
47 function wc_get_product($id) { return \wc_get_product($id); }
48 }
49
50 namespace SyncBasalam\Utilities {
51 function syncBasalamSettings()
52 {
53 return new class {
54 public function getSettings($key = null)
55 {
56 return \SyncBasalam\Admin\Settings\SettingsManager::getSettings($key);
57 }
58 };
59 }
60 }
61
62 namespace SyncBasalam\Tests\Registrar {
63 use PHPUnit\Framework\TestCase;
64 use SyncBasalam\Admin\Settings\SettingsConfig;
65 use SyncBasalam\Admin\Settings\SettingsManager;
66 use SyncBasalam\Admin\Settings\SettingsPageHandler;
67 use SyncBasalam\Jobs\Types\CreateSingleProductJob;
68 use SyncBasalam\Jobs\Types\UpdateSingleProductJob;
69 use SyncBasalam\Registrar\ProductListeners\ArchiveProduct;
70 use SyncBasalam\Registrar\ProductListeners\CreateWooProduct;
71 use SyncBasalam\Registrar\ProductListeners\RestoreProduct;
72 use SyncBasalam\Registrar\ProductListeners\UpdateWooProduct;
73 use SyncBasalam\Utilities\ProductMetaKey;
74
75 require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
76
77 class ProductSyncOperationsTest extends TestCase
78 {
79 private $jobs;
80 private $policy;
81
82 protected function setUp(): void
83 {
84 $GLOBALS['sync_basalam_test_options'] = [];
85 $GLOBALS['sync_basalam_test_products'] = [42 => new class {
86 public function is_type($type) { return false; }
87 }];
88 $GLOBALS['product_sync_test_meta'] = [];
89 $this->jobs = new class {
90 public array $created = [];
91 public function hasProductJobInProgress($id, $type)
92 {
93 foreach ($this->created as $job) {
94 if ($job['type'] === $type && $job['payload']['product_id'] === $id) return true;
95 }
96 return false;
97 }
98 public function createJob($type, $status, $payload)
99 {
100 $this->created[] = ['type' => $type, 'status' => $status, 'payload' => json_decode($payload, true)];
101 }
102 };
103 $this->policy = new class {
104 public bool $create = true;
105 public bool $update = true;
106 public bool $restricted = false;
107 public function canCreate() { return $this->create; }
108 public function canUpdate() { return $this->update; }
109 public function canUpdateProductStatus() { return $this->update; }
110 public function shouldRestrictUpdateFields($refresh) { return $this->restricted; }
111 };
112 $GLOBALS['product_sync_test_container'] = new class($this->policy) {
113 private $policy;
114 public function __construct($policy) { $this->policy = $policy; }
115 public function get($id) { return $this->policy; }
116 };
117 }
118
119 protected function tearDown(): void
120 {
121 $_POST = [];
122 unset(
123 $GLOBALS['sync_basalam_test_options'], $GLOBALS['sync_basalam_test_products'],
124 $GLOBALS['product_sync_test_container'], $GLOBALS['product_sync_test_meta'],
125 $GLOBALS['product_sync_test_post_type'], $GLOBALS['product_sync_test_post_status']
126 );
127 }
128
129 public static function operations(): array
130 {
131 return [
132 'both disabled' => [false, false],
133 'creation only' => [true, false],
134 'update only' => [false, true],
135 'both enabled' => [true, true],
136 ];
137 }
138
139 /** @dataProvider operations */
140 public function testNewProductUsesCreationSetting(bool $create, bool $update): void
141 {
142 $this->configure($create, $update);
143 (new CreateWooProduct($this->jobs))->handle(42);
144 $this->assertJob($create, 'sync_basalam_create_single_product');
145 }
146
147 /** @dataProvider operations */
148 public function testEditingAnUnconnectedProductUsesCreationSetting(bool $create, bool $update): void
149 {
150 $this->configure($create, $update);
151 (new UpdateWooProduct($this->jobs))->handle(42);
152 $this->assertJob($create, 'sync_basalam_create_single_product');
153 }
154
155 /** @dataProvider operations */
156 public function testEditingAConnectedProductUsesUpdateSetting(bool $create, bool $update): void
157 {
158 $this->configure($create, $update);
159 $this->connectProduct();
160 (new CreateWooProduct($this->jobs))->handle(42);
161 (new UpdateWooProduct($this->jobs))->handle(42);
162 $this->assertJob($update, 'sync_basalam_update_single_product');
163 }
164
165 public function testOverlappingSaveHooksQueueOnlyOneCreation(): void
166 {
167 $this->configure(true, true);
168 $create = new CreateWooProduct($this->jobs);
169 $create->handle(42); // save_post
170 $create->handle(42); // woocommerce_new_product
171 (new UpdateWooProduct($this->jobs))->handle(42);
172 $this->assertJob(true, 'sync_basalam_create_single_product');
173 }
174
175 public function testRepeatedUpdateHooksQueueOnlyOneUpdate(): void
176 {
177 $this->configure(true, true);
178 $this->connectProduct();
179 $listener = new UpdateWooProduct($this->jobs);
180 $listener->handle(42);
181 $listener->handle(42);
182 $this->assertJob(true, 'sync_basalam_update_single_product');
183 }
184
185 public function testDraftAndVariationProductsAreNotCreated(): void
186 {
187 $this->configure(true, true);
188 $GLOBALS['product_sync_test_post_status'] = 'draft';
189 (new CreateWooProduct($this->jobs))->handle(42);
190 (new UpdateWooProduct($this->jobs))->handle(42);
191 $GLOBALS['product_sync_test_post_status'] = 'publish';
192 $GLOBALS['sync_basalam_test_products'][42] = new class {
193 public function is_type($type) { return $type === 'variation'; }
194 };
195 (new CreateWooProduct($this->jobs))->handle(42);
196 (new UpdateWooProduct($this->jobs))->handle(42);
197 self::assertSame([], $this->jobs->created);
198 }
199
200 public function testMissingProductsAndOtherPostTypesAreIgnored(): void
201 {
202 $this->configure(true, true);
203 (new CreateWooProduct($this->jobs))->handle(404);
204 (new UpdateWooProduct($this->jobs))->handle(404);
205 $GLOBALS['product_sync_test_post_type'] = 'post';
206 (new CreateWooProduct($this->jobs))->handle(42);
207 (new UpdateWooProduct($this->jobs))->handle(42);
208 self::assertSame([], $this->jobs->created);
209 }
210
211 public function testInactiveVendorCannotCreateAnUnconnectedProduct(): void
212 {
213 $this->configure(true, true);
214 $this->policy->create = false;
215 (new UpdateWooProduct($this->jobs))->handle(42);
216 self::assertSame([], $this->jobs->created);
217 }
218
219 public function testSuspendedVendorCannotUpdateAConnectedProduct(): void
220 {
221 $this->configure(true, true);
222 $this->connectProduct();
223 $this->policy->update = false;
224 (new UpdateWooProduct($this->jobs))->handle(42);
225 self::assertSame([], $this->jobs->created);
226 }
227
228 public function testCreationDoesNotDependOnTheCustomUpdateFieldSelection(): void
229 {
230 $this->configure(true, false);
231 SettingsManager::updateSettings([SettingsConfig::SYNC_PRODUCT_FIELDS => 'custom', SettingsConfig::SYNC_PRODUCT_FIELD_SKU => 0]);
232 (new UpdateWooProduct($this->jobs))->handle(42);
233 $this->assertJob(true, 'sync_basalam_create_single_product');
234 }
235
236 public function testCustomUpdateWithoutFieldsIsNotQueued(): void
237 {
238 $this->configure(true, true);
239 $this->connectProduct();
240 SettingsManager::updateSettings([SettingsConfig::SYNC_PRODUCT_FIELDS => 'custom', SettingsConfig::SYNC_PRODUCT_FIELD_SKU => 0]);
241 (new UpdateWooProduct($this->jobs))->handle(42);
242 self::assertSame([], $this->jobs->created);
243 $this->policy->restricted = true;
244 (new UpdateWooProduct($this->jobs))->handle(42);
245 $this->assertJob(true, 'sync_basalam_update_single_product');
246 }
247
248 /** @dataProvider operations */
249 public function testArchiveAndRestoreUseUpdateSetting(bool $create, bool $update): void
250 {
251 $this->configure($create, $update);
252 $operations = new class {
253 public array $calls = [];
254 public function archiveExistProduct($id) { $this->calls[] = ['archive', $id]; }
255 public function restoreExistProduct($id) { $this->calls[] = ['restore', $id]; }
256 };
257 (new ArchiveProduct($operations))->handle(42);
258 (new RestoreProduct($operations))->handle(42);
259 self::assertSame($update ? [['archive', 42], ['restore', 42]] : [], $operations->calls);
260 }
261
262 /** @dataProvider operations */
263 public function testQueuedAutomaticJobsRecheckTheirOwnSetting(bool $create, bool $update): void
264 {
265 $this->configure($create, $update);
266 $operations = new class {
267 public array $calls = [];
268 public function createNewProduct($id, $category) { $this->calls[] = 'create'; return ['success' => true]; }
269 public function updateExistProduct($id, $category) { $this->calls[] = 'update'; return ['success' => true]; }
270 };
271 $created = (new CreateSingleProductJob($this->jobs, $operations))->execute(['product_id' => 42, 'automatic' => true]);
272 $updated = (new UpdateSingleProductJob($this->jobs, $operations))->execute(['product_id' => 42, 'automatic' => true]);
273 self::assertSame(!$create, $created->getData()['skipped'] ?? false);
274 self::assertSame(!$update, $updated->getData()['skipped'] ?? false);
275 self::assertSame(array_merge($create ? ['create'] : [], $update ? ['update'] : []), $operations->calls);
276 }
277
278 public function testManualJobsCanRunWithAutomaticSyncDisabled(): void
279 {
280 $this->configure(false, false);
281 $operations = new class {
282 public function createNewProduct($id, $category) { return ['success' => true]; }
283 public function updateExistProduct($id, $category) { return ['success' => true]; }
284 };
285 foreach ([CreateSingleProductJob::class, UpdateSingleProductJob::class] as $job) {
286 $result = (new $job($this->jobs, $operations))->execute(['product_id' => 42]);
287 self::assertTrue($result->getData()['result']['success']);
288 }
289 }
290
291 /** @dataProvider legacyValues */
292 public function testLegacyStatusMigratesBothOperations($legacy, bool $enabled): void
293 {
294 $GLOBALS['sync_basalam_test_options']['sync_basalam_settings'] = [SettingsConfig::SYNC_STATUS_PRODUCT => $legacy];
295 self::assertSame($enabled, SettingsManager::isProductCreationSyncEnabled());
296 self::assertSame($enabled, SettingsManager::isProductUpdateSyncEnabled());
297 self::assertSame($enabled, $GLOBALS['sync_basalam_test_options']['sync_basalam_settings'][SettingsConfig::SYNC_STATUS_PRODUCT_CREATE]);
298 }
299
300 public static function legacyValues(): array
301 {
302 return [[true, true], [1, true], ['1', true], ['true', true], [false, false], [0, false], ['0', false], ['', false], ['false', false]];
303 }
304
305 public function testUnrelatedSettingsSavePreservesSeparateChoices(): void
306 {
307 $this->configure(true, false);
308 SettingsManager::updateSettings([SettingsConfig::DEFAULT_WEIGHT => 200]);
309 self::assertTrue(SettingsManager::isProductCreationSyncEnabled());
310 self::assertFalse(SettingsManager::isProductUpdateSyncEnabled());
311 SettingsManager::updateSettings([SettingsConfig::SYNC_STATUS_PRODUCT_CREATE => '0', SettingsConfig::SYNC_STATUS_PRODUCT_UPDATE => '1']);
312 self::assertFalse(SettingsManager::isProductCreationSyncEnabled());
313 self::assertTrue(SettingsManager::isProductUpdateSyncEnabled());
314 self::assertTrue(SettingsManager::getSettings(SettingsConfig::SYNC_STATUS_PRODUCT));
315 }
316
317 public function testFreshInstallPersistsItsDefaultSettings(): void
318 {
319 self::assertFalse(SettingsManager::isProductCreationSyncEnabled());
320 self::assertFalse(SettingsManager::isProductUpdateSyncEnabled());
321 $settings = SettingsManager::getSettings();
322 self::assertSame($settings, $GLOBALS['sync_basalam_test_options']['sync_basalam_settings']);
323 self::assertSame($settings[SettingsConfig::WEBHOOK_HEADER_TOKEN], SettingsManager::getSettings(SettingsConfig::WEBHOOK_HEADER_TOKEN));
324 }
325
326 public function testLegacyToggleStillControlsBothOperations(): void
327 {
328 $this->configure(true, false);
329 SettingsManager::updateSettings([SettingsConfig::SYNC_STATUS_PRODUCT => '']);
330 self::assertFalse(SettingsManager::isProductCreationSyncEnabled());
331 self::assertFalse(SettingsManager::isProductUpdateSyncEnabled());
332 SettingsManager::updateSettings([SettingsConfig::SYNC_STATUS_PRODUCT => '1']);
333 self::assertTrue(SettingsManager::isProductCreationSyncEnabled());
334 self::assertTrue(SettingsManager::isProductUpdateSyncEnabled());
335 }
336
337 public function testMainProductSyncButtonTogglesBothOperations(): void
338 {
339 $this->configure(true, false);
340
341 $_POST = [
342 'sync_basalam_toggle_product' => '1',
343 'sync_basalam_settings' => [
344 SettingsConfig::SYNC_STATUS_PRODUCT_CREATE => '1',
345 SettingsConfig::SYNC_STATUS_PRODUCT_UPDATE => '0',
346 ],
347 ];
348 self::assertTrue(SettingsPageHandler::saveSettings());
349 self::assertFalse(SettingsManager::isProductCreationSyncEnabled());
350 self::assertFalse(SettingsManager::isProductUpdateSyncEnabled());
351
352 $_POST = [
353 'sync_basalam_toggle_product' => '1',
354 'sync_basalam_settings' => [
355 SettingsConfig::SYNC_STATUS_PRODUCT_CREATE => '0',
356 SettingsConfig::SYNC_STATUS_PRODUCT_UPDATE => '0',
357 ],
358 ];
359 self::assertTrue(SettingsPageHandler::saveSettings());
360 self::assertTrue(SettingsManager::isProductCreationSyncEnabled());
361 self::assertTrue(SettingsManager::isProductUpdateSyncEnabled());
362 }
363
364 public function testCreationOnlyAndStoppingSyncCanBeSavedWithEmptyCustomUpdateFields(): void
365 {
366 $this->configure(true, true);
367 SettingsManager::updateSettings([SettingsConfig::SYNC_PRODUCT_FIELDS => 'custom', SettingsConfig::SYNC_PRODUCT_FIELD_SKU => 0]);
368 foreach (['1', '0'] as $create) {
369 $_POST = ['sync_basalam_settings' => [SettingsConfig::SYNC_STATUS_PRODUCT_CREATE => $create, SettingsConfig::SYNC_STATUS_PRODUCT_UPDATE => '0']];
370 self::assertTrue(SettingsPageHandler::saveSettings());
371 self::assertSame($create === '1', SettingsManager::isProductCreationSyncEnabled());
372 self::assertFalse(SettingsManager::isProductUpdateSyncEnabled());
373 }
374 $_POST = ['sync_basalam_settings' => [SettingsConfig::SYNC_STATUS_PRODUCT_UPDATE => '1']];
375 self::assertFalse(SettingsPageHandler::saveSettings());
376 self::assertFalse(SettingsManager::isProductUpdateSyncEnabled());
377 }
378
379 private function configure(bool $create, bool $update): void
380 {
381 SettingsManager::updateSettings([
382 SettingsConfig::SYNC_STATUS_PRODUCT_CREATE => $create ? '1' : '0',
383 SettingsConfig::SYNC_STATUS_PRODUCT_UPDATE => $update ? '1' : '0',
384 ]);
385 }
386
387 private function connectProduct(): void
388 {
389 $GLOBALS['product_sync_test_meta'][42][ProductMetaKey::basalamProductId()] = 777;
390 }
391
392 private function assertJob(bool $enabled, string $type): void
393 {
394 self::assertSame($enabled ? [['type' => $type, 'status' => 'pending', 'payload' => ['product_id' => 42, 'automatic' => true]]] : [], $this->jobs->created);
395 }
396 }
397 }
398