42, 'enabled' => true, 'agreement_type' => 'Premium Support', 'allocated_hours' => 12.5, 'period_type' => 'annual', 'start_date' => '2026-01-01', 'end_date' => '2026-12-31', 'rollover_enabled' => false, 'notes' => 'Priority client', ], $agreement->normalize([ 'client_id' => ' 42 ', 'enabled' => 'yes', 'agreement_type' => ' Premium Support ', 'allocated_hours' => '12.50', 'period_type' => ' ANNUAL ', 'start_date' => ' 2026-01-01 ', 'end_date' => ' 2026-12-31 ', 'rollover_enabled' => 'off', 'notes' => ' Priority client ', ]), 'SLA agreement fields should normalize deterministically.'); $valid = $agreement->validate([ 'client_id' => '7', 'enabled' => '1', 'allocated_hours' => '0', 'period_type' => 'monthly', 'rollover_enabled' => '0', ]); sla_agreement_assert_same(true, $valid['valid'], 'A minimal SLA agreement should be valid.'); sla_agreement_assert_same([], $valid['errors'], 'A valid SLA agreement should have no errors.'); sla_agreement_assert_same(null, $valid['agreement_type'], 'Optional agreement type should normalize to null.'); sla_agreement_assert_same(null, $valid['start_date'], 'Optional start date should normalize to null.'); sla_agreement_assert_same(null, $valid['end_date'], 'Optional end date should normalize to null.'); sla_agreement_assert_same(null, $valid['notes'], 'Optional notes should normalize to null.'); $invalid = $agreement->validate([ 'client_id' => '4.2', 'enabled' => 'sometimes', 'agreement_type' => str_repeat('A', 121), 'allocated_hours' => '-0.01', 'period_type' => [], 'start_date' => [], 'end_date' => [], 'rollover_enabled' => [], 'notes' => [], ]); foreach (['client_id', 'enabled', 'agreement_type', 'allocated_hours', 'period_type', 'start_date', 'end_date', 'rollover_enabled', 'notes'] as $field) { if (!isset($invalid['errors'][$field])) { throw new RuntimeException("Expected validation error for {$field}."); } } sla_agreement_assert_same(false, $invalid['valid'], 'Invalid SLA agreement fields should report valid=false.'); $reversed = $agreement->validate([ 'client_id' => 7, 'allocated_hours' => 10, 'start_date' => '2026-12-31', 'end_date' => '2026-01-01', ]); if (!isset($reversed['errors']['end_date'])) { throw new RuntimeException('An end date before the start date must be rejected.'); } $badStartOnly = $agreement->validate([ 'client_id' => 7, 'allocated_hours' => 'not-a-number', 'start_date' => 'not-a-date', 'end_date' => '2026-12-31', ]); if (!isset($badStartOnly['errors']['allocated_hours'], $badStartOnly['errors']['start_date'])) { throw new RuntimeException('Non-numeric allocation and malformed dates must be rejected.'); } if (isset($badStartOnly['errors']['end_date'])) { throw new RuntimeException('A valid end date must not receive a range error when the start date is malformed.'); } $equalDates = $agreement->validate([ 'client_id' => 7, 'start_date' => '2026-06-01', 'end_date' => '2026-06-01', ]); sla_agreement_assert_same(true, $equalDates['valid'], 'Equal start and end dates should be accepted.'); $display = $agreement->display([ 'id' => 9, 'client_id' => 7, 'enabled' => true, 'agreement_type' => 'Premium', 'allocated_hours' => 12.5, 'period_type' => 'monthly', 'start_date' => '2026-01-01', 'end_date' => '2026-12-31', 'rollover_enabled' => false, 'notes' => 'Visible note', 'password_hash' => 'omit', 'credentials' => 'omit', 'internal_token' => 'omit', ]); sla_agreement_assert_same([ 'id' => 9, 'client_id' => 7, 'enabled' => true, 'agreement_type' => 'Premium', 'allocated_hours' => 12.5, 'period_type' => 'monthly', 'start_date' => '2026-01-01', 'end_date' => '2026-12-31', 'rollover_enabled' => false, 'notes' => 'Visible note', ], $display, 'Display projection must be explicitly allow-listed.'); sla_agreement_assert_same($display, $agreement->toDisplay([ ...$display, 'credentials' => 'omit', ]), 'toDisplay should provide the same safe projection.'); printf("SLA agreement tests: 7 passed\n");