~james/core

core/web/src/lib/api/ticket.test.ts -rw-r--r-- 4.2 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { afterEach, describe, expect, it, vi } from "vitest";
import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
import { atUriOf, cidOf, knotOf, labelKeyOf } from "#lib/api/syntax.ts";
import type { Blob } from "@atcute/lexicons/interfaces";
import type * as Ticket from "#lib/api/ticket.ts";

const agent = { sub: "did:plc:limpet" } as unknown as OAuthUserAgent;
const knot = knotOf("knot.example.com");
const uri = atUriOf("at://did:plc:barnacle/org.tangled.track.ticket/3lk");
const storedCid = "bafyreistored" + "a".repeat(46);
const nextCid = "bafyreinext" + "a".repeat(48);
const from = { uri, cid: cidOf(storedCid) };
const sealCid = "bafkreiseal" + "a".repeat(48);
const otterCid = "bafkreiotter" + "a".repeat(47);
const imageOf = (cid: string) => ({
	$type: "org.tangled.markup.embed.image",
	image: { $type: "blob", ref: { $link: cid }, mimeType: "image/png", size: 3 }
});

const stored = {
	$type: "org.tangled.track.ticket",
	title: { $type: "org.tangled.markup.markdown#inline", text: "kelp" },
	body: {
		$type: "org.tangled.markup.markdown",
		text: `forest ![seal](/blob/${sealCid})`,
		blobs: [imageOf(sealCid)]
	},
	state: "open",
	targetBranch: "main",
	tangledLabels: [{ key: "bug" }],
	mentions: ["did:plc:urchin"],
	createdAt: "2026-01-01T00:00:00Z",
	"x-tngl-editor": "did:plc:limpet"
};

const load = async () => {
	vi.resetModules();
	vi.doMock("$app/env", () => ({ browser: true }));
	vi.doMock("#lib/auth/agent.ts", () => ({ mintServiceAuth: async () => "token" }));
	const ticket = (await import("#lib/api/ticket.ts")) as typeof Ticket;
	const sent: Record<string, unknown>[] = [];
	vi.stubGlobal("fetch", async (_input: URL | RequestInfo, init?: RequestInit) => {
		if (!init?.body) return Response.json({ uri, cid: storedCid, value: stored });
		sent.push(JSON.parse(String(init.body)));
		return Response.json({ uri, cid: nextCid });
	});
	return { ticket, sent };
};

const { "x-tngl-editor": _, ...kept } = stored;

afterEach(() => {
	vi.unstubAllGlobals();
});

describe("ticket writes on the knot", () => {
	it("edits the prose and keeps everything the form doesn't show", async () => {
		const { ticket, sent } = await load();

		const body = `deeper ${kept.body.text}`;
		await ticket.editTicket(agent, knot, from, { title: "kelp bed", body });

		expect(sent[0]).toMatchObject({ swapRecord: storedCid });
		expect(sent[0].record).toEqual({
			...kept,
			title: { ...kept.title, text: "kelp bed" },
			body: { ...kept.body, text: body }
		});
	});

	it("pins the images the body links and lets the rest go", async () => {
		const { ticket, sent } = await load();
		const otter = imageOf(otterCid).image as Blob;

		await ticket.editTicket(agent, knot, from, {
			title: "kelp",
			body: `![otter](/blob/${otterCid}) ![otter again](/blob/${otterCid})`,
			blobs: [otter, otter]
		});

		expect(sent[0].record).toMatchObject({ body: { blobs: [imageOf(otterCid)] } });
	});

	it("leaves a blank body out, because the knot refuses an empty one", async () => {
		const { ticket, sent } = await load();

		await ticket.editTicket(agent, knot, from, { title: "kelp", body: "  " });

		expect(sent[0].record).not.toHaveProperty("body");
	});

	it("moves a pull to another target branch", async () => {
		const { ticket, sent } = await load();

		await ticket.editTicket(agent, knot, from, {
			title: "kelp",
			body: "forest",
			targetBranch: "next"
		});

		expect(sent[0].record).toMatchObject({ targetBranch: "next" });
	});

	it("changes state by rewriting the ticket", async () => {
		const { ticket, sent } = await load();

		const written = await ticket.setTicketState(agent, knot, from, "closed");

		expect(sent[0].record).toEqual({ ...kept, state: "closed" });
		expect(written.cid).toBe(nextCid);
	});

	it("replaces the label set and drops it when it ends up empty", async () => {
		const { ticket, sent } = await load();

		await ticket.setTicketLabels(agent, knot, from, [
			labelKeyOf("docs"),
			labelKeyOf("wontfix")
		]);
		await ticket.setTicketLabels(agent, knot, from, []);

		expect(sent[0].record).toMatchObject({
			tangledLabels: [
				{ $type: "org.tangled.label.defs#labelToken", key: "docs" },
				{ $type: "org.tangled.label.defs#labelToken", key: "wontfix" }
			]
		});
		expect(sent[1].record).not.toHaveProperty("tangledLabels");
	});
});