['label' => 'Microsoft 365', 'tenant' => 'example.onmicrosoft.com', 'product' => 'Business Premium'], 'network' => ['label' => 'Core network', 'hostname' => 'core-sw-01', 'ip_address' => '192.0.2.10', 'vlan' => 20], 'router' => ['label' => 'Edge router', 'hostname' => 'edge-01', 'ip_address' => '192.0.2.1', 'model' => 'RB5009'], 'infrastructure' => ['label' => 'Production server', 'hostname' => 'app-01', 'role' => 'application'], ] as $category => $data) { $result = $command->validate(['client_id' => 7, 'category' => $category, 'data' => $data]); technical_command_assert_same(true, $result['valid'], "{$category} information should validate."); technical_command_assert_same($category, $result['record']['category'], 'Category should be retained in the normalized record.'); technical_command_assert_same($data['label'], $result['record']['data']['label'], 'Structured category data should be retained.'); } $invalid = $command->validate(['client_id' => 7, 'category' => 'router', 'data' => ['label' => 'Router', 'ip_address' => 'not-an-ip', 'unexpected' => 'secret']]); if ($invalid['valid'] || !isset($invalid['errors']['data.ip_address'], $invalid['errors']['data.unexpected'])) { throw new RuntimeException('Structured technical information must reject invalid and unknown fields.'); } $existing = ['id' => 41, 'client_id' => 7, 'category' => 'network', 'data' => ['label' => 'Core', 'hostname' => 'sw-01', 'vlan' => 10]]; $edit = $command->validateEdit($existing, ['data' => ['vlan' => '20', 'notes' => ' Updated ']]); technical_command_assert_same(true, $edit['valid'], 'A technical-information edit should validate merged data.'); technical_command_assert_same(20, $edit['record']['data']['vlan'], 'Edit should normalize structured values.'); technical_command_assert_same('Updated', $edit['record']['data']['notes'], 'Edit should trim text values.'); $delete = $command->validateDelete($existing); technical_command_assert_same(['valid' => true, 'action' => 'delete', 'id' => 41, 'client_id' => 7, 'category' => 'network', 'errors' => []], $delete, 'Delete validation should return an adapter-ready action.'); $projection = $command->display(['id' => 41, 'client_id' => 7, 'category' => 'router', 'data' => ['label' => 'Edge', 'hostname' => 'edge-01', 'password' => 'do-not-show', 'secret' => 'do-not-show']]); technical_command_assert_same(['id' => 41, 'client_id' => 7, 'category' => 'router', 'data' => ['label' => 'Edge', 'hostname' => 'edge-01']], $projection, 'Display projection must allow-list safe structured fields.'); final class TechnicalInformationCommandFakePdo extends PDO { public function __construct() {} public function prepare(string $query, array $options = []): PDOStatement|false { return new class($query) extends PDOStatement { public function __construct(private string $query) {} public function execute(?array $params = null): bool { return true; } public function fetch(int $mode = PDO::FETCH_DEFAULT, int ...$args): mixed { return ['id' => 55, 'client_id' => 7, 'category' => 'microsoft', 'data_json' => '{"label":"M365","tenant":"example.onmicrosoft.com"}', 'updated_by' => null, 'created_at' => null, 'updated_at' => null]; } }; } } $repository = new TechnicalInformationRepository(new TechnicalInformationCommandFakePdo()); $stored = (new TechnicalInformationCommand($repository))->create(7, ['category' => 'microsoft', 'data' => ['label' => 'M365', 'tenant' => 'example.onmicrosoft.com']]); technical_command_assert_same(55, $stored['id'], 'Command should remain compatible with the repository adapter.'); printf("Technical information command tests: 9 passed\n");