Files
0pt/tests/lib/order-notes-client.test.ts
T

90 lines
3.5 KiB
TypeScript

/**
* Klient-adaptern (kernel-transport över host-API): offline = no-op + varning
* (INGEN fetch, inget tyst förlorat), 409 -> ConflictError med aktuell
* version, presence via kernelns upsertPresence.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
OfflineError,
OrderNotesClient,
OrderNotesTransport,
} from "@/lib/order-notes-client";
describe("OrderNotesTransport (kernel CollaborationTransport)", () => {
it("send() i offline-läge kastar OfflineError utan nätverksanrop (no-op, ej tyst)", async () => {
const t = new OrderNotesTransport("admin-simon");
t.setOffline(true);
await expect(
t.send({ id: "n1", actorId: "x", clock: 1, type: "note.update", payload: { body: "y" } }),
).rejects.toBeInstanceOf(OfflineError);
});
it("connect/disconnect växlar presence via upsertPresence (online -> offline)", async () => {
const t = new OrderNotesTransport("admin-simon");
await t.connect();
expect(t.getPresence().find((p) => p.participantId === "admin-simon")?.status).toBe("online");
await t.disconnect();
expect(t.getPresence().find((p) => p.participantId === "admin-simon")?.status).toBe("offline");
});
it("observeRemote advanserar Lamport-klockan och notifyar listeners", async () => {
const t = new OrderNotesTransport("me");
const seen: string[] = [];
t.onOperation((op) => seen.push(`${op.id}@${op.clock}`));
t.observeRemote({ id: "n1", actorId: "anna", clock: 4, type: "note.upsert", payload: { body: "x" } });
expect(seen).toEqual(["n1@4"]);
expect(t.getPresence().find((p) => p.participantId === "anna")?.status).toBe("online");
});
});
describe("OrderNotesClient över host-API", () => {
beforeEach(() => {
vi.restoreAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
it("offline-läge: add() är no-op — fetch anropas EJ, OfflineError kastas (ingen tyst förlust)", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch");
const client = new OrderNotesClient("admin-simon");
client.transport.setOffline(true);
await expect(client.add("order-1", "utkast")).rejects.toBeInstanceOf(OfflineError);
expect(fetchSpy).not.toHaveBeenCalled();
});
it("409 från servern -> ConflictError med serverns aktuella version", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({ error: "version_conflict", current_version: 3 }), { status: 409 }),
);
const client = new OrderNotesClient("admin-simon");
await expect(
client.update(
{ note_id: "n1", order_id: "o1", author_id: "a", body: "old", version: 1, clock: 1, updated_at: "" },
"new",
1,
),
).rejects.toMatchObject({ currentVersion: 3 });
});
it("load(): varje servernot replays som kernel-operation (klockor advanserar)", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(
JSON.stringify({
notes: [
{ note_id: "n1", order_id: "o1", author_id: "anna", body: "A", version: 1, clock: 2, updated_at: "" },
{ note_id: "n2", order_id: "o1", author_id: "bosse", body: "B", version: 2, clock: 5, updated_at: "" },
],
}),
{ status: 200 },
),
);
const client = new OrderNotesClient("admin-simon");
const { notes, versions } = await client.load("o1");
expect(notes).toHaveLength(2);
expect(versions.get("n2")).toBe(2);
expect(client.transport.getPresence().find((p) => p.participantId === "bosse")).toBeTruthy();
});
});