Files
orderPRO/tests/Unit/AllegroStatusSyncServiceTest.php
2026-03-28 15:04:35 +01:00

265 lines
9.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Modules\Cron\CronRepository;
use App\Modules\Settings\AllegroApiClient;
use App\Modules\Settings\AllegroIntegrationRepository;
use App\Modules\Settings\AllegroOrderImportService;
use App\Modules\Settings\AllegroOrderSyncStateRepository;
use App\Modules\Settings\AllegroStatusMappingRepository;
use App\Modules\Settings\AllegroStatusSyncService;
use App\Modules\Settings\AllegroTokenManager;
use PDO;
use PDOStatement;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use RuntimeException;
final class AllegroStatusSyncServiceTest extends TestCase
{
private CronRepository&MockObject $cronRepository;
private AllegroOrderImportService&MockObject $orderImportService;
private AllegroApiClient&MockObject $apiClient;
private AllegroTokenManager&MockObject $tokenManager;
private AllegroStatusMappingRepository&MockObject $statusMappings;
private AllegroOrderSyncStateRepository&MockObject $syncStateRepository;
private AllegroIntegrationRepository&MockObject $integrationRepository;
protected function setUp(): void
{
$this->cronRepository = $this->createMock(CronRepository::class);
$this->orderImportService = $this->createMock(AllegroOrderImportService::class);
$this->apiClient = $this->createMock(AllegroApiClient::class);
$this->tokenManager = $this->createMock(AllegroTokenManager::class);
$this->statusMappings = $this->createMock(AllegroStatusMappingRepository::class);
$this->syncStateRepository = $this->createMock(AllegroOrderSyncStateRepository::class);
$this->integrationRepository = $this->createMock(AllegroIntegrationRepository::class);
}
public function testPushDirectionProcessesMappedOrdersAndSkipsUnmapped(): void
{
$this->cronRepository
->method('getStringSetting')
->willReturn('orderpro_to_allegro');
$this->integrationRepository
->method('getActiveIntegrationId')
->willReturn(5);
$this->statusMappings
->method('buildOrderproToAllegroMap')
->willReturn([
'processing' => 'ready_for_processing',
]);
$this->tokenManager
->method('resolveToken')
->willReturn(['token-1', 'sandbox']);
$this->syncStateRepository
->method('getLastStatusPushedAt')
->willReturn(null);
$this->syncStateRepository
->expects($this->once())
->method('updateLastStatusPushedAt')
->with(5, '2026-03-28 10:10:00');
$this->apiClient
->expects($this->exactly(2))
->method('updateCheckoutFormFulfillment')
->with(
'sandbox',
'token-1',
$this->logicalOr('A1', 'A3'),
'ready_for_processing'
)
->willReturn([]);
$service = $this->createServiceWithPushRows([
['source_order_id' => 'A1', 'orderpro_status_code' => 'processing', 'latest_change' => '2026-03-28 10:00:00'],
['source_order_id' => 'A2', 'orderpro_status_code' => 'unknown', 'latest_change' => '2026-03-28 10:05:00'],
['source_order_id' => 'A3', 'orderpro_status_code' => 'processing', 'latest_change' => '2026-03-28 10:10:00'],
]);
$result = $service->sync();
$this->assertTrue($result['ok']);
$this->assertSame('orderpro_to_allegro', $result['direction']);
$this->assertSame(2, $result['pushed']);
$this->assertSame(1, $result['skipped']);
$this->assertSame(0, $result['failed']);
}
public function testPushDirectionReturnsEarlyWhenNoMappingsExist(): void
{
$this->cronRepository
->method('getStringSetting')
->willReturn('orderpro_to_allegro');
$this->integrationRepository
->method('getActiveIntegrationId')
->willReturn(5);
$this->statusMappings
->method('buildOrderproToAllegroMap')
->willReturn([]);
$this->apiClient
->expects($this->never())
->method('updateCheckoutFormFulfillment');
$service = $this->createServiceWithPushRows([]);
$result = $service->sync();
$this->assertTrue($result['ok']);
$this->assertSame('orderpro_to_allegro', $result['direction']);
$this->assertSame(0, $result['pushed']);
$this->assertSame(0, $result['failed']);
$this->assertStringContainsString('Brak mapowan', (string) ($result['message'] ?? ''));
}
public function testPushDirectionCollectsFailureAndContinuesProcessing(): void
{
$this->cronRepository
->method('getStringSetting')
->willReturn('orderpro_to_allegro');
$this->integrationRepository
->method('getActiveIntegrationId')
->willReturn(7);
$this->statusMappings
->method('buildOrderproToAllegroMap')
->willReturn([
'processing' => 'ready_for_processing',
]);
$this->tokenManager
->method('resolveToken')
->willReturn(['token-fail-test', 'sandbox']);
$this->syncStateRepository
->method('getLastStatusPushedAt')
->willReturn(null);
$this->syncStateRepository
->expects($this->once())
->method('updateLastStatusPushedAt')
->with(7, '2026-03-28 10:10:00');
$calls = 0;
$this->apiClient
->method('updateCheckoutFormFulfillment')
->willReturnCallback(function () use (&$calls): array {
$calls++;
if ($calls === 1) {
throw new RuntimeException('API Allegro HTTP 422');
}
return [];
});
$service = $this->createServiceWithPushRows([
['source_order_id' => 'X1', 'orderpro_status_code' => 'processing', 'latest_change' => '2026-03-28 10:00:00'],
['source_order_id' => 'X2', 'orderpro_status_code' => 'processing', 'latest_change' => '2026-03-28 10:10:00'],
]);
$result = $service->sync();
$this->assertTrue($result['ok']);
$this->assertSame(1, $result['pushed']);
$this->assertSame(1, $result['failed']);
$this->assertCount(1, $result['errors']);
$this->assertSame('X1', $result['errors'][0]['source_order_id'] ?? null);
}
public function testPushDirectionRetriesOnceAfter401(): void
{
$this->cronRepository
->method('getStringSetting')
->willReturn('orderpro_to_allegro');
$this->integrationRepository
->method('getActiveIntegrationId')
->willReturn(9);
$this->statusMappings
->method('buildOrderproToAllegroMap')
->willReturn([
'processing' => 'ready_for_processing',
]);
$this->tokenManager
->method('resolveToken')
->willReturnOnConsecutiveCalls(
['token-old', 'sandbox'],
['token-new', 'sandbox']
);
$this->syncStateRepository
->method('getLastStatusPushedAt')
->willReturn(null);
$this->syncStateRepository
->expects($this->once())
->method('updateLastStatusPushedAt')
->with(9, '2026-03-28 11:00:00');
$calls = 0;
$this->apiClient
->method('updateCheckoutFormFulfillment')
->willReturnCallback(function () use (&$calls): array {
$calls++;
if ($calls === 1) {
throw new RuntimeException('ALLEGRO_HTTP_401');
}
return [];
});
$service = $this->createServiceWithPushRows([
['source_order_id' => 'R1', 'orderpro_status_code' => 'processing', 'latest_change' => '2026-03-28 11:00:00'],
]);
$result = $service->sync();
$this->assertTrue($result['ok']);
$this->assertSame(1, $result['pushed']);
$this->assertSame(0, $result['failed']);
$this->assertSame(0, $result['skipped']);
$this->assertSame(2, $calls);
}
/**
* @param array<int, array<string, mixed>> $rows
*/
private function createServiceWithPushRows(array $rows): AllegroStatusSyncService
{
$statement = $this->createMock(PDOStatement::class);
$statement
->method('execute')
->willReturn(true);
$statement
->method('fetchAll')
->willReturn($rows);
$pdo = $this->createMock(PDO::class);
$pdo
->method('prepare')
->willReturn($statement);
return new AllegroStatusSyncService(
$this->cronRepository,
$this->orderImportService,
$this->apiClient,
$this->tokenManager,
$this->statusMappings,
$this->syncStateRepository,
$this->integrationRepository,
$pdo
);
}
}