PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.21
ووسلام – همگام سازی ووکامرس و باسلام v1.10.21
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 1.8.5 All 54 releases
sync-basalam / tests / Services / StaleVariationRecoveryTest.php

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

499 lines 16.8 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('esc_html')) {
5 function esc_html($value)
6 {
7 return (string) $value;
8 }
9 }
10
11 if (!function_exists('is_wp_error')) {
12 function is_wp_error($value)
13 {
14 return false;
15 }
16 }
17
18 if (!function_exists('wp_remote_retrieve_body')) {
19 function wp_remote_retrieve_body($response)
20 {
21 return $response['body'] ?? '';
22 }
23 }
24
25 if (!function_exists('wp_remote_retrieve_response_code')) {
26 function wp_remote_retrieve_response_code($response)
27 {
28 return $response['response']['code'] ?? 0;
29 }
30 }
31
32 if (!function_exists('get_option')) {
33 function get_option($name, $default = false)
34 {
35 return $GLOBALS['sync_basalam_test_options'][$name] ?? $default;
36 }
37 }
38
39 if (!function_exists('update_option')) {
40 function update_option($name, $value, $autoload = null)
41 {
42 $GLOBALS['sync_basalam_test_options'][$name] = $value;
43 return true;
44 }
45 }
46
47 if (!function_exists('wc_get_product')) {
48 function wc_get_product($productId)
49 {
50 return $GLOBALS['sync_basalam_test_products'][$productId] ?? null;
51 }
52 }
53
54 if (!function_exists('delete_post_meta')) {
55 function delete_post_meta($postId, $metaKey)
56 {
57 $GLOBALS['sync_basalam_deleted_meta'][] = [$postId, $metaKey];
58 return true;
59 }
60 }
61
62 if (!function_exists('get_post_meta')) {
63 function get_post_meta($postId, $metaKey, $single = false)
64 {
65 return $GLOBALS['sync_basalam_test_meta'][$postId][$metaKey] ?? '';
66 }
67 }
68
69 if (!function_exists('update_post_meta')) {
70 function update_post_meta($postId, $metaKey, $value)
71 {
72 $GLOBALS['sync_basalam_test_meta'][$postId][$metaKey] = $value;
73 $GLOBALS['sync_basalam_updated_meta'][] = [$postId, $metaKey, $value];
74 return true;
75 }
76 }
77
78 if (!function_exists('get_post_type')) {
79 function get_post_type($postId)
80 {
81 return 'product';
82 }
83 }
84
85 if (!function_exists('apply_filters')) {
86 function apply_filters($hookName, $value)
87 {
88 return $value;
89 }
90 }
91
92 if (!function_exists('do_action')) {
93 function do_action($hookName, ...$args)
94 {
95 }
96 }
97
98 if (!function_exists('syncBasalamSettings')) {
99 function syncBasalamSettings()
100 {
101 return new class {
102 public function getSettings($setting = null)
103 {
104 $settings = $GLOBALS['sync_basalam_test_options']['sync_basalam_settings'] ?? [];
105
106 return $setting === null ? $settings : ($settings[$setting] ?? null);
107 }
108 };
109 }
110 }
111
112 if (!class_exists('WC_Logger')) {
113 class WC_Logger
114 {
115 }
116 }
117
118 if (!function_exists('wc_get_logger')) {
119 function wc_get_logger()
120 {
121 return new class {
122 public function log($level, $message, $context = [])
123 {
124 }
125 };
126 }
127 }
128 }
129
130 namespace SyncBasalam\Services\Products {
131 if (!function_exists(__NAMESPACE__ . '\\syncBasalamContainer')) {
132 function syncBasalamContainer()
133 {
134 return $GLOBALS['sync_basalam_test_container'];
135 }
136 }
137 }
138
139 namespace SyncBasalam\Tests\Services {
140 use PHPUnit\Framework\TestCase;
141 use ReflectionMethod;
142 use SyncBasalam\Jobs\Exceptions\NonRetryableException;
143 use SyncBasalam\Jobs\Exceptions\StaleVariationException;
144 use SyncBasalam\Services\Api\ApiResponseHandler;
145 use SyncBasalam\Services\Products\UpdateProductVariationsService;
146 use SyncBasalam\Services\Products\UpdateSingleProductService;
147
148 require_once dirname(__DIR__, 2) . '/includes/Jobs/Exceptions/JobException.php';
149 require_once dirname(__DIR__, 2) . '/includes/Jobs/Exceptions/NonRetryableException.php';
150 require_once dirname(__DIR__, 2) . '/includes/Jobs/Exceptions/RetryableException.php';
151 require_once dirname(__DIR__, 2) . '/includes/Jobs/Exceptions/StaleVariationException.php';
152 require_once dirname(__DIR__, 2) . '/includes/Admin/Settings/SettingsConfig.php';
153 require_once dirname(__DIR__, 2) . '/includes/Admin/Settings/SettingsManager.php';
154 require_once dirname(__DIR__, 2) . '/includes/Utilities/ProductMetaKey.php';
155 require_once dirname(__DIR__, 2) . '/includes/Logger/LoggerInterface.php';
156 require_once dirname(__DIR__, 2) . '/includes/Logger/WooLogger.php';
157 require_once dirname(__DIR__, 2) . '/includes/Logger/Logger.php';
158 require_once dirname(__DIR__, 2) . '/includes/Services/Products/ProductConnection.php';
159 require_once dirname(__DIR__, 2) . '/includes/Services/Api/RequestStatusTracker.php';
160 require_once dirname(__DIR__, 2) . '/includes/Services/Api/ApiResponseHandler.php';
161 require_once dirname(__DIR__, 2) . '/includes/Services/Products/UpdateProductVariationsService.php';
162 require_once dirname(__DIR__, 2) . '/includes/Services/Products/UpdateSingleProductService.php';
163
164 class StaleVariationRecoveryTest extends TestCase
165 {
166 protected function setUp(): void
167 {
168 $GLOBALS['sync_basalam_test_options'] = [];
169 $GLOBALS['sync_basalam_test_products'] = [];
170 $GLOBALS['sync_basalam_deleted_meta'] = [];
171 $GLOBALS['sync_basalam_updated_meta'] = [];
172 $GLOBALS['sync_basalam_test_meta'] = [
173 7196 => ['sync_basalam_product_id' => 47405114],
174 ];
175 $GLOBALS['sync_basalam_test_container'] = new class {
176 public function get($id)
177 {
178 return new class {
179 public function canUpdate()
180 {
181 return true;
182 }
183
184 public function shouldRestrictUpdateFields($refresh = true)
185 {
186 return false;
187 }
188
189 public function restrictUpdatePayload(array $payload, $refresh = true)
190 {
191 return $payload;
192 }
193 };
194 }
195 };
196 $GLOBALS['wpdb'] = new class {
197 public $postmeta = 'wp_postmeta';
198 public $posts = 'wp_posts';
199
200 public function prepare($query, ...$args)
201 {
202 return $query;
203 }
204
205 public function get_results($query)
206 {
207 return [];
208 }
209 };
210 }
211
212 protected function tearDown(): void
213 {
214 unset(
215 $GLOBALS['sync_basalam_test_options'],
216 $GLOBALS['sync_basalam_test_products'],
217 $GLOBALS['sync_basalam_deleted_meta'],
218 $GLOBALS['sync_basalam_updated_meta'],
219 $GLOBALS['sync_basalam_test_meta'],
220 $GLOBALS['sync_basalam_test_container'],
221 $GLOBALS['wpdb']
222 );
223 }
224
225 public function testApiResponsePreservesNotFoundStatusCode(): void
226 {
227 $handler = new ApiResponseHandler();
228
229 try {
230 $handler->handle([
231 'body' => json_encode(['message' => 'not found']),
232 'response' => ['code' => 404],
233 ], 'https://core.basalam.com/v4/products/10/variations/20');
234 self::fail('Expected a non-retryable 404 exception.');
235 } catch (NonRetryableException $exception) {
236 self::assertSame(404, $exception->getCode());
237 self::assertSame(['message' => 'not found'], $exception->getResponseData());
238 }
239 }
240
241 public function testVariationUpdaterTurnsRemote404IntoStaleMappingSignal(): void
242 {
243 $api = new class {
244 public $calls = 0;
245
246 public function patch($url, $data)
247 {
248 $this->calls++;
249 throw new NonRetryableException('missing', 404);
250 }
251 };
252
253 $service = new UpdateProductVariationsService($api);
254
255 try {
256 $service->updateVariations(47405114, [
257 ['id' => 43666032, 'stock' => 1],
258 ['id' => 43666033, 'stock' => 2],
259 ], 7196);
260 self::fail('Expected a stale variation mapping exception.');
261 } catch (StaleVariationException $exception) {
262 self::assertSame(404, $exception->getCode());
263 self::assertSame(47405114, $exception->getBasalamProductId());
264 self::assertSame(43666032, $exception->getBasalamVariationId());
265 self::assertSame(1, $api->calls, 'Recovery should start after the first stale id.');
266 }
267 }
268
269 public function testVariationUpdaterKeepsTheNormalPerVariationPath(): void
270 {
271 $api = new class {
272 public $requests = [];
273
274 public function patch($url, $data)
275 {
276 $this->requests[] = [$url, $data];
277
278 return ['status_code' => 200, 'body' => '{}'];
279 }
280 };
281
282 $service = new UpdateProductVariationsService($api);
283 $result = $service->updateVariations(47405114, [
284 [
285 'id' => 54840740,
286 'primary_price' => 63150000,
287 'stock' => 0,
288 'sku' => 'wooden-bowl',
289 'properties' => [['property' => 'شکل', 'value' => 'گلدان']],
290 ],
291 [
292 'id' => 54840741,
293 'primary_price' => 64000000,
294 'stock' => 2,
295 ],
296 ], 7196);
297
298 self::assertSame(['updated' => 2, 'skipped' => 0, 'failed' => 0], $result);
299 self::assertSame([
300 [
301 'https://core.basalam.com/v4/products/47405114/variations/54840740',
302 ['primary_price' => 63150000, 'stock' => 0, 'sku' => 'wooden-bowl'],
303 ],
304 [
305 'https://core.basalam.com/v4/products/47405114/variations/54840741',
306 ['primary_price' => 64000000, 'stock' => 2],
307 ],
308 ], $api->requests);
309 }
310
311 public function testRecoveryBuildsFullIdFreePayloadBeforeClearingStoredMappings(): void
312 {
313 $product = new class {
314 public function is_type($type)
315 {
316 return $type === 'variable';
317 }
318
319 public function get_children()
320 {
321 return [10943];
322 }
323 };
324
325 $variantData = new class {
326 public function getVariants($product)
327 {
328 return [[
329 'id' => 43666032,
330 'primary_price' => 63150000,
331 'stock' => 0,
332 'properties' => [
333 ['property' => 'شکل', 'value' => 'گلدان'],
334 ],
335 ]];
336 }
337 };
338
339 $GLOBALS['sync_basalam_test_products'][7196] = $product;
340
341 $service = new UpdateSingleProductService(new \stdClass(), new \stdClass(), $variantData);
342 $method = new ReflectionMethod(UpdateSingleProductService::class, 'prepareVariationRemapPayload');
343 $method->setAccessible(true);
344
345 $payload = $method->invoke($service, 7196, [
346 'id' => 47405114,
347 'name' => 'کاسه بزرگ چوبی',
348 'variants' => [['id' => 43666032, 'stock' => 0]],
349 ]);
350
351 self::assertSame('کاسه بزرگ چوبی', $payload['name']);
352 self::assertArrayNotHasKey('id', $payload['variants'][0]);
353 self::assertSame(63150000, $payload['variants'][0]['primary_price']);
354 self::assertSame('گلدان', $payload['variants'][0]['properties'][0]['value']);
355 self::assertSame(
356 [[10943, 'sync_basalam_variation_id']],
357 $GLOBALS['sync_basalam_deleted_meta']
358 );
359 }
360
361 public function testCustomModeStaleVariationFallsBackToOneCompleteProductPatch(): void
362 {
363 $attribute = new class {
364 public function get_variation()
365 {
366 return true;
367 }
368 };
369
370 $variation = new class {
371 public function get_attribute($name)
372 {
373 return 'قر�
374 ز';
375 }
376 };
377
378 $product = new class($attribute) {
379 private $attribute;
380
381 public function __construct($attribute)
382 {
383 $this->attribute = $attribute;
384 }
385
386 public function is_type($type)
387 {
388 return $type === 'variable';
389 }
390
391 public function get_children()
392 {
393 return [10943];
394 }
395
396 public function get_attributes()
397 {
398 return ['pa_color' => $this->attribute];
399 }
400 };
401
402 $GLOBALS['sync_basalam_test_products'][7196] = $product;
403 $GLOBALS['sync_basalam_test_products'][10943] = $variation;
404 $GLOBALS['sync_basalam_test_options']['sync_basalam_settings'] = [
405 'sync_product_fields' => 'custom',
406 'sync_product_field_variant_price' => 1,
407 'sync_product_field_variant_stock' => 1,
408 ];
409
410 $variantDataService = new class {
411 public function getVariants($product)
412 {
413 return [[
414 'id' => 43666032,
415 'primary_price' => 63150000,
416 'stock' => 3,
417 'properties' => [
418 ['property' => 'رنگ', 'value' => 'قر�
419 ز'],
420 ],
421 ]];
422 }
423 };
424
425 $api = new class {
426 public $requests = [];
427
428 public function patch($url, $data)
429 {
430 $this->requests[] = [$url, $data];
431
432 if (strpos($url, '/variations/') !== false) {
433 throw new NonRetryableException('missing', 404);
434 }
435
436 return [
437 'status_code' => 200,
438 'body' => [
439 'variants' => [[
440 'id' => 99990001,
441 'properties' => [
442 ['value' => ['title' => 'قر�
443 ز']],
444 ],
445 ]],
446 ],
447 ];
448 }
449 };
450
451 $service = new UpdateSingleProductService(
452 $api,
453 new UpdateProductVariationsService($api),
454 $variantDataService
455 );
456 $result = $service->updateProductInBasalam([
457 'id' => 47405114,
458 'type' => 'variable',
459 'variants' => [[
460 'id' => 43666032,
461 'primary_price' => 63150000,
462 'stock' => 3,
463 ]],
464 ], 7196);
465
466 self::assertTrue($result['success']);
467 self::assertCount(2, $api->requests, 'A stale custom variation must be followed by one product PATCH.');
468 self::assertSame(
469 'https://core.basalam.com/v4/products/47405114/variations/43666032',
470 $api->requests[0][0]
471 );
472 self::assertSame([
473 'primary_price' => 63150000,
474 'stock' => 3,
475 ], $api->requests[0][1]);
476 self::assertSame(
477 'https://openapi.basalam.com/v1/products/47405114',
478 $api->requests[1][0]
479 );
480 self::assertSame([
481 'id' => 47405114,
482 'type' => 'variable',
483 'variants' => [[
484 'primary_price' => 63150000,
485 'stock' => 3,
486 'properties' => [
487 ['property' => 'رنگ', 'value' => 'قر�
488 ز'],
489 ],
490 ]],
491 ], $api->requests[1][1]);
492 self::assertContains(
493 [10943, 'sync_basalam_variation_id', 99990001],
494 $GLOBALS['sync_basalam_updated_meta']
495 );
496 }
497 }
498 }
499