Files
Inventory/tests/Api/Controller/EntityVersionTest.php
Matthieu 9299a46c8b feat(versioning) : add entity versioning with numbered versions and restore
Backend:
- Migration: version column on audit_logs and machines
- AuditLog, Machine, Composant, Piece, Product: version + skipAudit properties
- AbstractAuditSubscriber: auto-increment version, skip on restore, fix decimal diff
- Enriched snapshots with slots, custom fields and version number
- AuditLogRepository: findVersionHistory, findByVersion
- EntityVersionService: list, preview, restore with skeleton/integrity checks
- EntityVersionController: REST endpoints for all 4 entity types
- 11 tests covering list, preview, restore, auth

Frontend: update submodule pointer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:56 +01:00

175 lines
6.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Api\Controller;
use App\Tests\AbstractApiTestCase;
/**
* @internal
*/
class EntityVersionTest extends AbstractApiTestCase
{
// ── Versions list ───────────────────────────────────────────────
public function testMachineVersionsAfterCreateAndUpdate(): void
{
$machine = $this->createMachine('Machine V');
$gClient = $this->createGestionnaireClient();
$gClient->request('PATCH', self::iri('machines', $machine->getId()), [
'headers' => ['Content-Type' => 'application/merge-patch+json'],
'json' => ['name' => 'Machine V Updated'],
]);
$this->assertResponseIsSuccessful();
$vClient = $this->createViewerClient();
$vClient->request('GET', sprintf('/api/machines/%s/versions', $machine->getId()));
$this->assertResponseIsSuccessful();
$data = $vClient->getResponse()->toArray();
$this->assertArrayHasKey('items', $data);
$this->assertArrayHasKey('total', $data);
$this->assertGreaterThanOrEqual(1, $data['total']);
$firstItem = $data['items'][0];
$this->assertArrayHasKey('version', $firstItem);
$this->assertArrayHasKey('action', $firstItem);
$this->assertArrayHasKey('createdAt', $firstItem);
}
public function testComposantVersionsList(): void
{
$composant = $this->createComposant('Composant V');
$client = $this->createViewerClient();
$client->request('GET', sprintf('/api/composants/%s/versions', $composant->getId()));
$this->assertResponseIsSuccessful();
$data = $client->getResponse()->toArray();
$this->assertArrayHasKey('items', $data);
}
public function testPieceVersionsList(): void
{
$piece = $this->createPiece('Piece V');
$client = $this->createViewerClient();
$client->request('GET', sprintf('/api/pieces/%s/versions', $piece->getId()));
$this->assertResponseIsSuccessful();
}
public function testProductVersionsList(): void
{
$product = $this->createProduct('Product V');
$client = $this->createViewerClient();
$client->request('GET', sprintf('/api/products/%s/versions', $product->getId()));
$this->assertResponseIsSuccessful();
}
public function testVersionsNotFound(): void
{
$client = $this->createViewerClient();
$client->request('GET', '/api/machines/nonexistent-id/versions');
$this->assertResponseStatusCodeSame(404);
}
public function testVersionsUnauthenticated(): void
{
$machine = $this->createMachine('Machine V');
$client = $this->createUnauthenticatedClient();
$client->request('GET', sprintf('/api/machines/%s/versions', $machine->getId()));
$this->assertResponseStatusCodeSame(401);
}
// ── Preview ─────────────────────────────────────────────────────
public function testPreviewRequiresGestionnaire(): void
{
$machine = $this->createMachine('Machine P');
$client = $this->createViewerClient();
$client->request('GET', sprintf('/api/machines/%s/versions/1/preview', $machine->getId()));
$this->assertResponseStatusCodeSame(403);
}
public function testPreviewReturnsRestoreInfo(): void
{
$composant = $this->createComposant('Composant P');
// Update to create version 2
$gClient = $this->createGestionnaireClient();
$gClient->request('PATCH', self::iri('composants', $composant->getId()), [
'headers' => ['Content-Type' => 'application/merge-patch+json'],
'json' => ['name' => 'Composant P Updated'],
]);
$this->assertResponseIsSuccessful();
// Preview restore to version 1
$gClient2 = $this->createGestionnaireClient();
$gClient2->request('GET', sprintf('/api/composants/%s/versions/1/preview', $composant->getId()));
$this->assertResponseIsSuccessful();
$data = $gClient2->getResponse()->toArray();
$this->assertArrayHasKey('version', $data);
$this->assertArrayHasKey('restoreMode', $data);
$this->assertArrayHasKey('diff', $data);
$this->assertArrayHasKey('warnings', $data);
$this->assertEquals(1, $data['version']);
$this->assertEquals('full', $data['restoreMode']);
}
// ── Restore ─────────────────────────────────────────────────────
public function testRestoreRequiresGestionnaire(): void
{
$machine = $this->createMachine('Machine R');
$client = $this->createViewerClient();
$client->request('POST', sprintf('/api/machines/%s/versions/1/restore', $machine->getId()));
$this->assertResponseStatusCodeSame(403);
}
public function testRestoreCreatesNewVersion(): void
{
$composant = $this->createComposant('Original Name');
// Update
$gClient = $this->createGestionnaireClient();
$gClient->request('PATCH', self::iri('composants', $composant->getId()), [
'headers' => ['Content-Type' => 'application/merge-patch+json'],
'json' => ['name' => 'Updated Name'],
]);
$this->assertResponseIsSuccessful();
// Restore to version 1
$gClient2 = $this->createGestionnaireClient();
$gClient2->request('POST', sprintf('/api/composants/%s/versions/1/restore', $composant->getId()));
$this->assertResponseIsSuccessful();
$data = $gClient2->getResponse()->toArray();
$this->assertTrue($data['success']);
$this->assertEquals(1, $data['restoredFromVersion']);
$this->assertGreaterThan(2, $data['newVersion']);
// Verify the name was restored
$vClient = $this->createViewerClient();
$vClient->request('GET', self::iri('composants', $composant->getId()));
$this->assertResponseIsSuccessful();
$entityData = $vClient->getResponse()->toArray();
$this->assertEquals('Original Name', $entityData['name']);
}
public function testRestoreVersionNotFound(): void
{
$composant = $this->createComposant('Composant NF');
$client = $this->createGestionnaireClient();
$client->request('POST', sprintf('/api/composants/%s/versions/999/restore', $composant->getId()));
$this->assertResponseStatusCodeSame(404);
}
}