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).
312 lines
12 KiB
PHP
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']);
|
|
}
|
|
}
|