feat(portal): wire company-scoped invoices + add portal isolation tests (PMS-25) #125
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/pms-25-portal-invoices-and-tests"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Completes the Customer Portal API story (PMS-25, F6). The portal surface (
/api/v1/portal/*) already had working auth, tickets, and the KB feed; this PR closes the two remaining gaps: the invoice routes were stubbed, and there was no portal test coverage (notably AC6's cross-company isolation test).Closes PMS-25.
Invoices (AC3) - PMS-33 has landed
list_invoicesandget_invoicewere placeholders (empty page / always-404) gated on PMS-33, which is now Done. Both now serve real billing data, scoped to the authenticated contact's company:GET /api/v1/portal/invoicesfiltersBillingService::list_invoicesbycontact.company_id. The scope comes from the portal JWT, never a query param, so a contact cannot widen it.GET /api/v1/portal/invoices/:idreads within the contact's tenant, then enforcesinvoice.company_id == contact.company_idin code, returning404(not403) for another company's invoice so the portal never confirms its existence.BillingServicedependency, built increate_api_routerwith the same encryption key as the agent-side billing service.Tests (AC6 + the rest) -
tests/portal.rsNew integration coverage, one test per acceptance criterion:
401; a staff token is rejected on/portal/auth/meand a portal token is rejected on/auth/me.ticket_number); company B neither lists nor canGETcompany A's ticket (404).404for another company's.publishedarticles that arepublicorclient_specificscoped to the contact's company; drafts,internal, and another company'sclient_specificarticles are hidden.401unauthenticated and never501.Verification
cargo test --test portal-> 5 passed.cargo test --lib-> 110 passed.cargo fmt --checkclean;cargo clippy --all-targets -- -D warningsclean.🤖 Generated with Claude Code