buildRequest(rawBody: '{}', csrfHeader: ''); $response = $handler->handle($request, ['id' => 'demo']); self::assertSame(403, $response->status); self::assertStringContainsString('csrf', $response->body); } public function testItAcceptsAValidBattlefieldJsonAndReturnsOk(): void { [$token, $cookie] = CsrfToken::issue(self::SECRET); $handler = new PostBattlefieldEditor(); $request = $this->buildRequest( rawBody: json_encode($this->validBattlefield(), JSON_THROW_ON_ERROR), csrfHeader: $token, cookies: ['__csrf' => $cookie], server: ['__csrf_secret' => self::SECRET], ); $response = $handler->handle($request, ['id' => 'demo']); self::assertSame(200, $response->status); $body = json_decode($response->body, true); self::assertSame(true, $body['ok'] ?? null); self::assertSame('demo', $body['scenario']['id'] ?? null); } public function testItReturns400OnAnInvalidShape(): void { [$token, $cookie] = CsrfToken::issue(self::SECRET); $handler = new PostBattlefieldEditor(); $request = $this->buildRequest( rawBody: json_encode(['this' => 'is not a valid scenario'], JSON_THROW_ON_ERROR), csrfHeader: $token, cookies: ['__csrf' => $cookie], server: ['__csrf_secret' => self::SECRET], ); $response = $handler->handle($request, ['id' => 'demo']); self::assertSame(400, $response->status); $body = json_decode($response->body, true); self::assertSame(false, $body['ok'] ?? null); self::assertNotEmpty($body['errors'] ?? []); } /** * @param array $cookies * @param array $server */ private function buildRequest(string $rawBody, string $csrfHeader, array $cookies = [], array $server = []): Request { $server['HTTP_X_CSRF_TOKEN'] = $csrfHeader; $server['CONTENT_TYPE'] = 'application/json'; return new Request([], [], [], $cookies, $server, '', 'POST', '/scenarios/demo/edit/battlefield', 'application/json', $rawBody); } /** @return array */ private function validBattlefield(): 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, ]; } }