Files
shopPRO/tests/Unit/Domain/Order/OrderAdminServiceTest.php
Jacek Pyziak 52119a0724 feat: database-backed cron job queue replacing JSON file system
Replace file-based JSON cron queue with DB-backed job queue (pp_cron_jobs,
pp_cron_schedules). New Domain\CronJob module: CronJobType (constants),
CronJobRepository (CRUD, atomic fetch, retry/backoff), CronJobProcessor
(orchestration with handler registration). Priority ordering guarantees
apilo_send_order (40) runs before sync tasks (50). Includes cron.php auth
protection, race condition fix in fetchNext, API response validation,
and DI wiring across all entry points. 41 new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:29:11 +01:00

245 lines
9.6 KiB
PHP

<?php
namespace Tests\Unit\Domain\Order;
use PHPUnit\Framework\TestCase;
use Domain\Order\OrderAdminService;
use Domain\Order\OrderRepository;
use Domain\Product\ProductRepository;
use Domain\Settings\SettingsRepository;
use Domain\Transport\TransportRepository;
use Domain\CronJob\CronJobRepository;
use Domain\CronJob\CronJobType;
class OrderAdminServiceTest extends TestCase
{
private function createService(
$orderRepo = null,
$productRepo = null,
$settingsRepo = null,
$transportRepo = null
): OrderAdminService {
if (!$orderRepo) {
$orderRepo = $this->createMock(OrderRepository::class);
}
return new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
}
public function testConstructorAcceptsOnlyOrderRepository(): void
{
$orderRepo = $this->createMock(OrderRepository::class);
$service = new OrderAdminService($orderRepo);
$this->assertInstanceOf(OrderAdminService::class, $service);
}
public function testConstructorAcceptsAllDependencies(): void
{
$orderRepo = $this->createMock(OrderRepository::class);
$productRepo = $this->createMock(ProductRepository::class);
$settingsRepo = $this->createMock(SettingsRepository::class);
$transportRepo = $this->createMock(TransportRepository::class);
$service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
$this->assertInstanceOf(OrderAdminService::class, $service);
}
public function testSearchProductsReturnsEmptyForEmptyQuery(): void
{
$productRepo = $this->createMock(ProductRepository::class);
$productRepo->expects($this->never())->method('searchProductByNameAjax');
$service = $this->createService(null, $productRepo);
$result = $service->searchProducts('', 'pl');
$this->assertSame([], $result);
}
public function testSearchProductsReturnsEmptyWithoutProductRepo(): void
{
$service = $this->createService();
$result = $service->searchProducts('test', 'pl');
$this->assertSame([], $result);
}
public function testSearchProductsReturnsFormattedResults(): void
{
$productRepo = $this->createMock(ProductRepository::class);
$productRepo->method('searchProductByNameAjax')
->with('koszulka', 'pl')
->willReturn([
['product_id' => 10],
['product_id' => 20],
]);
$productRepo->method('findCached')
->willReturnCallback(function ($id) {
if ($id === 10) {
return [
'language' => ['name' => 'Koszulka biała'],
'sku' => 'KB-001',
'ean' => '',
'price_brutto' => 49.99,
'price_brutto_promo' => 39.99,
'vat' => 23,
'quantity' => 15,
'parent_id' => 0,
];
}
return null; // product 20 not found
});
$productRepo->method('getProductImg')
->willReturn('/images/products/test.jpg');
$service = $this->createService(null, $productRepo);
$results = $service->searchProducts('koszulka', 'pl');
$this->assertCount(1, $results);
$this->assertSame(10, $results[0]['product_id']);
$this->assertSame('Koszulka biała', $results[0]['name']);
$this->assertSame('KB-001', $results[0]['sku']);
$this->assertSame(49.99, $results[0]['price_brutto']);
$this->assertSame(39.99, $results[0]['price_brutto_promo']);
$this->assertSame(15, $results[0]['quantity']);
}
public function testSaveOrderProductsReturnsFalseForInvalidOrderId(): void
{
$service = $this->createService();
$this->assertFalse($service->saveOrderProducts(0, []));
}
public function testSaveOrderProductsDeletesRemovedProducts(): void
{
$orderRepo = $this->createMock(OrderRepository::class);
$productRepo = $this->createMock(ProductRepository::class);
$settingsRepo = $this->createMock(SettingsRepository::class);
$transportRepo = $this->createMock(TransportRepository::class);
// Existing products
$orderRepo->method('orderProducts')
->with(1)
->willReturn([
['id' => 100, 'product_id' => 5, 'quantity' => 2, 'price_brutto' => 10, 'price_brutto_promo' => 0],
['id' => 101, 'product_id' => 6, 'quantity' => 1, 'price_brutto' => 20, 'price_brutto_promo' => 0],
]);
// Product 100 is submitted with delete flag
// Product 101 is not submitted at all (also deleted)
$orderRepo->expects($this->exactly(2))->method('deleteOrderProduct');
$orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
$transportRepo->method('findActiveById')->willReturn(null);
// Stock should be returned for both deleted products
$productRepo->method('getQuantity')->willReturn(10);
$productRepo->expects($this->exactly(2))->method('updateQuantity');
$service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
$result = $service->saveOrderProducts(1, [
['order_product_id' => 100, 'delete' => '1', 'quantity' => 2],
]);
$this->assertTrue($result);
}
public function testSaveOrderProductsUpdatesQuantityAndAdjustsStock(): void
{
$orderRepo = $this->createMock(OrderRepository::class);
$productRepo = $this->createMock(ProductRepository::class);
$settingsRepo = $this->createMock(SettingsRepository::class);
$transportRepo = $this->createMock(TransportRepository::class);
// Existing: qty=3
$orderRepo->method('orderProducts')
->willReturn([
['id' => 100, 'product_id' => 5, 'quantity' => 3, 'price_brutto' => 10, 'price_brutto_promo' => 0],
]);
// Submit: qty=5 (increased by 2 → stock decreases by 2)
$orderRepo->expects($this->once())->method('updateOrderProduct')
->with(100, $this->callback(function ($data) {
return $data['quantity'] === 5;
}));
$orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
$transportRepo->method('findActiveById')->willReturn(null);
$productRepo->method('getQuantity')->with(5)->willReturn(20);
$productRepo->expects($this->once())->method('updateQuantity')
->with(5, 18); // 20 + (3 - 5) = 18
$service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
$service->saveOrderProducts(1, [
['order_product_id' => 100, 'product_id' => 5, 'quantity' => 5, 'price_brutto' => 10, 'price_brutto_promo' => 0],
]);
}
public function testSaveOrderProductsAddsNewProductAndDecreasesStock(): void
{
$orderRepo = $this->createMock(OrderRepository::class);
$productRepo = $this->createMock(ProductRepository::class);
$settingsRepo = $this->createMock(SettingsRepository::class);
$transportRepo = $this->createMock(TransportRepository::class);
$orderRepo->method('orderProducts')->willReturn([]);
$orderRepo->expects($this->once())->method('addOrderProduct')
->with(1, $this->callback(function ($data) {
return $data['product_id'] === 10
&& $data['name'] === 'New Product'
&& $data['quantity'] === 2;
}));
$orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
$transportRepo->method('findActiveById')->willReturn(null);
$productRepo->method('getQuantity')->with(10)->willReturn(15);
$productRepo->expects($this->once())->method('updateQuantity')
->with(10, 13); // 15 - 2
$service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
$service->saveOrderProducts(1, [
[
'order_product_id' => 0,
'product_id' => 10,
'parent_product_id' => 10,
'name' => 'New Product',
'vat' => 23,
'price_brutto' => 50,
'price_brutto_promo' => 0,
'quantity' => 2,
],
]);
}
public function testGetFreeDeliveryThresholdReturnsZeroWithoutSettingsRepo(): void
{
$service = $this->createService();
$this->assertSame(0.0, $service->getFreeDeliveryThreshold());
}
public function testGetFreeDeliveryThresholdReturnsValue(): void
{
$settingsRepo = $this->createMock(SettingsRepository::class);
$settingsRepo->method('getSingleValue')
->with('free_delivery')
->willReturn('150.00');
$service = $this->createService(null, null, $settingsRepo);
$this->assertSame(150.0, $service->getFreeDeliveryThreshold());
}
// =========================================================================
// queueApiloSync — DB-based via CronJobRepository
// =========================================================================
public function testConstructorAcceptsCronJobRepo(): void
{
$orderRepo = $this->createMock(OrderRepository::class);
$cronJobRepo = $this->createMock(CronJobRepository::class);
$service = new OrderAdminService($orderRepo, null, null, null, $cronJobRepo);
$this->assertInstanceOf(OrderAdminService::class, $service);
}
}