7, 'name' => 'Acme IT', 'status' => 'active'], ['id' => 9, 'name' => 'Other Client', 'status' => 'inactive'], ]; $clientCommand = new ClientUpdateCommand(); $created = $clientCommand->validateForCreate([ 'name' => ' New Client ', 'support_email' => ' NEW@EXAMPLE.TEST ', ], $clients); client_crud_assert_same(true, $created['valid'], 'A unique client should be accepted for creation.'); client_crud_assert_same('New Client', $created['name'], 'Client names should be normalized before duplicate checks.'); client_crud_assert_same('new@example.test', $created['support_email'], 'Client email should be normalized.'); $duplicate = $clientCommand->validateForCreate(['name' => ' acme it '], $clients); client_crud_assert_same(false, $duplicate['valid'], 'Normalized duplicate client names should be rejected.'); if (!isset($duplicate['errors']['name'])) throw new RuntimeException('Duplicate client names should produce a name error.'); $edited = $clientCommand->validateForEdit(7, ['name' => ' ACME IT ', 'status' => 'active'], $clients); client_crud_assert_same(true, $edited['valid'], 'Editing a client should ignore its own duplicate row.'); $deactivated = $clientCommand->validateDeactivate(['id' => 7, 'status' => 'active']); client_crud_assert_same(['valid' => true, 'id' => 7, 'status' => 'inactive', 'errors' => []], $deactivated, 'Active clients should be deactivatable.'); $reactivated = $clientCommand->validateReactivate(['id' => 9, 'status' => 'inactive']); client_crud_assert_same(['valid' => true, 'id' => 9, 'status' => 'active', 'errors' => []], $reactivated, 'Inactive clients should be reactivatable.'); $contacts = [ ['id' => 11, 'client_id' => 7, 'name' => 'Jane Doe', 'email' => 'jane@example.test', 'is_primary' => true], ]; $contactCommand = new ContactUpdateCommand(); $contact = $contactCommand->validateForCreate([ 'client_id' => '7', 'name' => ' John Doe ', 'email' => ' JOHN@EXAMPLE.TEST ', 'is_primary' => 'yes', ], $contacts); client_crud_assert_same(true, $contact['valid'], 'A unique contact should be accepted.'); client_crud_assert_same(7, $contact['client_id'], 'Contact client IDs should normalize to integers.'); client_crud_assert_same(true, $contact['is_primary'], 'Primary flags should normalize to booleans.'); client_crud_assert_same([11], $contact['replace_primary_contact_ids'], 'Promoting a contact should identify the prior primary contact.'); $contactDuplicate = $contactCommand->validateForCreate(['client_id' => 7, 'name' => ' jane doe ', 'email' => 'other@example.test'], $contacts); client_crud_assert_same(false, $contactDuplicate['valid'], 'Duplicate contacts should be rejected after normalization.'); if (!isset($contactDuplicate['errors']['name'])) throw new RuntimeException('Duplicate contact names should produce a name error.'); $contactDisplay = $contactCommand->display(['id' => 11, 'client_id' => 7, 'name' => 'Jane Doe', 'email' => 'jane@example.test', 'phone' => null, 'is_primary' => true, 'password' => 'secret', 'credentials' => 'token', 'internal_secret' => 'omit']); client_crud_assert_same(['id' => 11, 'client_id' => 7, 'name' => 'Jane Doe', 'email' => 'jane@example.test', 'phone' => null, 'is_primary' => true], $contactDisplay, 'Contact display projections must exclude credentials and internal secrets.'); printf("Client CRUD tests: 10 passed\n");