buildRequest('{}', ''); $response = $handler->handle($request, ['id' => 'demo']); self::assertSame(403, $response->status); } public function testItAcceptsAValidScenarioAndReturnsTheInitialMatch(): void { [$token, $cookie] = CsrfToken::issue(self::SECRET); $handler = new PostStartMatch(); $request = $this->buildRequest( rawBody: json_encode($this->validScenario(), JSON_THROW_ON_ERROR), csrfHeader: $token, cookies: ['__csrf' => $cookie, '__csrf_token' => $token], ); $response = $handler->handle($request, ['id' => 'demo']); self::assertSame(200, $response->status); $body = json_decode($response->body, true); self::assertSame('alpha', $body['match']['activeTeamId'] ?? null); self::assertSame(1, $body['match']['round'] ?? null); self::assertCount(6, $body['match']['units'] ?? []); self::assertIsString($body['matchId'] ?? null); self::assertMatchesRegularExpression('/^[a-f0-9]{16,}$/', $body['matchId']); self::assertIsString($body['turnToken'] ?? null); self::assertMatchesRegularExpression('/^[a-f0-9]{32}$/', $body['turnToken']); } public function testItReturns400OnAnInvalidScenario(): void { [$token, $cookie] = CsrfToken::issue(self::SECRET); $handler = new PostStartMatch(); $request = $this->buildRequest( rawBody: json_encode(['this' => 'is not a valid scenario'], JSON_THROW_ON_ERROR), csrfHeader: $token, cookies: ['__csrf' => $cookie, '__csrf_token' => $token], ); $response = $handler->handle($request, ['id' => 'demo']); self::assertSame(400, $response->status); } public function testItReturnsATurnTokenValidForTheInitialMatchState(): void { [$token, $cookie] = CsrfToken::issue(self::SECRET); $handler = new PostStartMatch(); $request = $this->buildRequest( rawBody: json_encode($this->validScenario(), JSON_THROW_ON_ERROR), csrfHeader: $token, cookies: ['__csrf' => $cookie, '__csrf_token' => $token], ); $response = $handler->handle($request, ['id' => 'demo']); self::assertSame(200, $response->status); $body = json_decode($response->body, true); $matchId = $body['matchId']; $turnToken = $body['turnToken']; $lastActionIndex = count($body['match']['actionLog'] ?? []); self::assertTrue(\BattleForge\Application\TurnToken::verify( self::SECRET, $matchId, $body['match']['activeTeamId'], $body['match']['round'], $lastActionIndex, $turnToken, )); } /** @param array $cookies */ private function buildRequest(string $rawBody, string $csrfHeader, array $cookies = []): Request { $server = [ 'HTTP_X_CSRF_TOKEN' => $csrfHeader, '__csrf_secret' => self::SECRET, 'CONTENT_TYPE' => 'application/json', ]; return new Request([], [], [], $cookies, $server, '', 'POST', '/scenarios/demo/start', 'application/json', $rawBody); } /** @return array */ private function validScenario(): array { return [ 'id' => 'demo', 'name' => 'Demo', 'battlefieldWidth' => 8, 'battlefieldHeight' => 8, 'battlefieldTerrain' => [], 'teamA' => [ 'units' => [ ['id' => 'a1', 'archetype' => 'defender', 'maxHealth' => 12, 'attack' => 3, 'defense' => 4, 'speed' => 2, 'x' => 0, 'y' => 0], ['id' => 'a2', 'archetype' => 'defender', 'maxHealth' => 12, 'attack' => 3, 'defense' => 4, 'speed' => 2, 'x' => 1, 'y' => 0], ['id' => 'a3', 'archetype' => 'defender', 'maxHealth' => 12, 'attack' => 3, 'defense' => 4, 'speed' => 2, 'x' => 2, 'y' => 0], ], ], 'teamB' => [ 'units' => [ ['id' => 'b1', 'archetype' => 'striker', 'maxHealth' => 8, 'attack' => 5, 'defense' => 2, 'speed' => 3, 'x' => 7, 'y' => 7], ['id' => 'b2', 'archetype' => 'striker', 'maxHealth' => 8, 'attack' => 5, 'defense' => 2, 'speed' => 3, 'x' => 6, 'y' => 7], ['id' => 'b3', 'archetype' => 'striker', 'maxHealth' => 8, 'attack' => 5, 'defense' => 2, 'speed' => 3, 'x' => 5, 'y' => 7], ], ], 'victoryCondition' => 'eliminate_all', 'holdRoundsRequired' => 1, ]; } }