All checks were successful
CI / secret-scan (push) Successful in 16s
CI / php-lint (push) Successful in 30s
CI / static-tests (push) Successful in 1m2s
CI / js-tests (push) Successful in 37s
CI / secret-scan (pull_request) Successful in 18s
CI / php-lint (pull_request) Successful in 28s
CI / static-tests (pull_request) Successful in 1m2s
CI / js-tests (pull_request) Successful in 37s
163 lines
6.4 KiB
PHP
163 lines
6.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Order;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use App\Core\DatabaseInterface;
|
|
use App\Order\OrderQueryRepository;
|
|
|
|
/**
|
|
* Double de lecture minimal pour OrderQueryRepository::paidQueueWithDetail : route
|
|
* les quatre SELECT (file paid -> order_item -> selections -> modifiers) sur des
|
|
* jeux de lignes scriptes. Les requetes detail utilisent IN (...) sans parametre lie
|
|
* (ids casts en entier dans le repo) : on desambiguise donc uniquement sur le texte
|
|
* SQL. Aucune ecriture (l'operation est lecture seule, RG-5 de 5.1).
|
|
*/
|
|
final class FakeKdsDatabase implements DatabaseInterface
|
|
{
|
|
/** @var list<array<string, mixed>> commandes paid renvoyees par la file. */
|
|
public array $orders = [];
|
|
/** @var list<array<string, mixed>> lignes order_item (tous order_id confondus). */
|
|
public array $items = [];
|
|
/** @var list<array<string, mixed>> lignes order_item_selection. */
|
|
public array $selections = [];
|
|
/** @var list<array<string, mixed>> lignes order_item_modifier (avec ingredient_name). */
|
|
public array $modifiers = [];
|
|
|
|
public function fetch(string $sql, array $params = []): ?array
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function fetchAll(string $sql, array $params = []): array
|
|
{
|
|
if (str_contains($sql, "WHERE status = 'paid'")) {
|
|
return $this->orders;
|
|
}
|
|
if (str_contains($sql, 'FROM order_item WHERE order_id IN')) {
|
|
return $this->items;
|
|
}
|
|
if (str_contains($sql, 'FROM order_item_selection')) {
|
|
return $this->selections;
|
|
}
|
|
if (str_contains($sql, 'FROM order_item_modifier')) {
|
|
return $this->modifiers;
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
public function execute(string $sql, array $params = []): int
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public function transaction(callable $fn): void
|
|
{
|
|
$fn($this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Couvre la derivation de la bande SLA (slaBand, RG-4 de 5.1 / Note 6 : vert < 5 min,
|
|
* ambre 5-10 min, rouge > 10 min, calcul depuis now - paid_at avec horloge injectee)
|
|
* et l'assemblage du payload enrichi (paidQueueWithDetail : items imbriquant
|
|
* selections + modifiers, tri preserve, id technique non expose).
|
|
*/
|
|
final class OrderQueryRepositoryTest extends TestCase
|
|
{
|
|
private const NOW = 1_700_000_000; // epoch de reference fixe (deterministe).
|
|
|
|
/** paid_at calibre a $secondsAgo secondes avant NOW. */
|
|
private function paidAtSecondsAgo(int $secondsAgo): string
|
|
{
|
|
return date('Y-m-d H:i:s', self::NOW - $secondsAgo);
|
|
}
|
|
|
|
public function testSlaBandFreshBelowFiveMinutes(): void
|
|
{
|
|
$repo = new OrderQueryRepository(new FakeKdsDatabase());
|
|
// 4 min ecoulees -> sous le seuil ambre (300 s) -> vert.
|
|
self::assertSame('fresh', $repo->slaBand($this->paidAtSecondsAgo(240), self::NOW));
|
|
}
|
|
|
|
public function testSlaBandWarnBetweenFiveAndTenMinutes(): void
|
|
{
|
|
$repo = new OrderQueryRepository(new FakeKdsDatabase());
|
|
// 7 min ecoulees -> >= 300 s et < 600 s -> ambre.
|
|
self::assertSame('warn', $repo->slaBand($this->paidAtSecondsAgo(420), self::NOW));
|
|
}
|
|
|
|
public function testSlaBandLateBeyondTenMinutes(): void
|
|
{
|
|
$repo = new OrderQueryRepository(new FakeKdsDatabase());
|
|
// 12 min ecoulees -> >= 600 s (seuil cible) -> rouge.
|
|
self::assertSame('late', $repo->slaBand($this->paidAtSecondsAgo(720), self::NOW));
|
|
}
|
|
|
|
public function testSlaBandFallsBackToFreshOnEmptyPaidAt(): void
|
|
{
|
|
$repo = new OrderQueryRepository(new FakeKdsDatabase());
|
|
// Donnee absente : pas d'alerte sur une valeur manquante.
|
|
self::assertSame('fresh', $repo->slaBand('', self::NOW));
|
|
}
|
|
|
|
public function testPaidQueueWithDetailNestsItemsSelectionsAndModifiers(): void
|
|
{
|
|
$db = new FakeKdsDatabase();
|
|
$db->orders = [
|
|
['id' => 7, 'order_number' => 'K7', 'source' => 'kiosk', 'service_mode' => 'dine_in', 'service_tag' => '3', 'total_ttc_cents' => 990, 'paid_at' => $this->paidAtSecondsAgo(120)],
|
|
];
|
|
$db->items = [
|
|
['id' => 50, 'order_id' => 7, 'item_type' => 'menu', 'format' => 'maxi', 'label_snapshot' => 'Menu Le 280', 'quantity' => 1],
|
|
];
|
|
$db->selections = [
|
|
['order_item_id' => 50, 'label_snapshot' => 'Coca 50cl'],
|
|
['order_item_id' => 50, 'label_snapshot' => 'Grande Frite'],
|
|
];
|
|
$db->modifiers = [
|
|
['order_item_id' => 50, 'action' => 'remove', 'ingredient_name' => 'oignon'],
|
|
['order_item_id' => 50, 'action' => 'add', 'ingredient_name' => 'bacon'],
|
|
];
|
|
|
|
$queue = (new OrderQueryRepository($db))->paidQueueWithDetail(['kiosk', 'counter', 'drive'], self::NOW);
|
|
|
|
self::assertCount(1, $queue);
|
|
$order = $queue[0];
|
|
// L'id technique (cle de jointure) n'est pas expose a la vue.
|
|
self::assertArrayNotHasKey('id', $order);
|
|
self::assertSame('K7', $order['order_number']);
|
|
self::assertSame('fresh', $order['sla_band']); // 2 min ecoulees.
|
|
|
|
self::assertCount(1, $order['items']);
|
|
$item = $order['items'][0];
|
|
self::assertSame('Menu Le 280', $item['label_snapshot']);
|
|
self::assertSame('maxi', $item['format']);
|
|
self::assertSame(['Coca 50cl', 'Grande Frite'], array_column($item['selections'], 'label_snapshot'));
|
|
self::assertSame(['oignon', 'bacon'], array_column($item['modifiers'], 'ingredient_name'));
|
|
self::assertSame(['remove', 'add'], array_column($item['modifiers'], 'action'));
|
|
}
|
|
|
|
public function testPaidQueueWithDetailEmptyWhenNoVisibleSource(): void
|
|
{
|
|
// Aucune source visible -> file vide (coherent avec paidQueue).
|
|
$queue = (new OrderQueryRepository(new FakeKdsDatabase()))->paidQueueWithDetail([], self::NOW);
|
|
self::assertSame([], $queue);
|
|
}
|
|
|
|
public function testPaidQueueWithDetailHandlesOrderWithoutItems(): void
|
|
{
|
|
// Commande sans ligne (cas degrade) : items vide, pas d'erreur.
|
|
$db = new FakeKdsDatabase();
|
|
$db->orders = [
|
|
['id' => 9, 'order_number' => 'K9', 'source' => 'kiosk', 'service_mode' => 'takeaway', 'service_tag' => null, 'total_ttc_cents' => 500, 'paid_at' => $this->paidAtSecondsAgo(60)],
|
|
];
|
|
|
|
$queue = (new OrderQueryRepository($db))->paidQueueWithDetail(['kiosk'], self::NOW);
|
|
|
|
self::assertCount(1, $queue);
|
|
self::assertSame([], $queue[0]['items']);
|
|
}
|
|
}
|