runFrontController(); self::assertStringContainsString('New scenario', $homeBody); self::assertStringContainsString('name="csrf-token"', $homeBody); // Step 2: POST team editor. $_COOKIE['__csrf'] = $cookie; $_SERVER['REQUEST_METHOD'] = 'POST'; $_SERVER['REQUEST_URI'] = '/scenarios/demo/edit/team'; $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; $_SERVER['__csrf_secret'] = self::SECRET; $_POST = $this->validTeamPost($token); $teamBody = $this->runFrontController(); self::assertSame(200, $this->lastStatus); self::assertStringContainsString('localStorage.setItem', $teamBody); self::assertStringContainsString('scenario:demo', $teamBody); // Step 3: POST battlefield editor. $_SERVER['REQUEST_METHOD'] = 'POST'; $_SERVER['REQUEST_URI'] = '/scenarios/demo/edit/battlefield'; $_SERVER['CONTENT_TYPE'] = 'application/json'; $_SERVER['HTTP_X_CSRF_TOKEN'] = $token; $_POST = []; $this->rawBody = json_encode($this->validBattlefieldPayload(), JSON_THROW_ON_ERROR); $_SERVER['__raw_body'] = $this->rawBody; $bfBody = $this->runFrontController(); self::assertSame(200, $this->lastStatus); $bfJson = json_decode($bfBody, true); self::assertSame(true, $bfJson['ok'] ?? null); // Step 4: POST start-match. $_SERVER['REQUEST_METHOD'] = 'POST'; $_SERVER['REQUEST_URI'] = '/scenarios/demo/start'; $this->rawBody = json_encode($this->validBattlefieldPayload(), JSON_THROW_ON_ERROR); $_SERVER['__raw_body'] = $this->rawBody; $startBody = $this->runFrontController(); self::assertSame(200, $this->lastStatus); $startJson = json_decode($startBody, true); self::assertSame('alpha', $startJson['match']['activeTeamId'] ?? null); self::assertSame(1, $startJson['match']['round'] ?? null); self::assertCount(6, $startJson['match']['units'] ?? []); } finally { if (is_dir($uploadsRoot)) { foreach (glob($uploadsRoot . '/*/*') as $file) { unlink($file); } foreach (glob($uploadsRoot . '/*') as $dir) { rmdir($dir); } rmdir($uploadsRoot); } } } private string $rawBody = ''; private int $lastStatus = 0; private function runFrontController(): string { $this->lastStatus = 0; $body = $this->captureOutput(function (): void { $this->lastStatus = (require __DIR__ . '/../../public/index.php') ?? http_response_code(); }); return $body; } /** @param callable(): void $fn */ private function captureOutput(callable $fn): string { ob_start(); try { $fn(); } finally { $output = (string) ob_get_clean(); } return $output; } /** @return array */ private function validTeamPost(string $token): array { return [ '_csrf' => $token, 'id' => 'demo', 'name' => 'Demo', 'battlefieldWidth' => '8', 'battlefieldHeight' => '8', 'teamA' => ['units' => $this->unitRows('a', 0, 2, 0)], 'teamB' => ['units' => $this->unitRows('b', 5, 7, 7)], 'victoryCondition' => 'eliminate_all', 'holdRoundsRequired' => '1', ]; } /** @return array */ private function validBattlefieldPayload(): array { return [ 'id' => 'demo', 'name' => 'Demo', 'battlefieldWidth' => 8, 'battlefieldHeight' => 8, 'battlefieldTerrain' => [], 'teamA' => ['units' => $this->unitRows('a', 0, 2, 0)], 'teamB' => ['units' => $this->unitRows('b', 5, 7, 7)], 'victoryCondition' => 'eliminate_all', 'holdRoundsRequired' => 1, ]; } /** * @return list> */ private function unitRows(string $teamPrefix, int $xStart, int $xEnd, int $y): array { $rows = []; for ($i = 0; $i <= $xEnd - $xStart; $i++) { $rows[] = [ 'id' => "{$teamPrefix}{$i}", 'archetype' => 'defender', 'maxHealth' => 12, 'attack' => 3, 'defense' => 4, 'speed' => 2, 'x' => $xStart + $i, 'y' => $y, ]; } return $rows; } }