Compare commits

...

2 commits

8 changed files with 129 additions and 89 deletions

View file

@ -0,0 +1,29 @@
<?php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Drop the unused distribution_mode column from scenario (#61).
*/
final class Version20260627065959 extends AbstractMigration
{
public function getDescription(): string
{
return 'Drop the unused distribution_mode column from scenario (no consumer; #61).';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE scenario DROP distribution_mode');
}
public function down(Schema $schema): void
{
// Assumes an empty table (the column was always empty/unused); the
// NOT NULL re-add would fail on any pre-existing populated rows.
$this->addSql('ALTER TABLE scenario ADD distribution_mode VARCHAR(255) NOT NULL');
}
}

View file

@ -6,7 +6,6 @@ use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use App\Enum\DistributionMode;
use App\Repository\ScenarioRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
@ -33,9 +32,6 @@ class Scenario
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private User $owner;
#[ORM\Column(enumType: DistributionMode::class)]
private DistributionMode $distributionMode = DistributionMode::EVEN;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
private string $name;
@ -58,11 +54,6 @@ class Scenario
return $this->owner;
}
public function getDistributionMode(): DistributionMode
{
return $this->distributionMode;
}
public function getName(): string
{
return $this->name;
@ -80,13 +71,6 @@ class Scenario
return $this;
}
public function setDistributionMode(DistributionMode $distributionMode): static
{
$this->distributionMode = $distributionMode;
return $this;
}
public function setName(string $name): static
{
$this->name = $name;

View file

@ -1,9 +0,0 @@
<?php
namespace App\Enum;
enum DistributionMode: string
{
case EVEN = 'even';
case PRIORITY = 'priority';
}

View file

@ -3,7 +3,6 @@
namespace App\Tests\Entity;
use App\Entity\Scenario;
use App\Enum\DistributionMode;
use App\Tests\Concerns\CreatesUsers;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
@ -42,58 +41,6 @@ final class ScenarioPersistenceTest extends KernelTestCase
self::assertSame('Household Budget', $reloaded->getName());
}
public function testDistributionModeDefaultsToEvenWhenNotExplicitlySet(): void
{
$scenario = new Scenario();
$scenario->setName('Default Mode Scenario');
$scenario->setOwner($this->persistOwner());
$this->em->persist($scenario);
$this->em->flush();
$id = $scenario->getId();
$this->em->clear();
$reloaded = $this->em->find(Scenario::class, $id);
self::assertNotNull($reloaded);
self::assertSame(
DistributionMode::EVEN,
$reloaded->getDistributionMode(),
'distribution_mode must default to EVEN when not explicitly set.',
);
}
public function testItRoundTripsAnExplicitlySetPriorityDistributionMode(): void
{
$scenario = new Scenario();
$scenario->setName('Priority Mode Scenario');
$scenario->setDistributionMode(DistributionMode::PRIORITY);
$scenario->setOwner($this->persistOwner());
$this->em->persist($scenario);
$this->em->flush();
$id = $scenario->getId();
$this->em->clear();
$reloaded = $this->em->find(Scenario::class, $id);
self::assertNotNull($reloaded);
self::assertInstanceOf(
DistributionMode::class,
$reloaded->getDistributionMode(),
'distribution_mode must hydrate as a DistributionMode enum instance, not a raw string.',
);
self::assertSame(
DistributionMode::PRIORITY,
$reloaded->getDistributionMode(),
'An explicitly set PRIORITY distribution_mode must survive a flush + clear + reload round-trip.',
);
}
public function testDescriptionRoundTripsWhenSet(): void
{
$scenario = new Scenario();

View file

@ -3,7 +3,6 @@
namespace App\Tests\Enum;
use App\Enum\BucketAllocationType;
use App\Enum\DistributionMode;
use App\Enum\StreamFrequency;
use App\Enum\StreamType;
use PHPUnit\Framework\TestCase;
@ -22,9 +21,6 @@ final class RemainingEnumsTest extends TestCase
$this->assertSame('income', StreamType::INCOME->value);
$this->assertSame('expense', StreamType::EXPENSE->value);
$this->assertSame('even', DistributionMode::EVEN->value);
$this->assertSame('priority', DistributionMode::PRIORITY->value);
}
public function testBucketAllocationTypeBackingValues(): void

View file

@ -212,6 +212,103 @@ final class ScenarioApiTest extends WebTestCase
);
}
public function testPostThenGetOnReturnedIriRoundTripsForTheOwner(): void
{
$this->login();
$this->client->request(
'POST',
'/api/scenarios',
server: [
'CONTENT_TYPE' => 'application/ld+json',
'HTTP_ACCEPT' => 'application/ld+json',
],
content: json_encode(['name' => 'New Scenario From UI']),
);
$created = json_decode((string) $this->client->getResponse()->getContent(), true);
self::assertIsArray($created, 'The created resource response must be a JSON-LD object.');
self::assertArrayHasKey(
'@id',
$created,
'POST must return the created scenario IRI so the SPA can redirect to its show page.',
);
$iri = $created['@id'];
$this->client->request('GET', $iri, server: [
'HTTP_ACCEPT' => 'application/ld+json',
]);
$response = $this->client->getResponse();
self::assertSame(
200,
$response->getStatusCode(),
'A GET on the just-created IRI by the same owner must return 200 — the create->redirect->show flow must never land on an owner-scope 404.',
);
$body = json_decode((string) $response->getContent(), true);
self::assertIsArray($body, 'The item response must be a JSON-LD object.');
self::assertSame(
$iri,
$body['@id'] ?? null,
'The show page must resolve the same IRI the create response returned.',
);
self::assertSame('New Scenario From UI', $body['name'] ?? null);
}
public function testPostWithDescriptionPersistsAndEchoesIt(): void
{
$this->login();
$this->client->request(
'POST',
'/api/scenarios',
server: [
'CONTENT_TYPE' => 'application/ld+json',
'HTTP_ACCEPT' => 'application/ld+json',
],
content: json_encode([
'name' => 'Documented Fund',
'description' => 'Money set aside for the kitchen renovation.',
]),
);
$response = $this->client->getResponse();
self::assertSame(
201,
$response->getStatusCode(),
'POST /api/scenarios with name + optional description must return 201 Created.',
);
$body = json_decode((string) $response->getContent(), true);
self::assertIsArray($body, 'The created resource response must be a JSON-LD object.');
self::assertSame(
'Money set aside for the kitchen renovation.',
$body['description'] ?? null,
'The create response must echo the supplied optional description back to the SPA.',
);
$this->em->clear();
$persisted = $this->em->getRepository(Scenario::class)->findOneBy(['name' => 'Documented Fund']);
self::assertNotNull(
$persisted,
'A successful POST with a description must persist the new Scenario.',
);
self::assertSame(
'Money set aside for the kitchen renovation.',
$persisted->getDescription(),
'The optional description must survive persistence.',
);
}
public function testPostWithMissingNameIsRejectedAsUnprocessable(): void
{
$this->login();

View file

@ -6,7 +6,6 @@ use App\Entity\Bucket;
use App\Entity\Scenario;
use App\Enum\BucketAllocationType;
use App\Enum\BucketType;
use App\Enum\DistributionMode;
use App\Service\Allocation\AllocateIncome;
use App\Service\Allocation\Result;
use Doctrine\Common\Collections\ArrayCollection;
@ -14,7 +13,7 @@ use PHPUnit\Framework\TestCase;
/**
* Tests for the redesigned allocation engine single strategy, no distribution
* modes. `preview()` no longer accepts a `DistributionMode` parameter at all
* modes. `preview()` does not accept any distribution-mode parameter
* Model C's even-split-on-tie behaviour is intrinsic to the engine, not a mode.
*
* THE MODEL (type-phased macro-order, priority tiers within each phase):
@ -476,8 +475,7 @@ final class AllocateIncomeTest extends TestCase
private function makeScenario(): Scenario
{
return (new Scenario())
->setName('Household Budget')
->setDistributionMode(DistributionMode::PRIORITY);
->setName('Household Budget');
}
private function makeFixedLimitBucket(

View file

@ -6,7 +6,6 @@ use App\Entity\Bucket;
use App\Entity\Scenario;
use App\Enum\BucketAllocationType;
use App\Enum\BucketType;
use App\Enum\DistributionMode;
use App\Service\Allocation\Result;
use PHPUnit\Framework\TestCase;
@ -78,8 +77,7 @@ final class ResultTest extends TestCase
private function makeScenario(): Scenario
{
return (new Scenario())
->setName('Household Budget')
->setDistributionMode(DistributionMode::PRIORITY);
->setName('Household Budget');
}
private function makeBucket(Scenario $scenario, int $priority, string $name): Bucket