test: verify share bot API contract

This commit is contained in:
2026-08-16 01:19:20 +07:00 Unverified
parent c9f01231c6
commit f32171a576
3 changed files with 161 additions and 56 deletions
+60
View File
@@ -0,0 +1,60 @@
import { describe, expect, test } from "bun:test";
import {
buildBotShareResponse,
hasValidShareBotBearer,
rejectSuppliedBotProfile,
resolveBotSenderProfile,
} from "@/lib/share/bot";
describe("share bot contract", () => {
test("requires an exact bearer secret", () => {
expect(hasValidShareBotBearer(null, "secret")).toBeFalse();
expect(hasValidShareBotBearer("Basic secret", "secret")).toBeFalse();
expect(hasValidShareBotBearer("Bearer wrong", "secret")).toBeFalse();
expect(hasValidShareBotBearer("Bearer secret", "secret")).toBeTrue();
});
test("rejects request-provided identity snapshots", () => {
const formData = new FormData();
formData.set("displayName", "Untrusted name");
expect(() => rejectSuppliedBotProfile(formData)).toThrow("must not be supplied");
});
test("distinguishes an invalid guild sender from lookup downtime", async () => {
await expect(
resolveBotSenderProfile("1", async () => null),
).rejects.toMatchObject({ status: 422 });
await expect(
resolveBotSenderProfile("1", async () => {
throw new Error("Discord unavailable");
}),
).rejects.toMatchObject({ status: 503 });
});
test("returns the documented successful response shape", () => {
expect(
buildBotShareResponse({
baseUrl: "https://example.com",
share: {
id: "a1b2c3d4",
createdAt: new Date("2026-08-16T00:00:00.000Z"),
},
displayName: "Example User",
avatarUrl: "https://cdn.discordapp.com/avatar.png",
imagePath: "/api/cdn/image-1",
}),
).toEqual({
success: true,
share: {
id: "a1b2c3d4",
url: "https://example.com/share/a1b2c3d4",
author: {
displayName: "Example User",
avatarUrl: "https://cdn.discordapp.com/avatar.png",
},
imageUrl: "https://example.com/api/cdn/image-1",
createdAt: "2026-08-16T00:00:00.000Z",
},
});
});
});
+82
View File
@@ -0,0 +1,82 @@
import { timingSafeEqual } from "node:crypto";
import { ShareHttpError } from "@/lib/share/http-error";
export type BotSenderProfile = {
id: string;
username: string;
globalName: string | null;
avatarUrl: string | null;
};
const FORBIDDEN_PROFILE_FIELDS = [
"displayName",
"avatarUrl",
"authorName",
"authorAvatarUrl",
] as const;
export function hasValidShareBotBearer(
authorization: string | null,
expected: string,
) {
if (!authorization?.startsWith("Bearer ")) return false;
const suppliedBytes = Buffer.from(authorization.slice("Bearer ".length));
const expectedBytes = Buffer.from(expected);
return suppliedBytes.length === expectedBytes.length &&
timingSafeEqual(suppliedBytes, expectedBytes);
}
export function rejectSuppliedBotProfile(formData: FormData) {
for (const field of FORBIDDEN_PROFILE_FIELDS) {
if (formData.has(field)) {
throw new ShareHttpError(
`Profile field ${field} must not be supplied`,
400,
);
}
}
}
export async function resolveBotSenderProfile(
discordId: string,
lookup: (discordId: string) => Promise<BotSenderProfile | null>,
) {
let profile: BotSenderProfile | null;
try {
profile = await lookup(discordId);
} catch {
throw new ShareHttpError("Discord profile lookup is unavailable", 503);
}
if (!profile) {
throw new ShareHttpError(
"Discord sender was not found in the configured guild",
422,
);
}
return profile;
}
export function buildBotShareResponse({
baseUrl,
share,
displayName,
avatarUrl,
imagePath,
}: {
baseUrl: string;
share: { id: string; createdAt: Date };
displayName: string;
avatarUrl: string | null;
imagePath: string | null;
}) {
return {
success: true as const,
share: {
id: share.id,
url: `${baseUrl}/share/${share.id}`,
author: { displayName, avatarUrl },
imageUrl: imagePath ? `${baseUrl}${imagePath}` : null,
createdAt: share.createdAt.toISOString(),
},
};
}