mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 15:48:11 +00:00
feat: integration tests
This commit is contained in:
92
integration/src/authz.it.ts
Normal file
92
integration/src/authz.it.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Who is allowed to touch a payment. Cheap to run (no booking chain), and the
|
||||
* failures here are the expensive kind: a tenant reading another tenant's
|
||||
* invoice, or an unauthenticated caller marking one paid.
|
||||
*/
|
||||
import { afterAll, describe, expect, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import {
|
||||
API,
|
||||
PAYMENT_API,
|
||||
api,
|
||||
closeDb,
|
||||
customerA,
|
||||
customerB,
|
||||
db,
|
||||
login,
|
||||
payment,
|
||||
} from "./client";
|
||||
|
||||
describe("payment authorization boundaries", () => {
|
||||
afterAll(closeDb);
|
||||
|
||||
it("hides one tenant's invoice from the other", async () => {
|
||||
const rows = await db<{ id: string; company_id: string }>(
|
||||
`SELECT i.id, i.company_id FROM freight.invoices i
|
||||
JOIN freight.companies c ON c.id = i.company_id
|
||||
WHERE c.tin = '0102030405' AND i.deleted_at IS NULL
|
||||
ORDER BY i.created_at DESC LIMIT 1`,
|
||||
);
|
||||
if (!rows[0]) return; // nothing billed yet in this run — payment files cover it
|
||||
const res = await api(customerB, "get", `/api/billing/my-invoices/${rows[0].id}`);
|
||||
expect([403, 404]).toContain(res.status);
|
||||
});
|
||||
|
||||
it("refuses to let one tenant pay the other's invoice", async () => {
|
||||
const rows = await db<{ id: string }>(
|
||||
`SELECT i.id FROM freight.invoices i
|
||||
JOIN freight.companies c ON c.id = i.company_id
|
||||
WHERE c.tin = '0102030405' AND i.status <> 'PAID' AND i.deleted_at IS NULL
|
||||
ORDER BY i.created_at DESC LIMIT 1`,
|
||||
);
|
||||
if (!rows[0]) return;
|
||||
const res = await api(customerB, "post", `/api/billing/my-invoices/${rows[0].id}/pay`, {
|
||||
method: "CBE_BIRR",
|
||||
platform: "web",
|
||||
});
|
||||
expect(res.status).toBeGreaterThanOrEqual(400);
|
||||
});
|
||||
|
||||
it("keeps a portal customer out of backoffice payment operations", async () => {
|
||||
const res = await api(customerA, "get", "/api/billing/invoices");
|
||||
expect(res.status).toBeGreaterThanOrEqual(400);
|
||||
});
|
||||
|
||||
it("rejects a portal account on the backoffice login audience", async () => {
|
||||
const res = await login(customerA, "12345678", "backoffice");
|
||||
expect(res.status).toBeGreaterThanOrEqual(400);
|
||||
});
|
||||
|
||||
it("requires the service token on freight's mark-paid callback", async () => {
|
||||
const body = {
|
||||
version: 1,
|
||||
eventId: "authz-probe",
|
||||
eventType: "payment.succeeded",
|
||||
occurredAt: new Date().toISOString(),
|
||||
service: "FREIGHT",
|
||||
intentId: "00000000-0000-0000-0000-000000000000",
|
||||
referenceType: "SHIPMENT",
|
||||
referenceId: "00000000-0000-0000-0000-000000000000",
|
||||
provider: "CBE_BIRR",
|
||||
amountMinor: 1,
|
||||
currency: "ETB",
|
||||
};
|
||||
const res = await request(API).post("/api/internal/payments/mark-paid").send(body);
|
||||
expect([401, 403]).toContain(res.status);
|
||||
});
|
||||
|
||||
it("requires the service token on the payment API's internal surface", async () => {
|
||||
const res = await request(PAYMENT_API).get("/payments/intents?service=FREIGHT");
|
||||
expect([400, 401, 403]).toContain(res.status);
|
||||
|
||||
// …and accepts it when present (400 = bad query, not an auth failure).
|
||||
const withToken = await payment("get", "/payments/intents?service=FREIGHT");
|
||||
expect([401, 403]).not.toContain(withToken.status);
|
||||
});
|
||||
|
||||
it("leaves the provider webhook surface public — trust is the signature", async () => {
|
||||
// A garbage payload must be acked, not 401'd: providers do not authenticate.
|
||||
const res = await request(PAYMENT_API).post("/webhooks/cbe-birr").send({ nonsense: true });
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user