95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { publishRelease, type ReleaseInput } from "./release-client.js";
|
|
|
|
const input: ReleaseInput = {
|
|
credential: "synthetic-test-value-not-a-real-credential",
|
|
version: "1.2.3",
|
|
commit: "a".repeat(40),
|
|
notes: "Verified changes",
|
|
jar: new Uint8Array([0x50, 0x4b, 0x03, 0x04]),
|
|
};
|
|
|
|
function success(init: RequestInit): Response {
|
|
return Response.json({
|
|
id: 42,
|
|
draft: init.method !== "PATCH",
|
|
tag_name: "v1.2.3",
|
|
name: "purpur-carry-1.2.3.jar",
|
|
size: input.jar.length,
|
|
});
|
|
}
|
|
|
|
test("publishes exact bytes through a fixed-repository draft without redirects", async () => {
|
|
const calls: { url: string; init: RequestInit }[] = [];
|
|
const id = await publishRelease(input, async (url, init) => {
|
|
calls.push({ url, init });
|
|
return success(init);
|
|
});
|
|
|
|
assert.equal(id, 42);
|
|
assert.equal(calls.length, 3);
|
|
assert.equal(calls[0]!.url, "https://git.garvis.dev/api/v1/repos/dmg/purpur-carry/releases");
|
|
assert.deepEqual(JSON.parse(String(calls[0]!.init.body)), {
|
|
tag_name: "v1.2.3",
|
|
target_commitish: input.commit,
|
|
name: "v1.2.3",
|
|
body: input.notes,
|
|
draft: true,
|
|
prerelease: false,
|
|
});
|
|
assert.equal(calls[1]!.url,
|
|
"https://git.garvis.dev/api/v1/repos/dmg/purpur-carry/releases/42/assets?name=purpur-carry-1.2.3.jar");
|
|
assert.deepEqual(new Uint8Array(await new Response(calls[1]!.init.body).arrayBuffer()), input.jar);
|
|
assert.equal(calls[2]!.url, "https://git.garvis.dev/api/v1/repos/dmg/purpur-carry/releases/42");
|
|
assert.deepEqual(JSON.parse(String(calls[2]!.init.body)), { draft: false });
|
|
for (const call of calls) {
|
|
assert.equal(call.init.redirect, "error");
|
|
assert.equal(new Headers(call.init.headers).get("authorization"), `token ${input.credential}`);
|
|
assert.ok(!call.url.includes(input.credential));
|
|
}
|
|
});
|
|
|
|
for (const failureStep of [1, 2, 3]) {
|
|
for (const status of [302, 401, 409, 500]) {
|
|
test(`stops at step ${failureStep} on HTTP ${status} without leaking API bodies`, async () => {
|
|
let calls = 0;
|
|
await assert.rejects(publishRelease(input, async (_url, init) => {
|
|
calls++;
|
|
if (calls === failureStep) return new Response(input.credential, { status });
|
|
return success(init);
|
|
}), (error: unknown) => {
|
|
assert.ok(error instanceof Error);
|
|
assert.ok(error.message.includes(String(status)));
|
|
assert.ok(!error.message.includes(input.credential));
|
|
return true;
|
|
});
|
|
assert.equal(calls, failureStep);
|
|
});
|
|
}
|
|
}
|
|
|
|
test("sanitizes transport errors", async () => {
|
|
await assert.rejects(publishRelease(input, async () => {
|
|
throw new Error(`diagnostic containing ${input.credential}`);
|
|
}), { message: "Release API request failed." });
|
|
});
|
|
|
|
for (const invalid of [
|
|
{ credential: "" },
|
|
{ credential: "header\r\ninjection" },
|
|
{ version: "../../latest" },
|
|
{ version: "1.0.0-SNAPSHOT" },
|
|
{ commit: "main" },
|
|
{ jar: new Uint8Array() },
|
|
]) {
|
|
test(`rejects invalid ${Object.keys(invalid)[0]} before network access`, async () => {
|
|
let calls = 0;
|
|
await assert.rejects(publishRelease({ ...input, ...invalid }, async () => {
|
|
calls++;
|
|
return Response.json({ id: 42 });
|
|
}), { message: "Invalid release input." });
|
|
assert.equal(calls, 0);
|
|
});
|
|
}
|