Files
Inventory/tests/Api/Entity/MachineContextCustomFieldTest.php
Matthieu 5c55441e6c fix(audit) : visibilité protected pour ActorProfileResolver
AbstractAuditSubscriber déclarait $actorProfileResolver en private readonly
via promoted property. MachineAuditSubscriber surcharge onFlush() et accède
à $this->actorProfileResolver, mais private n'est pas hérité — PHP voyait
null et levait "Call to a member function resolve() on null" sur chaque
flush Doctrine touchant des link entities.

Le passage à protected suit la convention déjà en place dans la classe
(safeGet, normalizeValue, persistAuditLog, etc. sont protected). readonly
préserve l'immutabilité de la dépendance DI.

Ajoute aussi deux tests de régression pour le clone des contextFieldValues
(symétrique au test composant existant) et nettoie deux lignes vides
cosmétiques laissées par le refactor précédent.

- testCloneMachineCopiesPieceContextFieldValues : vérifie que les CFV
  context d'un MachinePieceLink sont bien rattachées au nouveau lien
  après clone.
- testCloneMachineLeavesSourceContextFieldValuesIntact : vérifie que la
  machine source garde ses CFV context après clone (invariant implicite).
2026-05-06 15:30:59 +02:00

312 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Api\Entity;
use App\Enum\ModelCategory;
use App\Tests\AbstractApiTestCase;
/**
* @internal
*/
class MachineContextCustomFieldTest extends AbstractApiTestCase
{
public function testStructureReturnsContextFieldsOnComponentLink(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site A');
$modelType = $this->createModelType('Motor', 'MOT', ModelCategory::COMPONENT);
$contextField = $this->createCustomField(
name: 'Voltage',
type: 'number',
typeComposant: $modelType,
machineContextOnly: true,
);
$normalField = $this->createCustomField(
name: 'Serial',
type: 'text',
typeComposant: $modelType,
);
$machine = $this->createMachine('Machine A', $site);
$composant = $this->createComposant('Motor 1', 'MOT-001', $modelType);
$link = $this->createMachineComponentLink($machine, $composant);
$this->createCustomFieldValue(
customField: $contextField,
value: '220',
composant: $composant,
machineComponentLink: $link,
);
$response = $client->request('GET', '/api/machines/'.$machine->getId().'/structure');
$this->assertResponseIsSuccessful();
$data = $response->toArray();
$componentLink = $data['componentLinks'][0];
$this->assertArrayHasKey('contextCustomFields', $componentLink);
$this->assertCount(1, $componentLink['contextCustomFields']);
$this->assertSame('Voltage', $componentLink['contextCustomFields'][0]['name']);
$this->assertTrue($componentLink['contextCustomFields'][0]['machineContextOnly']);
$this->assertArrayHasKey('contextCustomFieldValues', $componentLink);
$this->assertCount(1, $componentLink['contextCustomFieldValues']);
$this->assertSame('220', $componentLink['contextCustomFieldValues'][0]['value']);
$normalFields = array_filter(
$componentLink['composant']['customFields'],
fn (array $f) => 'Serial' === $f['name'],
);
$this->assertCount(1, $normalFields);
}
public function testStructureReturnsContextFieldsOnPieceLink(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site B');
$modelType = $this->createModelType('Bearing', 'BRG', ModelCategory::PIECE);
$contextField = $this->createCustomField(
name: 'Wear Level',
type: 'select',
typePiece: $modelType,
machineContextOnly: true,
);
$contextField->setOptions(['Good', 'Fair', 'Replace']);
$this->getEntityManager()->flush();
$machine = $this->createMachine('Machine B', $site);
$piece = $this->createPiece('Bearing 1', 'BRG-001', $modelType);
$link = $this->createMachinePieceLink($machine, $piece);
$this->createCustomFieldValue(
customField: $contextField,
value: 'Fair',
piece: $piece,
machinePieceLink: $link,
);
$response = $client->request('GET', '/api/machines/'.$machine->getId().'/structure');
$data = $response->toArray();
$pieceLink = $data['pieceLinks'][0];
$this->assertCount(1, $pieceLink['contextCustomFields']);
$this->assertSame('Wear Level', $pieceLink['contextCustomFields'][0]['name']);
$this->assertCount(1, $pieceLink['contextCustomFieldValues']);
$this->assertSame('Fair', $pieceLink['contextCustomFieldValues'][0]['value']);
}
public function testUpsertContextFieldValueViaComponentLink(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site C');
$modelType = $this->createModelType('Pump', 'PMP', ModelCategory::COMPONENT);
$contextField = $this->createCustomField(
name: 'Flow Rate',
type: 'number',
typeComposant: $modelType,
machineContextOnly: true,
);
$machine = $this->createMachine('Machine C', $site);
$composant = $this->createComposant('Pump 1', 'PMP-001', $modelType);
$link = $this->createMachineComponentLink($machine, $composant);
$response = $client->request('POST', '/api/custom-fields/values/upsert', [
'json' => [
'customFieldId' => $contextField->getId(),
'machineComponentLinkId' => $link->getId(),
'value' => '380',
],
]);
$this->assertResponseIsSuccessful();
$data = $response->toArray();
$this->assertSame('380', $data['value']);
}
public function testSameComposantDifferentValuesPerMachine(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site D');
$modelType = $this->createModelType('Valve', 'VLV', ModelCategory::COMPONENT);
$contextField = $this->createCustomField(
name: 'Pressure',
type: 'number',
typeComposant: $modelType,
machineContextOnly: true,
);
$machineA = $this->createMachine('Machine A', $site);
$machineB = $this->createMachine('Machine B', $site);
$composant = $this->createComposant('Valve 1', 'VLV-001', $modelType);
$linkA = $this->createMachineComponentLink($machineA, $composant);
$linkB = $this->createMachineComponentLink($machineB, $composant);
$this->createCustomFieldValue(
customField: $contextField,
value: '100',
composant: $composant,
machineComponentLink: $linkA,
);
$this->createCustomFieldValue(
customField: $contextField,
value: '200',
composant: $composant,
machineComponentLink: $linkB,
);
$dataA = $client->request('GET', '/api/machines/'.$machineA->getId().'/structure')->toArray();
$this->assertSame('100', $dataA['componentLinks'][0]['contextCustomFieldValues'][0]['value']);
$dataB = $client->request('GET', '/api/machines/'.$machineB->getId().'/structure')->toArray();
$this->assertSame('200', $dataB['componentLinks'][0]['contextCustomFieldValues'][0]['value']);
}
public function testMachineContextOnlyFieldSerialization(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site E');
$modelType = $this->createModelType('Sensor', 'SNS', ModelCategory::COMPONENT);
$contextField = $this->createCustomField(
name: 'Calibration Date',
type: 'date',
typeComposant: $modelType,
machineContextOnly: true,
);
$response = $client->request('GET', '/api/custom_fields/'.$contextField->getId());
$this->assertResponseIsSuccessful();
$data = $response->toArray();
$this->assertTrue($data['machineContextOnly']);
}
public function testCloneMachineCopiesContextFieldValues(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site F');
$modelType = $this->createModelType('Motor Clone', 'MOTC', ModelCategory::COMPONENT);
$contextField = $this->createCustomField(
name: 'RPM Setting',
type: 'number',
typeComposant: $modelType,
machineContextOnly: true,
);
$source = $this->createMachine('Source Machine', $site);
$composant = $this->createComposant('Motor C', 'MOTC-001', $modelType);
$link = $this->createMachineComponentLink($source, $composant);
$this->createCustomFieldValue(
customField: $contextField,
value: '3000',
composant: $composant,
machineComponentLink: $link,
);
$response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
'json' => [
'name' => 'Cloned Machine',
'siteId' => $site->getId(),
],
]);
$this->assertResponseStatusCodeSame(201);
$data = $response->toArray();
$clonedLink = $data['componentLinks'][0] ?? null;
$this->assertNotNull($clonedLink);
$this->assertCount(1, $clonedLink['contextCustomFieldValues']);
$this->assertSame('3000', $clonedLink['contextCustomFieldValues'][0]['value']);
}
public function testCloneMachineCopiesPieceContextFieldValues(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site G');
$modelType = $this->createModelType('Bearing Clone', 'BRGC', ModelCategory::PIECE);
$contextField = $this->createCustomField(
name: 'Wear Level',
type: 'text',
typePiece: $modelType,
machineContextOnly: true,
);
$source = $this->createMachine('Source Piece Machine', $site);
$piece = $this->createPiece('Bearing C', 'BRGC-001', $modelType);
$link = $this->createMachinePieceLink($source, $piece);
$this->createCustomFieldValue(
customField: $contextField,
value: 'Fair',
piece: $piece,
machinePieceLink: $link,
);
$response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
'json' => [
'name' => 'Cloned Piece Machine',
'siteId' => $site->getId(),
],
]);
$this->assertResponseStatusCodeSame(201);
$data = $response->toArray();
$clonedLink = $data['pieceLinks'][0] ?? null;
$this->assertNotNull($clonedLink, 'Clone should expose at least one pieceLink');
$this->assertCount(1, $clonedLink['contextCustomFieldValues']);
$this->assertSame('Fair', $clonedLink['contextCustomFieldValues'][0]['value']);
}
public function testCloneMachineLeavesSourceContextFieldValuesIntact(): void
{
$client = $this->createGestionnaireClient();
$site = $this->createSite('Site H');
$modelType = $this->createModelType('Motor Source', 'MOTS', ModelCategory::COMPONENT);
$contextField = $this->createCustomField(
name: 'RPM',
type: 'number',
typeComposant: $modelType,
machineContextOnly: true,
);
$source = $this->createMachine('Original Machine', $site);
$composant = $this->createComposant('Motor S', 'MOTS-001', $modelType);
$link = $this->createMachineComponentLink($source, $composant);
$this->createCustomFieldValue(
customField: $contextField,
value: '1500',
composant: $composant,
machineComponentLink: $link,
);
$client->request('POST', '/api/machines/'.$source->getId().'/clone', [
'json' => [
'name' => 'Clone Machine',
'siteId' => $site->getId(),
],
]);
$this->assertResponseStatusCodeSame(201);
// Source must still expose its original context field value
$sourceData = $client->request('GET', '/api/machines/'.$source->getId().'/structure')->toArray();
$sourceLink = $sourceData['componentLinks'][0] ?? null;
$this->assertNotNull($sourceLink, 'Source machine should still expose its component link');
$this->assertCount(1, $sourceLink['contextCustomFieldValues']);
$this->assertSame('1500', $sourceLink['contextCustomFieldValues'][0]['value']);
}
}