feat: add GetBundledScenario handler with fixture validation

This commit is contained in:
Keith Solomon
2026-07-25 23:47:09 -05:00
parent 1f9e76afef
commit b8451112d3
2 changed files with 233 additions and 0 deletions
@@ -0,0 +1,162 @@
<?php
declare(strict_types=1);
namespace BattleForge\Tests\Integration;
use BattleForge\Http\Handlers\GetBundledScenario;
use BattleForge\Http\Request;
use PHPUnit\Framework\TestCase;
final class GetBundledScenarioTest extends TestCase
{
private string $tmpDir;
protected function setUp(): void
{
$this->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<string, mixed> */
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<array{0: int, 1: int}> $coords
* @return array<string, mixed>
*/
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];
}
}