diff --git a/public/index.php b/public/index.php index c4d07cc..a2bcea4 100644 --- a/public/index.php +++ b/public/index.php @@ -70,11 +70,13 @@ if ($contentType !== null) { } // 5. Read the raw body for fetch POSTs that submit JSON. -$rawBody = (string) file_get_contents('php://input'); +$rawBody = (string) ($_SERVER['__raw_body'] ?? file_get_contents('php://input')); // 6. Build a Request with the request data, the CSRF secret, and the upload token. $server = $_SERVER; -$server['__csrf_secret'] = $secret; +if (!isset($server['__csrf_secret'])) { + $server['__csrf_secret'] = $secret; +} $server['__uploads_token'] = $uploadsToken; $request = new Request( @@ -123,3 +125,5 @@ foreach ($response->headers as $name => $value) { header($name . ': ' . $value); } echo $response->body; + +return $response->status; diff --git a/tests/Integration/FullFlowTest.php b/tests/Integration/FullFlowTest.php new file mode 100644 index 0000000..133ea2a --- /dev/null +++ b/tests/Integration/FullFlowTest.php @@ -0,0 +1,163 @@ +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; + } +}