All checks were successful
CI / static-tests (push) Successful in 47s
CI / secret-scan (pull_request) Successful in 9s
CI / php-lint (pull_request) Successful in 23s
CI / static-tests (pull_request) Successful in 47s
CI / js-tests (pull_request) Successful in 25s
CI / secret-scan (push) Successful in 10s
CI / php-lint (push) Successful in 22s
CI / js-tests (push) Successful in 29s
Ouvre le domaine commande cote back-office (les commandes existent depuis P4 1a/1b et, depuis la borne L4, sont reellement creees). - App\Order\OrderQueryRepository (read-side, DatabaseInterface seul) : recent($limit) pour la liste admin + salesKpis() (CA encaisse paid+delivered, nb payees, panier moyen, CA/nb du jour, total, repartition par statut). - OrderAdminController : GET /admin/orders, permission order.read, liste lecture seule (numero, mode, chevalet, statut, total ttc, date). Les transitions deliver/cancel restent aux ecrans operationnels (hors back-office MVP). - StatsController : ferme la dette "KPIs de vente differes P4" -> section Ventes dans /admin/stats (CA, commandes payees, panier moyen, total). Hook orderQuery() = seam. - Nav "Commandes" (order.read) reactivee dans la sidebar (etait volontairement absente tant que la route n'existait pas). - Tests : OrderQueryRepositoryDbTest (integration, vraie DB : 3) + OrderAdminControllerTest (double : guard 403/200 + rendu) + StatsControllerTest (section Ventes). 350 PHP verts, PHPStan L6 propre. Routes verifiees live (302 -> login, gardees par SessionGuard).
174 lines
5.2 KiB
PHP
174 lines
5.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Admin;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use App\Auth\SessionManager;
|
|
use App\Catalogue\StatsRepository;
|
|
use App\Controllers\StatsController;
|
|
use App\Core\Config;
|
|
use App\Core\Database;
|
|
use App\Core\DatabaseInterface;
|
|
use App\Core\Request;
|
|
use App\Order\OrderQueryRepository;
|
|
use App\Tests\Support\FakeDatabase;
|
|
|
|
/**
|
|
* Stub de StatsRepository : KPIs canned, sans base. Permet de tester le rendu du
|
|
* controleur independamment des requetes d'agregation (couvertes par le DbTest).
|
|
*/
|
|
final class StubStatsRepository extends StatsRepository
|
|
{
|
|
public function counts(): array
|
|
{
|
|
return [
|
|
'products' => ['total' => 53, 'available' => 50],
|
|
'categories' => ['total' => 9, 'active' => 9],
|
|
'menus' => ['total' => 13, 'available' => 12],
|
|
'ingredients' => ['total' => 7, 'active' => 6],
|
|
];
|
|
}
|
|
|
|
public function stockHealth(): array
|
|
{
|
|
return [
|
|
'active_total' => 6,
|
|
'bands' => ['normal' => 4, 'low' => 1, 'critical' => 1],
|
|
'alerts' => [
|
|
['name' => 'Cheddar', 'stock_pct' => 3, 'stock_band' => 'critical'],
|
|
['name' => 'Cornichon', 'stock_pct' => 8, 'stock_band' => 'low'],
|
|
],
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stub d'OrderQueryRepository : KPIs de vente canned (rendu du bloc Ventes teste
|
|
* sans base ; les agregats sont couverts par OrderQueryRepositoryDbTest).
|
|
*/
|
|
final class StubOrderQueryRepository extends OrderQueryRepository
|
|
{
|
|
public function salesKpis(): array
|
|
{
|
|
return [
|
|
'revenue_cents' => 20800, 'paid_count' => 8, 'avg_basket_cents' => 2600,
|
|
'revenue_today_cents' => 5200, 'paid_count_today' => 2, 'total_orders' => 11,
|
|
'by_status' => ['paid' => 8, 'pending_payment' => 2, 'cancelled' => 1],
|
|
];
|
|
}
|
|
|
|
public function recent(int $limit = 50): array
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
final class TestStatsController extends StatsController
|
|
{
|
|
public function __construct(
|
|
Request $request,
|
|
Config $config,
|
|
Database $database,
|
|
private readonly SessionManager $testSession,
|
|
private readonly FakeDatabase $fakeDb,
|
|
) {
|
|
parent::__construct($request, $config, $database);
|
|
}
|
|
|
|
protected function sessionManager(): SessionManager
|
|
{
|
|
return $this->testSession;
|
|
}
|
|
|
|
protected function db(): DatabaseInterface
|
|
{
|
|
return $this->fakeDb;
|
|
}
|
|
|
|
protected function statsRepository(): StatsRepository
|
|
{
|
|
return new StubStatsRepository($this->fakeDb);
|
|
}
|
|
|
|
protected function orderQuery(): OrderQueryRepository
|
|
{
|
|
return new StubOrderQueryRepository($this->fakeDb);
|
|
}
|
|
}
|
|
|
|
final class StatsControllerTest extends TestCase
|
|
{
|
|
/** @var list<string> */
|
|
private array $touchedKeys = [];
|
|
private SessionManager $session;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->setEnv('SESSION_LIFETIME_IDLE', '14400');
|
|
$this->setEnv('SESSION_LIFETIME_ABSOLUTE', '36000');
|
|
$this->session = new SessionManager(new Config(), true);
|
|
$now = time();
|
|
$this->session->set('user_id', 1);
|
|
$this->session->set('role_id', 2);
|
|
$this->session->set('logged_in_at', $now - 100);
|
|
$this->session->set('last_activity', $now - 50);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach ($this->touchedKeys as $key) {
|
|
putenv($key);
|
|
}
|
|
$this->touchedKeys = [];
|
|
}
|
|
|
|
private function setEnv(string $key, string $value): void
|
|
{
|
|
$this->touchedKeys[] = $key;
|
|
putenv($key . '=' . $value);
|
|
}
|
|
|
|
private function permittedDb(): FakeDatabase
|
|
{
|
|
$db = new FakeDatabase();
|
|
$db->guardUserRow = ['is_active' => 1];
|
|
$db->userDisplayRow = ['first_name' => 'Manon', 'last_name' => 'G', 'role_label' => 'Manager'];
|
|
$db->canResult = true;
|
|
$db->permissionCodes = ['stats.read'];
|
|
|
|
return $db;
|
|
}
|
|
|
|
private function controller(FakeDatabase $db): TestStatsController
|
|
{
|
|
$request = new Request('GET', '/admin/stats', [], [], '', '203.0.113.5');
|
|
|
|
return new TestStatsController($request, new Config(), new Database(new Config()), $this->session, $db);
|
|
}
|
|
|
|
public function testRequiresStatsRead(): void
|
|
{
|
|
$db = $this->permittedDb();
|
|
$db->canResult = false;
|
|
|
|
self::assertSame(403, $this->controller($db)->index()->status());
|
|
}
|
|
|
|
public function testRendersCatalogueCountsAndStockAlerts(): void
|
|
{
|
|
$db = $this->permittedDb();
|
|
$response = $this->controller($db)->index();
|
|
|
|
self::assertSame(200, $response->status());
|
|
$body = $response->body();
|
|
self::assertStringContainsString('Statistiques', $body);
|
|
self::assertStringContainsString('53', $body); // compteur produits
|
|
self::assertStringContainsString('Cheddar', $body); // alerte stock critique
|
|
self::assertStringContainsString('critical', $body); // bande
|
|
self::assertStringContainsString('Ventes', $body); // section KPIs vente
|
|
self::assertStringContainsString('CA encaisse', $body);
|
|
self::assertStringContainsString('208,00 EUR', $body); // revenue_cents 20800 formate
|
|
}
|
|
}
|