185 lines
5.7 KiB
PHP
185 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace BattleForge\Tests\Unit\Http;
|
|
|
|
use BattleForge\Application\TurnToken;
|
|
use BattleForge\Domain\Archetype;
|
|
use BattleForge\Domain\Battlefield;
|
|
use BattleForge\Domain\MatchState;
|
|
use BattleForge\Domain\Position;
|
|
use BattleForge\Domain\UnitState;
|
|
use BattleForge\Http\CsrfToken;
|
|
use BattleForge\Http\MatchActionSupport;
|
|
use BattleForge\Http\Request;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class MatchActionSupportTest extends TestCase
|
|
{
|
|
private const SECRET = 'support-test-secret';
|
|
|
|
public function testItRejectsARequestWithAMissingCsrfToken(): void
|
|
{
|
|
$support = new MatchActionSupport(self::SECRET);
|
|
$request = $this->buildRequest(
|
|
match: $this->validMatchArray(),
|
|
csrf: '',
|
|
turnToken: 'irrelevant',
|
|
);
|
|
|
|
$result = $support->verify($request);
|
|
|
|
self::assertNotNull($result->response);
|
|
self::assertSame(403, $result->response->status);
|
|
self::assertNull($result->matchIdAndMatch);
|
|
}
|
|
|
|
public function testItRejectsARequestWithABadTurnToken(): void
|
|
{
|
|
[$csrf, $cookie] = CsrfToken::issue(self::SECRET);
|
|
$match = $this->validMatch();
|
|
$matchId = '0123456789abcdef';
|
|
$badToken = TurnToken::issue(self::SECRET, $matchId, 'bravo', 1, 0);
|
|
|
|
$support = new MatchActionSupport(self::SECRET);
|
|
$request = $this->buildRequest(
|
|
match: $this->validMatchArray(),
|
|
csrf: $csrf,
|
|
turnToken: $badToken,
|
|
cookies: ['__csrf' => $cookie],
|
|
);
|
|
|
|
$result = $support->verify($request);
|
|
|
|
self::assertNotNull($result->response);
|
|
self::assertSame(409, $result->response->status);
|
|
$body = json_decode($result->response->body, true);
|
|
self::assertSame('turn', $body['error'] ?? null);
|
|
}
|
|
|
|
public function testItRejectsAMalformedMatchState(): void
|
|
{
|
|
[$csrf, $cookie] = CsrfToken::issue(self::SECRET);
|
|
$support = new MatchActionSupport(self::SECRET);
|
|
$request = $this->buildRequest(
|
|
match: ['this' => 'is not a match'],
|
|
csrf: $csrf,
|
|
turnToken: 'whatever',
|
|
cookies: ['__csrf' => $cookie],
|
|
);
|
|
|
|
$result = $support->verify($request);
|
|
|
|
self::assertNotNull($result->response);
|
|
self::assertSame(400, $result->response->status);
|
|
}
|
|
|
|
public function testItReturnsTheValidatedPairForAValidRequest(): void
|
|
{
|
|
$match = $this->validMatch();
|
|
$matchArray = $this->validMatchArray();
|
|
$matchId = '0123456789abcdef';
|
|
$token = TurnToken::issue(
|
|
self::SECRET,
|
|
$matchId,
|
|
$match->activeTeamId,
|
|
$match->round,
|
|
count($match->actionLog)
|
|
);
|
|
[$csrf, $cookie] = CsrfToken::issue(self::SECRET);
|
|
|
|
$support = new MatchActionSupport(self::SECRET);
|
|
$body = json_encode(['matchId' => $matchId, 'match' => $matchArray], JSON_THROW_ON_ERROR);
|
|
$request = new Request(
|
|
[],
|
|
[],
|
|
[],
|
|
['__csrf' => $cookie],
|
|
[
|
|
'HTTP_X_CSRF_TOKEN' => $csrf,
|
|
'HTTP_X_TURN_TOKEN' => $token,
|
|
'__csrf_secret' => self::SECRET,
|
|
'CONTENT_TYPE' => 'application/json',
|
|
],
|
|
'',
|
|
'POST',
|
|
'/matches/current/move',
|
|
'application/json',
|
|
$body
|
|
);
|
|
|
|
$result = $support->verify($request);
|
|
|
|
self::assertNull($result->response);
|
|
self::assertNotNull($result->matchIdAndMatch);
|
|
self::assertSame($matchId, $result->matchIdAndMatch['matchId']);
|
|
self::assertInstanceOf(MatchState::class, $result->matchIdAndMatch['match']);
|
|
}
|
|
|
|
private function validMatch(): MatchState
|
|
{
|
|
return new MatchState(
|
|
new Battlefield(8, 8),
|
|
[
|
|
$this->makeUnit('alpha-1', 'alpha', 0, 0, Archetype::Defender),
|
|
$this->makeUnit('alpha-2', 'alpha', 1, 0, Archetype::Defender),
|
|
$this->makeUnit('alpha-3', 'alpha', 2, 0, Archetype::Defender),
|
|
$this->makeUnit('bravo-1', 'bravo', 7, 7, Archetype::Striker),
|
|
$this->makeUnit('bravo-2', 'bravo', 6, 7, Archetype::Striker),
|
|
$this->makeUnit('bravo-3', 'bravo', 5, 7, Archetype::Striker),
|
|
],
|
|
'alpha',
|
|
);
|
|
}
|
|
|
|
private function makeUnit(string $id, string $teamId, int $x, int $y, Archetype $archetype): UnitState
|
|
{
|
|
return new UnitState(
|
|
$id,
|
|
$teamId,
|
|
new Position($x, $y),
|
|
10,
|
|
10,
|
|
3,
|
|
3,
|
|
2,
|
|
2,
|
|
false,
|
|
$archetype,
|
|
);
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function validMatchArray(): array
|
|
{
|
|
return \BattleForge\Application\ScenarioSerializer::matchToArray($this->validMatch());
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $match
|
|
* @param array<string, string> $cookies
|
|
*/
|
|
private function buildRequest(array $match, string $csrf, string $turnToken, array $cookies = []): Request
|
|
{
|
|
$body = json_encode(['matchId' => '0123456789abcdef', 'match' => $match], JSON_THROW_ON_ERROR);
|
|
return new Request(
|
|
[],
|
|
[],
|
|
[],
|
|
$cookies,
|
|
[
|
|
'HTTP_X_CSRF_TOKEN' => $csrf,
|
|
'HTTP_X_TURN_TOKEN' => $turnToken,
|
|
'__csrf_secret' => self::SECRET,
|
|
'CONTENT_TYPE' => 'application/json',
|
|
],
|
|
'',
|
|
'POST',
|
|
'/matches/current/move',
|
|
'application/json',
|
|
$body
|
|
);
|
|
}
|
|
}
|