From 952e475066e249de49896182c7baf5eaf917a2e0 Mon Sep 17 00:00:00 2001 From: Jules Date: Fri, 19 Jun 2026 13:34:25 +0200 Subject: [PATCH 1/2] add test format Add test format for devs working on future sets that are not released on BGA yet --- src/Controller/FormatController.php | 31 +++- src/Enum/DeckFormat.php | 2 + src/Validator/Format/TestFormatValidator.php | 29 ++++ tests/Controller/FormatControllerTest.php | 27 +++ .../Format/TestFormatValidatorTest.php | 163 ++++++++++++++++++ 5 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 src/Validator/Format/TestFormatValidator.php create mode 100644 tests/Validator/Format/TestFormatValidatorTest.php diff --git a/src/Controller/FormatController.php b/src/Controller/FormatController.php index 0995083..c7cc356 100644 --- a/src/Controller/FormatController.php +++ b/src/Controller/FormatController.php @@ -4,14 +4,17 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Attribute\Route; class FormatController extends AbstractController { #[Route('/api/formats', name: 'api_formats', methods: ['GET'])] - public function __invoke(): JsonResponse + public function __invoke(Request $request): JsonResponse { - return $this->json([ + $includeHidden = $request->query->getBoolean('hiddenFormats'); + + $formats = [ [ 'code' => 'sandbox', 'label' => 'Sandbox', @@ -27,6 +30,22 @@ public function __invoke(): JsonResponse 'maxCopiesPerRarity' => null, ], ], + [ + 'code' => 'test', + 'label' => 'Test', + 'hidden' => true, + 'minCards' => 4, + 'maxCards' => 100, + 'allowBanned' => true, + 'allowSuspended' => true, + 'limits' => [ + 'unique' => null, + 'rare' => null, + 'exalted' => null, + 'maxCopiesPerName' => null, + 'maxCopiesPerRarity' => null, + ], + ], [ 'code' => 'nuc', 'label' => 'NUC', @@ -92,6 +111,12 @@ public function __invoke(): JsonResponse 'maxCopiesPerRarity' => 1, ], ], - ]); + ]; + + if (!$includeHidden) { + $formats = array_values(array_filter($formats, fn (array $format) => empty($format['hidden']))); + } + + return $this->json($formats); } } diff --git a/src/Enum/DeckFormat.php b/src/Enum/DeckFormat.php index ad29a19..527b67d 100644 --- a/src/Enum/DeckFormat.php +++ b/src/Enum/DeckFormat.php @@ -9,6 +9,7 @@ enum DeckFormat: string case Singleton = 'singleton'; case SingletonNuc = 'singleton_nuc'; case Sandbox = 'sandbox'; + case Test = 'test'; public static function fromInput(string $value): self { @@ -18,6 +19,7 @@ public static function fromInput(string $value): self 'singleton' => self::Singleton, 'singleton_nuc', 'singleton_no_unique' => self::SingletonNuc, 'sandbox' => self::Sandbox, + 'test' => self::Test, default => throw new \ValueError(sprintf('"%s" is not a valid DeckFormat.', $value)), }; } diff --git a/src/Validator/Format/TestFormatValidator.php b/src/Validator/Format/TestFormatValidator.php new file mode 100644 index 0000000..302902d --- /dev/null +++ b/src/Validator/Format/TestFormatValidator.php @@ -0,0 +1,29 @@ +request('GET', '/api/formats'); + + self::assertResponseIsSuccessful(); + + $data = json_decode($client->getResponse()->getContent(), true); + $codes = array_column($data, 'code'); + + self::assertNotContains('test', $codes); + } + + public function testHiddenFormatsAreReturnedWhenRequested(): void + { + $client = static::createClient(); + $client->request('GET', '/api/formats?hiddenFormats=true'); + + self::assertResponseIsSuccessful(); + + $data = json_decode($client->getResponse()->getContent(), true); + $codes = array_column($data, 'code'); + + self::assertContains('test', $codes); + self::assertContains('sandbox', $codes); + } + public function testSandboxAllowsBannedAndSuspendedCards(): void { $client = static::createClient(); diff --git a/tests/Validator/Format/TestFormatValidatorTest.php b/tests/Validator/Format/TestFormatValidatorTest.php new file mode 100644 index 0000000..5146f4c --- /dev/null +++ b/tests/Validator/Format/TestFormatValidatorTest.php @@ -0,0 +1,163 @@ +validator = new TestFormatValidator(); + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private function card(string $ref, int $qty = 1): DeckCard + { + $card = new DeckCard(); + $card->setCardReference($ref); + $card->setQuantity($qty); + + return $card; + } + + private function deck(DeckCard ...$cards): Deck + { + $deck = $this->createStub(Deck::class); + $deck->method('getDeckCards')->willReturn(new ArrayCollection($cards)); + + return $deck; + } + + private function data( + string $ref, + string $typeRef = 'PERMANENT', + string $faction = 'AX', + string $rarityRef = 'COMMON', + string $name = '', + ): array { + return [ + 'reference' => $ref, + 'cardType' => ['reference' => $typeRef], + 'faction' => ['code' => $faction], + 'rarity' => ['reference' => $rarityRef], + 'name' => $name ?: $ref, + ]; + } + + /** + * @return array{0: array, 1: DeckCard[]} + */ + private function buildMinimalValidDeck(string $faction = 'AX'): array + { + $cardsData = []; + $deckCards = []; + + $heroRef = 'ALT_CORE_B_AX_0_C'; + $deckCards[] = $this->card($heroRef); + $cardsData[$heroRef] = $this->data($heroRef, 'HERO_MAIN', $faction); + + for ($i = 1; $i <= 4; ++$i) { + $ref = sprintf('ALT_CORE_B_%s_%d_C', $faction, $i); + $deckCards[] = $this->card($ref); + $cardsData[$ref] = $this->data($ref, 'PERMANENT', $faction); + } + + return [$cardsData, $deckCards]; + } + + public function testGetFormatIsTest(): void + { + self::assertSame('test', $this->validator->getFormat()); + } + + public function testValidDeckHasNoErrors(): void + { + [$cardsData, $deckCards] = $this->buildMinimalValidDeck(); + + $errors = $this->validator->validate($this->deck(...$deckCards), $cardsData); + + self::assertSame([], $errors); + } + + // ── Sets 6 and 7 (FUGUE, EOLE) are allowed ──────────────────────────────── + + public function testFugueSetIsAllowed(): void + { + [$cardsData, $deckCards] = $this->buildMinimalValidDeck(); + + $ref = 'ALT_FUGUE_B_AX_1_C'; + $deckCards[] = $this->card($ref); + $cardsData[$ref] = $this->data($ref, 'PERMANENT', 'AX', 'COMMON', 'Fugue Card'); + + $errors = $this->validator->validate($this->deck(...$deckCards), $cardsData); + + self::assertEmpty(array_filter($errors, fn ($e) => str_contains($e, 'FUGUE'))); + } + + public function testEoleSetIsAllowed(): void + { + [$cardsData, $deckCards] = $this->buildMinimalValidDeck(); + + $ref = 'ALT_EOLE_B_AX_1_C'; + $deckCards[] = $this->card($ref); + $cardsData[$ref] = $this->data($ref, 'PERMANENT', 'AX', 'COMMON', 'Eole Card'); + + $errors = $this->validator->validate($this->deck(...$deckCards), $cardsData); + + self::assertEmpty(array_filter($errors, fn ($e) => str_contains($e, 'EOLE'))); + } + + public function testLegalityDetailReportsSetsAsLegalForForbiddenSets(): void + { + [$cardsData, $deckCards] = $this->buildMinimalValidDeck(); + + $ref = 'ALT_FUGUE_B_AX_1_C'; + $deckCards[] = $this->card($ref); + $cardsData[$ref] = $this->data($ref, 'PERMANENT', 'AX', 'COMMON', 'Fugue Card'); + + $detail = $this->validator->computeLegalityDetail($this->deck(...$deckCards), $cardsData); + + self::assertTrue($detail['sets']); + } + + // ── Same permissive rules as Sandbox ────────────────────────────────────── + + public function testBannedAndSuspendedCardsAreAllowed(): void + { + [$cardsData, $deckCards] = $this->buildMinimalValidDeck(); + + $ref = 'ALT_CORE_B_AX_1_C'; + $cardsData[$ref] = array_merge($cardsData[$ref], ['isBanned' => true, 'isSuspended' => true]); + + $errors = $this->validator->validate($this->deck(...$deckCards), $cardsData); + + self::assertEmpty(array_filter($errors, fn ($e) => str_contains($e, 'banned') || str_contains($e, 'suspended'))); + } + + public function testMultipleFactionsDeckHasNoErrors(): void + { + $heroRef = 'ALT_CORE_B_AX_0_C'; + $cardsData = [ + $heroRef => $this->data($heroRef, 'HERO_MAIN', 'AX'), + ]; + $deckCards = [$this->card($heroRef)]; + + foreach (['AX', 'LY', 'MU', 'OR', 'YZ'] as $i => $faction) { + $ref = sprintf('ALT_CORE_B_%s_%d_C', $faction, $i + 1); + $deckCards[] = $this->card($ref); + $cardsData[$ref] = $this->data($ref, 'PERMANENT', $faction); + } + + $errors = $this->validator->validate($this->deck(...$deckCards), $cardsData); + + self::assertSame([], $errors); + } +} From 1df0fb04e758e441bcd41b21d11d563f357a6411 Mon Sep 17 00:00:00 2001 From: Jules Date: Fri, 19 Jun 2026 13:45:07 +0200 Subject: [PATCH 2/2] format de test accessible via BGA --- src/Controller/BgaDeckController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controller/BgaDeckController.php b/src/Controller/BgaDeckController.php index 6c42c31..f825816 100644 --- a/src/Controller/BgaDeckController.php +++ b/src/Controller/BgaDeckController.php @@ -16,7 +16,7 @@ class BgaDeckController extends AbstractController { - private const BGA_VALID_FORMATS = ['standard', 'nuc', 'singleton_nuc', 'sandbox']; + private const BGA_VALID_FORMATS = ['standard', 'nuc', 'singleton_nuc', 'sandbox', 'test']; public function __construct( private readonly DeckRepository $deckRepository, @@ -43,6 +43,7 @@ public function collection(Request $request): JsonResponse 'SANDBOX' => 'sandbox', 'SINGLETON' => 'singleton', 'SINGLETON_NUC' => 'singleton_nuc', + 'TEST' => 'test', default => '', };