Files
edr-platform/e2e/freight/cypress/e2e/flows/booking_clearance_query.cy.ts
2026-07-24 13:26:19 +00:00

142 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* BOOKING — clearance document QUERY (reject) + re-upload lifecycle:
*
* Every other spec that touches clearance only ever APPROVES documents
* (`clearGeneralBooking` in import-utils.ts). This spec drives the other
* branch of `reviewDocument` — QUERIED — which no spec exercises:
*
* querying a document with no note is rejected (a note is required)
* querying with a note succeeds: reviewStatus → QUERIED, the note is
* stored, and a CHANGES_REQUESTED review note is recorded on the booking
* re-uploading the SAME document resets its review to PENDING (the
* customer's fix clears the query — submitClearanceDocuments always
* resets reviewed docs back to PENDING on re-upload)
* GL re-reviews it APPROVED → finalize succeeds → CLEARANCE_READY
*
* GENERAL contract, per-booking clearance gate (AWAITING_DOCUMENTS at
* creation, before ops ever sees it) — same fixture shape as
* clearGeneralBooking, with an ad-hoc document. Retries off.
*/
import {
apiPost,
bookContainers,
db,
departureAt,
eatDayStr,
glUpload,
seedImportContract,
superAdmin,
withBooking,
} from "./import-utils";
const stamp = String(Date.now());
// Per-booking clearance (Path A) skips the booking-window gate entirely, so
// this date never needs a real schedule behind it — just a binding day string.
const BOOKING_DAY = eatDayStr(departureAt(33));
interface ReviewRow {
status: string;
note: string | null;
}
function reviewFor(bookingId: string, fn: (row: ReviewRow) => void) {
db<ReviewRow>(
`SELECT status, note FROM freight.booking_document_review
WHERE booking_id = $1 AND file_key = 'custom_e2e'`,
[bookingId],
).then(({ rows }) => {
expect(rows, "review row for custom_e2e").to.have.length(1);
fn(rows[0]);
});
}
describe("booking: clearance document query (reject) + re-upload lifecycle", { retries: 0 }, () => {
before(() => {
cy.task("db:seedFile", "seed-import-corridor.sql");
seedImportContract({ suffix: "CQ1", reference: `CTR-IMP-${stamp}-CQ1`, kind: "GENERAL" });
});
it("books under the GENERAL contract — starts at the clearance gate", () => {
bookContainers({
suffix: "CQ1",
runStamp: stamp,
isoSeed: 0,
forty: 1,
scheduledDate: BOOKING_DAY,
});
withBooking("CQ1", (b) =>
expect(b.status, "AWAITING_DOCUMENTS").to.eq("AWAITING_DOCUMENTS"),
);
});
it("uploads a document; querying it with no note is rejected", () => {
withBooking("CQ1", (b) => {
glUpload(`/api/bookings/${b.id}/clearance/documents`, {}, "custom_e2e");
apiPost(
superAdmin,
`/api/bookings/${b.id}/clearance/review`,
{ fileKey: "custom_e2e", status: "QUERIED" },
false,
).then((res) => {
expect(res.status, "note required").to.be.within(400, 422);
expect(JSON.stringify(res.body)).to.include("A note is required");
});
});
});
it("querying with a note succeeds — reviewStatus QUERIED, note stored, review note recorded", () => {
withBooking("CQ1", (b) => {
apiPost(superAdmin, `/api/bookings/${b.id}/clearance/review`, {
fileKey: "custom_e2e",
status: "QUERIED",
note: "E2E: wrong document, please re-upload the correct one",
}).then((res) => expect(res.status, "queried").to.be.oneOf([200, 201]));
reviewFor(b.id, (row) => {
expect(row.status, "QUERIED").to.eq("QUERIED");
expect(row.note, "note stored").to.include("wrong document");
});
db<{ n: string }>(
`SELECT count(*) AS n FROM freight.booking_review_note
WHERE booking_id = $1 AND type = 'CHANGES_REQUESTED'`,
[b.id],
).then(({ rows }) =>
expect(Number(rows[0].n), "queried review note recorded").to.be.at.least(1),
);
// Still under review — the query doesn't advance the booking status.
withBooking("CQ1", (fresh) =>
expect(fresh.status, "still DOCUMENTS_UNDER_REVIEW").to.eq("DOCUMENTS_UNDER_REVIEW"),
);
});
});
it("re-uploading resets the review to PENDING; GL approves it and finalize succeeds", () => {
withBooking("CQ1", (b) => {
glUpload(`/api/bookings/${b.id}/clearance/documents`, {}, "custom_e2e");
reviewFor(b.id, (row) => expect(row.status, "reset to PENDING").to.eq("PENDING"));
apiPost(superAdmin, `/api/bookings/${b.id}/clearance/review`, {
fileKey: "custom_e2e",
status: "APPROVED",
}).then((res) => expect(res.status, "approved").to.be.oneOf([200, 201]));
reviewFor(b.id, (row) => expect(row.status, "APPROVED").to.eq("APPROVED"));
apiPost(superAdmin, `/api/bookings/${b.id}/clearance/finalize`).then((res) =>
expect(res.status, "finalized").to.be.oneOf([200, 201]),
);
});
withBooking("CQ1", (b) =>
expect(b.status, "CLEARANCE_READY").to.eq("CLEARANCE_READY"),
);
});
});
export {};