tmpDir = sys_get_temp_dir() . '/bf-bundled-one-' . bin2hex(random_bytes(4)); mkdir($this->tmpDir, 0700, true); file_put_contents( $this->tmpDir . '/manifest.json', json_encode(['valid' => ['name' => 'Valid', 'summary' => 'OK']], JSON_THROW_ON_ERROR), ); $this->writeValidFixture(); } protected function tearDown(): void { if (is_dir($this->tmpDir)) { foreach (glob($this->tmpDir . '/*') ?: [] as $file) { unlink($file); } rmdir($this->tmpDir); } } public function testReturnsTheScenarioJsonForAKnownId(): void { $handler = new GetBundledScenario($this->tmpDir); $response = $handler->handle( new Request([], [], [], [], [], '', 'GET', '/scenarios/bundled/valid', null, ''), ['id' => 'valid'], ); self::assertSame(200, $response->status); $body = json_decode($response->body, true); self::assertSame('valid', $body['id'] ?? null); self::assertSame('Valid', $body['name'] ?? null); self::assertSame(8, $body['battlefield']['width'] ?? null); } public function testReturns404ForAnUnknownId(): void { $handler = new GetBundledScenario($this->tmpDir); $response = $handler->handle( new Request([], [], [], [], [], '', 'GET', '/scenarios/bundled/unknown', null, ''), ['id' => 'unknown'], ); self::assertSame(404, $response->status); } public function testReturns404ForAnIdThatFailsTheRegex(): void { $handler = new GetBundledScenario($this->tmpDir); $response = $handler->handle( new Request([], [], [], [], [], '', 'GET', '/scenarios/bundled/..', null, ''), ['id' => '..'], ); self::assertSame(404, $response->status); } public function testReturns500WhenTheFixtureFailsValidation(): void { $bad = [ 'id' => 'broken', 'name' => 'Broken', 'battlefield' => ['width' => 8, 'height' => 8, 'terrain' => []], 'units' => [], 'deploymentZones' => [], 'objectives' => [], 'victoryCondition' => 'eliminate_all', 'holdRoundsRequired' => 1, ]; file_put_contents($this->tmpDir . '/broken.json', json_encode($bad, JSON_THROW_ON_ERROR)); file_put_contents( $this->tmpDir . '/manifest.json', json_encode([ 'valid' => ['name' => 'Valid', 'summary' => 'OK'], 'broken' => ['name' => 'Broken', 'summary' => 'Bad'], ], JSON_THROW_ON_ERROR), ); $handler = new GetBundledScenario($this->tmpDir); $response = $handler->handle( new Request([], [], [], [], [], '', 'GET', '/scenarios/bundled/broken', null, ''), ['id' => 'broken'], ); self::assertSame(500, $response->status); $body = json_decode($response->body, true); self::assertSame('fixture invalid', $body['error'] ?? null); self::assertIsArray($body['details'] ?? null); self::assertNotEmpty($body['details']); } private function writeValidFixture(): void { $fixture = [ 'id' => 'valid', 'name' => 'Valid', 'battlefield' => ['width' => 8, 'height' => 8, 'terrain' => []], 'units' => [ $this->unit('a1', 'alpha', 0, 0), $this->unit('a2', 'alpha', 1, 0), $this->unit('a3', 'alpha', 2, 0), $this->unit('b1', 'bravo', 7, 7), $this->unit('b2', 'bravo', 6, 7), $this->unit('b3', 'bravo', 5, 7), ], 'deploymentZones' => [ 'alpha' => $this->deploymentZone('alpha', [[0, 0], [1, 0], [2, 0]]), 'bravo' => $this->deploymentZone('bravo', [[7, 7], [6, 7], [5, 7]]), ], 'objectives' => [], 'victoryCondition' => 'eliminate_all', 'holdRoundsRequired' => 1, ]; file_put_contents($this->tmpDir . '/valid.json', json_encode($fixture, JSON_THROW_ON_ERROR)); } /** @return array */ private function unit(string $id, string $teamId, int $x, int $y): array { return [ 'id' => $id, 'teamId' => $teamId, 'position' => ['x' => $x, 'y' => $y], 'maxHealth' => 10, 'health' => 10, 'attack' => 3, 'defense' => 3, 'speed' => 2, 'archetype' => 'defender', 'abilities' => [], ]; } /** * @param list $coords * @return array */ private function deploymentZone(string $teamId, array $coords): array { $positions = []; foreach ($coords as $coord) { $positions[] = ['x' => $coord[0], 'y' => $coord[1]]; } return ['teamId' => $teamId, 'positions' => $positions]; } }