diff --git a/__tests__/message-handler.test.ts b/__tests__/message-handler.test.ts index 6da73e3..14ccc0b 100644 --- a/__tests__/message-handler.test.ts +++ b/__tests__/message-handler.test.ts @@ -32,6 +32,8 @@ describe("onMessage", () => { "syncBuybackPricing", "verify-write", "verify-remove", + "org-verify-write", + "org-verify-remove", ], }, }); @@ -504,6 +506,137 @@ describe("onMessage verify actions", () => { }); }); +describe("onMessage org verify actions", () => { + const verificationToken = "FLEETYARDS-ABCDEFGHIJ"; + + const orgPage = (history: string, manifesto = "Ours.") => + `
Intro.
${manifesto}
Our board.
"), + live = orgPage("Our board.
"), + save = { success: 1 }, + publish = { success: 1 }, + }: Rsi = {}) => + vi.spyOn(globalThis, "fetch").mockImplementation(async (url) => { + const target = String(url); + if (target.endsWith("/en/orgs/MARU/admin/content")) return new Response(content); + if (target.endsWith("/en/orgs/MARU/admin/preview")) return new Response(preview); + if (target.endsWith("/en/orgs/MARU")) return new Response(live); + if (target.endsWith("/api/orgs/saveDraft")) return json(save); + if (target.endsWith("/api/orgs/publishDraft")) return json(publish); + throw new Error(`unexpected ${target}`); + }); + + const send = async (message: object) => { + const sendResponse = vi.fn(); + await onMessage( + JSON.stringify(message), + sendResponse, + vi.fn().mockResolvedValue("rsi-token"), + "1.2.3" + ); + return JSON.parse(sendResponse.mock.calls[0]![0]); + }; + + const posted = (fetch: ReturnTypeOur board.
", "Half done.") }); + + const result = await send({ action: "org-verify-write", sid: "MARU", token: verificationToken }); + + expect(result.code).toBe(409); + expect(posted(fetch, "/api/orgs/saveDraft")).toEqual([]); + expect(posted(fetch, "/api/orgs/publishDraft")).toEqual([]); + }); + + it("answers 422 for org pages it cannot read", async () => { + const fetch = mockRsi({ preview: "" }); + + const result = await send({ action: "org-verify-write", sid: "MARU", token: verificationToken }); + + expect(result.code).toBe(422); + expect(posted(fetch, "/api/orgs/saveDraft")).toEqual([]); + }); + + it("does not publish when saving the draft was refused", async () => { + const fetch = mockRsi({ save: { success: 0, msg: "ErrCsrf" } }); + + const result = await send({ action: "org-verify-write", sid: "MARU", token: verificationToken }); + + expect(result.code).toBe(502); + expect(posted(fetch, "/api/orgs/publishDraft")).toEqual([]); + }); + + it("refuses an SID that is not one", async () => { + const fetch = vi.spyOn(globalThis, "fetch"); + + const result = await send({ action: "org-verify-write", sid: "../x", token: verificationToken }); + + expect(result.code).toBe(400); + expect(fetch).not.toHaveBeenCalled(); + }); + + it("removes the token it appended and publishes again", async () => { + const fetch = mockRsi({ + content: contentPage(`Our board.\n\n${verificationToken}`), + live: orgPage(`Our board.
${verificationToken}
`), + }); + + const result = await send({ action: "org-verify-remove", sid: "MARU", token: verificationToken }); + + expect(result.payload).toEqual({ sid: "MARU", changed: true }); + expect(posted(fetch, "/api/orgs/saveDraft")).toEqual([ + { symbol: "MARU", history: "Our board." }, + ]); + expect(posted(fetch, "/api/orgs/publishDraft")).toHaveLength(1); + }); +}); + describe("handleResponse", () => { it("posts message for fleetyards.net origin", () => { const postMessage = vi.fn(); diff --git a/__tests__/org.test.ts b/__tests__/org.test.ts new file mode 100644 index 0000000..8e23e0e --- /dev/null +++ b/__tests__/org.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect } from "vitest"; +import { + contentBlocks, + hasPendingChanges, + isAccessDenied, + parseDraftField, +} from "@/lib/org"; + +// Trimmed from a live org page. +const orgPage = (history: string, manifesto = "Our manifesto.") => ` +Welcome aboard.
${manifesto}
Our board.
\n\nFLEETYARDS-ABCDEFGHIJ
' + ) + ) + ).toEqual(["Welcome aboard.", "Our board.", "Our manifesto."]); + }); + + it("refuses a page without text blocks", () => { + expect(contentBlocks("Our board.
"), + orgPage("Our board.
FLEETYARDS-ABCDEFGHIJ
") + ) + ).toBe(false); + }); + + it("sees another officer's unpublished edit", () => { + expect( + hasPendingChanges( + orgPage("Our board.
", "A new manifesto."), + orgPage("Our board.
") + ) + ).toBe(true); + }); + + it("refuses to compare a page it cannot read", () => { + expect(hasPendingChanges("", orgPage("x
"))).toBeNull(); + }); +}); diff --git a/lib/bio.ts b/lib/bio.ts index c91cc29..f24edd5 100644 --- a/lib/bio.ts +++ b/lib/bio.ts @@ -13,7 +13,7 @@ const ENTITIES: Record