~james/core

core/web/src/lib/api/spindleLogs.test.ts -rw-r--r-- 3.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
import { describe, expect, it } from "vitest";
import { decodeLogFrame, pipelineLogsUrl } from "#lib/api/spindleLogs.ts";

// captured off the spindle's own serializer: CiSubscribePipelineLogs_Event
// .Serialize in api/tangled/cisubscribePipelineLogs_ext.go, hex-dumped
const frames = {
	controlStart:
		"a261746823636f6e74726f6c626f7001a7646b696e6464757365726473746570016474696d6574323032362d30382d30365431323a30303a30305a6673746174757365737461727467636f6d6d616e64676563686f20686967636f6e74656e746952756e20746573747368776f726b666c6f77656275696c64",
	controlMinimal:
		"a261746823636f6e74726f6c626f7001a46473746570016474696d6574323032362d30382d30365431323a30303a30395a67636f6e74656e746952756e20746573747368776f726b666c6f77656275696c64",
	data: "a26174652364617461626f7001a56473746570016474696d6574323032362d30382d30365431323a30303a30315a6673747265616d6673746465727267636f6e74656e746e1b5b33316d626f6f6d1b5b306d0a68776f726b666c6f77656275696c64",
	error: "a1626f7020a2656572726f7270576f726b666c6f774e6f74466f756e64676d657373616765706e6f207375636820776f726b666c6f77"
} as const;

const bytes = (hex: string): Uint8Array =>
	Uint8Array.from(hex.match(/../g)!.map((pair) => parseInt(pair, 16)));

describe("decodeLogFrame", () => {
	it("reads a step start with its command and kind", () => {
		expect(decodeLogFrame(bytes(frames.controlStart))).toEqual({
			kind: "control",
			control: {
				time: "2026-08-06T12:00:00Z",
				workflow: "build",
				step: 1,
				content: "Run tests",
				command: "echo hi",
				status: "start",
				kind: "user"
			}
		});
	});

	it("leaves the optional control fields off when the spindle omits them", () => {
		const frame = decodeLogFrame(bytes(frames.controlMinimal));
		expect(frame).toEqual({
			kind: "control",
			control: {
				time: "2026-08-06T12:00:09Z",
				workflow: "build",
				step: 1,
				content: "Run tests"
			}
		});
	});

	it("keeps a data line's ansi bytes and trailing newline intact", () => {
		expect(decodeLogFrame(bytes(frames.data))).toEqual({
			kind: "data",
			data: {
				time: "2026-08-06T12:00:01Z",
				workflow: "build",
				step: 1,
				content: "\u001b[31mboom\u001b[0m\n",
				stream: "stderr"
			}
		});
	});

	it("surfaces the stream's error frame", () => {
		expect(decodeLogFrame(bytes(frames.error))).toEqual({
			kind: "error",
			error: "WorkflowNotFound",
			message: "no such workflow"
		});
	});

	it("ignores a message type it doesn't know", () => {
		// same header shape, unknown t
		const unknown = "a2617464236e6577626f7001a0";
		expect(decodeLogFrame(bytes(unknown))).toBeNull();
	});
});

describe("pipelineLogsUrl", () => {
	it("upgrades the spindle's scheme and repeats the workflow param", () => {
		expect(
			pipelineLogsUrl("spindle.tangled.org", "did:plc:thisrepo", "3lz", ["build", "test"])
		).toBe(
			"wss://spindle.tangled.org/xrpc/org.tangled.ci.subscribePipelineLogs?repo=did%3Aplc%3Athisrepo&pipeline=3lz&workflows=build&workflows=test"
		);
	});

	it("stays plaintext for a local spindle", () => {
		expect(pipelineLogsUrl("localhost:6555", "did:plc:thisrepo", "3lz", ["build"])).toBe(
			"ws://localhost:6555/xrpc/org.tangled.ci.subscribePipelineLogs?repo=did%3Aplc%3Athisrepo&pipeline=3lz&workflows=build"
		);
	});
});