test(e2e): add API CRUD specs for time-tracking, projects, billing, contracts (PMS-155) #136

Merged
nrupard merged 5 commits from feat/pms-155-e2e-core-modules into main 2026-06-09 17:04:46 +02:00
Owner

What

Adds Playwright E2E API specs for the next batch of real PSA modules (PMS-155, "fill out E2E testing for the rest"): time-tracking, projects, billing, and contracts. Each spec drives a full create/read/update/list/delete lifecycle against the deployed API through the existing bearer-token request fixture, matching the style of the existing tickets/contacts specs.

This is the first batch; the remaining real modules (tenants extras, calendar/dispatch, sla, assets, knowledge_base, notifications, rmm, reports, settings, audit) are left for follow-up PRs to keep the diff reviewable and contain deploy-gate risk.

Module gating (important)

These four modules are tenant-gated and default to DISABLED: is_module_enabled COALESCEs a missing module_config row to FALSE, so every route behind a RequireModuleEnabled extractor 404s until the module is on. Each spec enables its module first via the admin-only PUT /api/v1/settings/modules/{module} route (new lib/factories.enableModule helper), so the specs run regardless of the staging tenant's current config. The enable persists on the E2E tenant; that is configuration, not swept residue.

No new-route deadlock: unlike PMS-149, the routes under test already ship in staging, so the specs assert real responses directly rather than tolerating a pre-deploy 405.

Coverage shape (driven by what is cleanable)

  • Time-tracking: work types, time entries, rounding rules. Full lifecycle, deleted inline in reverse-dependency order (entry before its work type).
  • Projects: project, phase, task, plus the admin task-status a task needs. No company (company_id is optional on a project).
  • Billing: tax-rates and payments get the full lifecycle (both have DELETE routes). Invoices have NO delete route, so creating one would leave permanent residue (the leak PMS-149/PMS-155 set out to avoid); the spec smoke-reads the invoice list only. A follow-up should add a delete/void-and-purge invoice route plus the matching lifecycle.
  • Contracts: contract, contract item, standalone rate card, hour-balance read smoke. Rate-card items are skipped because they need a work_type_id that would couple this spec to the time_tracking module.

Mechanics

  • Decimals are sent as JSON strings to match the house convention (see tests/audit_hooks.rs).
  • Enum string values (contract_type managed_services, payment_method check, item_type product, rounding_method nearest) are taken from the migration CHECK constraints.
  • Global teardown gains best-effort backstop sweeps for the new top-level named resources (projects, contracts, work types, rounding rules, task statuses, rate cards, tax rates); records without a run-suffixed name (time entries, tasks, contract items, payments) are deleted inline. Sweeps for gated-module resources no-op when the module is disabled.
  • The api project's testMatch is widened to pick up the four new specs.

Verification

  • npx tsc --noEmit clean.
  • npx playwright test --list shows all four new specs under the api project.
  • Full run happens in staging CI (the suite targets the deployed API; no local DB path).

🤖 Generated with Claude Code

## What Adds Playwright E2E API specs for the next batch of real PSA modules (PMS-155, "fill out E2E testing for the rest"): time-tracking, projects, billing, and contracts. Each spec drives a full create/read/update/list/delete lifecycle against the deployed API through the existing bearer-token request fixture, matching the style of the existing tickets/contacts specs. This is the first batch; the remaining real modules (tenants extras, calendar/dispatch, sla, assets, knowledge_base, notifications, rmm, reports, settings, audit) are left for follow-up PRs to keep the diff reviewable and contain deploy-gate risk. ## Module gating (important) These four modules are tenant-gated and default to DISABLED: `is_module_enabled` COALESCEs a missing `module_config` row to `FALSE`, so every route behind a `RequireModuleEnabled` extractor 404s until the module is on. Each spec enables its module first via the admin-only `PUT /api/v1/settings/modules/{module}` route (new `lib/factories.enableModule` helper), so the specs run regardless of the staging tenant's current config. The enable persists on the E2E tenant; that is configuration, not swept residue. No new-route deadlock: unlike PMS-149, the routes under test already ship in staging, so the specs assert real responses directly rather than tolerating a pre-deploy 405. ## Coverage shape (driven by what is cleanable) - Time-tracking: work types, time entries, rounding rules. Full lifecycle, deleted inline in reverse-dependency order (entry before its work type). - Projects: project, phase, task, plus the admin task-status a task needs. No company (company_id is optional on a project). - Billing: tax-rates and payments get the full lifecycle (both have DELETE routes). Invoices have NO delete route, so creating one would leave permanent residue (the leak PMS-149/PMS-155 set out to avoid); the spec smoke-reads the invoice list only. A follow-up should add a delete/void-and-purge invoice route plus the matching lifecycle. - Contracts: contract, contract item, standalone rate card, hour-balance read smoke. Rate-card items are skipped because they need a work_type_id that would couple this spec to the time_tracking module. ## Mechanics - Decimals are sent as JSON strings to match the house convention (see `tests/audit_hooks.rs`). - Enum string values (contract_type `managed_services`, payment_method `check`, item_type `product`, rounding_method `nearest`) are taken from the migration CHECK constraints. - Global teardown gains best-effort backstop sweeps for the new top-level named resources (projects, contracts, work types, rounding rules, task statuses, rate cards, tax rates); records without a run-suffixed name (time entries, tasks, contract items, payments) are deleted inline. Sweeps for gated-module resources no-op when the module is disabled. - The `api` project's `testMatch` is widened to pick up the four new specs. ## Verification - `npx tsc --noEmit` clean. - `npx playwright test --list` shows all four new specs under the `api` project. - Full run happens in staging CI (the suite targets the deployed API; no local DB path). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
test(e2e): add API CRUD specs for time-tracking, projects, billing, contracts (PMS-155)
Some checks failed
E2E (staging) / Playwright against staging (pull_request) Failing after 39s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m58s
802a291341
Fills E2E coverage for the next batch of real PSA modules. Each spec drives a full create/read/update/list/delete lifecycle against the deployed API via the existing bearer-token request fixture, mirroring the tickets/contacts specs.

These four modules are tenant-gated and default to DISABLED (is_module_enabled COALESCEs a missing module_config row to FALSE), so every spec enables its module up front through the admin-only PUT /api/v1/settings/modules/{module} route via a new lib/factories.enableModule helper. This keeps the specs self-sufficient regardless of the staging tenant's current config. The routes themselves already ship in staging, so unlike PMS-149 there is no new-route deploy-gate deadlock to tolerate here.

Coverage notes: billing invoices have no DELETE route, so creating one would leave permanent residue (the leak PMS-149/PMS-155 set out to avoid); the billing spec therefore smoke-reads the invoice list only and runs the full lifecycle on tax-rates and payments, which are deletable. Rate-card items are skipped because they require a work_type_id that would couple the contracts spec to the time_tracking module. Decimals are sent as JSON strings to match the house convention in tests/audit_hooks.rs.

Each record carries a run suffix and is deleted inline in reverse-dependency order; global teardown gains best-effort backstop sweeps for the new top-level named resources (projects, contracts, work types, rounding rules, task statuses, rate cards, tax rates) and the api project's testMatch is widened to pick the new specs up.

#PMS-155

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test(e2e): make PMS-155 spec cleanup resilient to a single delete failure
Some checks failed
E2E (staging) / Playwright against staging (pull_request) Failing after 29s
Check / fmt + clippy + compile + tests (pull_request) Successful in 56s
9aaeac64e6
Code-review follow-up. The inline cleanup chained `expect((await delete).ok()).toBeTruthy()` per resource, so a non-2xx on an early delete threw and skipped the remaining deletes, orphaning records the teardown name-sweep cannot reach (time entries, tasks, contract items, payments carry no run-suffixed name). Collect every delete (still in reverse-dependency FK order) and assert afterwards, so all deletes run regardless of an individual failure and the test still fails loudly on any non-ok status.

#PMS-155

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test(e2e): add API CRUD specs for calendar, SLA, assets, knowledge-base (PMS-155 batch 2)
Some checks failed
E2E (staging) / Playwright against staging (pull_request) Failing after 46s
Check / fmt + clippy + compile + tests (pull_request) Successful in 2m35s
fcf367f4d8
Second batch of E2E coverage for the real PSA modules, same shape as batch 1: each spec drives a full create/read/update/list/delete lifecycle against the deployed API via the bearer-token request fixture.

- calendar: appointment (create/get/update/list/delete) + time-off (create/get/delete) + on-call schedule (create/update/delete). Gated module, enabled first; on-call is manager-gated (admin satisfies it). Appointment datetimes are RFC 3339 with end after start.
- sla: policy (create/get/update/list/delete) + business hours + holiday calendar. SLA is NOT module-gated, but every write is admin-only. Targets are skipped (they need a priority_id that would couple this to the tickets module).
- assets: asset type -> asset -> configuration item. Gated module. The config item exercises the encrypted-at-rest path: its value is encrypted on write and the single-item GET decrypts it (the list omits it); the spec asserts the round-trip.
- knowledge-base: category -> article with the auto-versioning path (create makes v1, a content update snapshots v2; the spec checks version history has >= 2 rows). Category and article slugs are unique per tenant, so each create uses a fresh runSuffix slug.

Mechanics: enum string values (appointment_type meeting, time-off type personal, asset status in_repair, on-call rotation_type daily, kb visibility internal/public) come from the migration CHECK constraints. Cleanup runs every delete before asserting (collect-then-assert) so one failure cannot orphan the rest. global.teardown.ts is refactored to a single ordered table of sweep targets (children before parents, company last) and now also backstops the new named resources; gated-module sweeps no-op when the module is disabled. The api project testMatch is widened for the four new specs.

#PMS-155

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test(e2e): add API specs for notifications, settings, audit, reports (PMS-155 batch 3)
Some checks failed
Check / fmt + clippy + compile + tests (pull_request) Successful in 2m3s
E2E (staging) / Playwright against staging (pull_request) Failing after 2m6s
007ba8bdee
Third batch of E2E coverage for the cross-cutting/platform modules.

- notifications: template + channel create/list/delete. Not module-gated; writes are admin-only. Templates and channels expose no update or get-by-id route, so the lifecycle is create -> list -> delete. channel_type uses CHECK-constrained enums ('email' for the template, 'in_app' for the channel since it needs no external config).
- settings: full tenant-setting upsert/read/update/delete keyed by (category, key). A throwaway 'e2e' category with a run-suffixed key is used so validate_setting_value accepts the value verbatim (unknown category/key pairs pass) and nothing real is overwritten; the row is deleted inline and its absence confirmed with a 404.
- audit: read-only end-to-end check. Creates a company, then asserts GET /audit-log?entity_type=companies&action=create surfaces a matching entry by entity_id (the contacts service writes the audit row in the same transaction, so it is immediately queryable; default sort is newest-first). Company cleaned up inline.
- reports: read-only smoke. Enables the gated 'reports' module, then asserts the dashboard/tickets/time/billing aggregations and a CSV export all return 200 (no mandatory params; date ranges default to 30 days; billing's manager gate is satisfied by the E2E admin). Nothing created, no residue.

global.teardown.ts gains backstop sweeps for notification templates and channels; the api project testMatch is widened for the four new specs.

#PMS-155

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test(e2e): add API specs for RMM and the dispatch board (PMS-155 batch 4)
Some checks failed
E2E (staging) / Playwright against staging (pull_request) Failing after 45s
Check / fmt + clippy + compile + tests (pull_request) Successful in 56s
Create release / Create release from merged PR (pull_request) Has been skipped
8f878ddfbe
Fourth batch, closing out the remaining real modules.

- rmm: connection -> alert rule + device mapping. The module is tenant-gated ('rmm_integration') and every route is admin-only. The connection is created with throwaway credentials: create_connection only stores the api_key encrypted at rest (no live provider call - that is the separate /test route) and the response never echoes the key, so no real RMM system is needed. provider uses the CHECK-constrained enum 'tactical_rmm'. The connection gets full create/read/update/list; alert rules and device mappings are create/list/delete only and reference the connection (FK), so they are deleted before it.
- dispatch: read-only smoke of the aggregated technician board (RequireCalendar). It requires both from+to RFC 3339 query params (400s without), so the spec passes a one-day range and asserts 200. No residue.

global.teardown.ts gains backstop sweeps for rmm alert rules, device mappings, and connections (children before the connection); device mappings carry a run-suffixed device_name so they are sweepable. The api project testMatch is widened for the two new specs.

#PMS-155

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/pms-155-e2e-core-modules 2026-06-09 17:04:46 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!136
No description provided.