[
  {
    "id": "S-01",
    "group": "Security and data protection",
    "type": "Security",
    "test": "API key is loaded from environment variables or a secrets manager and is never hard-coded in source files."
  },
  {
    "id": "S-02",
    "group": "Security and data protection",
    "type": "Security",
    "test": "API key does not appear in any client-side bundle."
  },
  {
    "id": "S-03",
    "group": "Security and data protection",
    "type": "Security",
    "test": "API key does not appear in any log output."
  },
  {
    "id": "S-04",
    "group": "Security and data protection",
    "type": "Security",
    "test": "All PayCaptain API calls are made server-side only."
  },
  {
    "id": "S-05",
    "group": "Security and data protection",
    "type": "Integration",
    "test": "All API communication uses HTTPS."
  },
  {
    "id": "S-06",
    "group": "Security and data protection",
    "type": "E2E",
    "test": "Authentication is required for all routes that display or modify employee data."
  },
  {
    "id": "S-07",
    "group": "Security and data protection",
    "type": "E2E",
    "test": "Role-based access control is enforced."
  },
  {
    "id": "S-08",
    "group": "Security and data protection",
    "type": "E2E",
    "test": "Session expires after the configured inactivity timeout."
  },
  {
    "id": "S-09",
    "group": "Security and data protection",
    "type": "Integration",
    "test": "Login attempts are recorded in the audit log."
  },
  {
    "id": "S-10",
    "group": "Security and data protection",
    "type": "Security",
    "test": "CSRF protection is present on all state-changing endpoints."
  },
  {
    "id": "E-01",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject an employee record missing any required field."
  },
  {
    "id": "E-02",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if payBasis is \"Salaried\" and salary is missing."
  },
  {
    "id": "E-03",
    "group": "Employee data",
    "type": "Unit",
    "test": "Accept gender \"Male\" and \"Female\". Reject all other values."
  },
  {
    "id": "E-04",
    "group": "Employee data",
    "type": "Unit",
    "test": "Accept valid UK postcodes. Reject invalid postcodes."
  },
  {
    "id": "E-05",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if any address field is empty."
  },
  {
    "id": "E-06",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if leaveDate is before startDate."
  },
  {
    "id": "E-07",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if payEffectiveDate is before startDate or after leaveDate."
  },
  {
    "id": "E-08",
    "group": "Employee data",
    "type": "Unit",
    "test": "For a new employee, reject if payEffectiveDate does not equal startDate."
  },
  {
    "id": "E-09",
    "group": "Employee data",
    "type": "Unit",
    "test": "When salary changes, reject if payEffectiveDate is not updated."
  },
  {
    "id": "E-10",
    "group": "Employee data",
    "type": "Unit",
    "test": "When rateOfPay changes, reject if payEffectiveDate is not updated."
  },
  {
    "id": "E-11",
    "group": "Employee data",
    "type": "Unit",
    "test": "When payBasis changes, reject if payEffectiveDate is not updated."
  },
  {
    "id": "E-12",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if two employment records share the same payrollCode."
  },
  {
    "id": "E-13",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if payrollCode is modified on an existing record."
  },
  {
    "id": "E-14",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if a re-hired employee reuses a previous payrollCode."
  },
  {
    "id": "E-15",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if employment date ranges overlap for the same hrEmployeeId."
  },
  {
    "id": "E-16",
    "group": "Employee data",
    "type": "Unit",
    "test": "Accept non-overlapping employments for the same person."
  },
  {
    "id": "E-17",
    "group": "Employee data",
    "type": "Integration",
    "test": "Do not send an unchanged employee record to the API."
  },
  {
    "id": "E-18",
    "group": "Employee data",
    "type": "Integration",
    "test": "Send an employee record when at least one field has changed."
  },
  {
    "id": "E-19",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject records for non-PAYE workers."
  },
  {
    "id": "E-20",
    "group": "Employee data",
    "type": "Unit",
    "test": "Reject if payBasis is not exactly \"Hourly\" or \"Salaried\"."
  },
  {
    "id": "E-21",
    "group": "Employee data",
    "type": "Unit",
    "test": "If niNumber is provided, validate the UK National Insurance number format."
  },
  {
    "id": "E-22",
    "group": "Employee data",
    "type": "Unit",
    "test": "If starterDeclaration is provided, accept only \"A\", \"B\" or \"C\"."
  },
  {
    "id": "A-01",
    "group": "Shifts",
    "type": "Unit",
    "test": "Reject a shift record that has not been approved."
  },
  {
    "id": "A-02",
    "group": "Shifts",
    "type": "Integration",
    "test": "Accept a shift record that has been explicitly approved."
  },
  {
    "id": "A-03",
    "group": "Shifts",
    "type": "Unit",
    "test": "Reject if type is not one of the allowed values."
  },
  {
    "id": "A-04",
    "group": "Shifts",
    "type": "Unit",
    "test": "Reject if hrEmployeeId or payrollCode does not match an active employee."
  },
  {
    "id": "A-05",
    "group": "Shifts",
    "type": "Unit",
    "test": "Reject if shiftId is missing or empty."
  },
  {
    "id": "A-06",
    "group": "Shifts",
    "type": "Integration",
    "test": "Do not send an unchanged shift record to the API."
  },
  {
    "id": "A-07",
    "group": "Shifts",
    "type": "Integration",
    "test": "Setting archived to true triggers a resubmission."
  },
  {
    "id": "A-08",
    "group": "Shifts",
    "type": "E2E",
    "test": "A user with the Viewer role cannot approve shift records."
  },
  {
    "id": "A-09",
    "group": "Shifts",
    "type": "Unit",
    "test": "Reject if payBasis is not \"Hourly\" or \"Daily\"."
  },
  {
    "id": "P-01",
    "group": "Payments",
    "type": "Unit",
    "test": "Reject if payCode is missing, empty or not a configured Pay Code Name."
  },
  {
    "id": "P-02",
    "group": "Payments",
    "type": "Unit",
    "test": "Reject if hrEmployeeId or payrollCode does not match an active employee."
  },
  {
    "id": "P-03",
    "group": "Payments",
    "type": "Unit",
    "test": "Reject if paymentId is missing or empty."
  },
  {
    "id": "P-04",
    "group": "Payments",
    "type": "Integration",
    "test": "Do not send an unchanged payment record to the API."
  },
  {
    "id": "P-05",
    "group": "Payments",
    "type": "Integration",
    "test": "Setting archived to true triggers a resubmission."
  },
  {
    "id": "P-06",
    "group": "Payments",
    "type": "Unit",
    "test": "Reject if date is missing or not a valid ISO 8601 date."
  },
  {
    "id": "P-07",
    "group": "Payments",
    "type": "E2E",
    "test": "A user with the Viewer role cannot create, edit or archive pay elements."
  },
  {
    "id": "I-01",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "The X-API-Key header is present on every request."
  },
  {
    "id": "I-02",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "On HTTP 429, retry with exponential backoff."
  },
  {
    "id": "I-03",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "On HTTP 5xx, retry with exponential backoff."
  },
  {
    "id": "I-04",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "After 5 consecutive failures, halt further attempts and alert."
  },
  {
    "id": "I-05",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "Never exceed 5 requests per second."
  },
  {
    "id": "I-06",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "Never exceed 100 requests per minute."
  },
  {
    "id": "I-07",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "On HTTP 400, do not retry."
  },
  {
    "id": "I-08",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "GET /employees pagination is followed until a 204."
  },
  {
    "id": "I-09",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "GET /payslips pagination is followed until a 204."
  },
  {
    "id": "I-10",
    "group": "Integration and resilience",
    "type": "Security",
    "test": "No stack traces or internal errors are exposed to the user."
  },
  {
    "id": "I-11",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "All create, update and archive operations are recorded in the audit log."
  },
  {
    "id": "I-12",
    "group": "Integration and resilience",
    "type": "Integration",
    "test": "Never hold more than 3 concurrent connections."
  }
]
