feat: add extensive test suite

This commit is contained in:
2026-08-30 11:33:53 +07:00 Unverified
parent 061cce00dc
commit 8359c7272e
131 changed files with 8717 additions and 2200 deletions
+61 -65
View File
@@ -7,85 +7,81 @@ import { donations, settings } from "@/lib/db/schema";
import { sse } from "@/lib/db/sse-endpoints";
import { getPostHogClient } from "@/lib/posthog-server";
import { fileToDataUrl } from "@/lib/utils";
import {
getDonationImage,
publishTestPopup,
reloadDonationWidget,
resendDonationPopup,
resetDonationGoal,
} from "./service";
function commonDependencies() {
return {
isAdmin: adminCheck,
capture: (
event: Parameters<ReturnType<typeof getPostHogClient>["capture"]>[0],
) => getPostHogClient().capture(event),
publish: (event: "update" | "ping" | "refresh", data: unknown) => {
if (event === "update") return sse.donate.pub(event, data as null);
if (event === "refresh") return sse.donate.pub(event, data as null);
return sse.donate.pub(
event,
data as Parameters<typeof sse.donate.pub<"ping">>[1],
);
},
};
}
export async function resetGoal() {
if (!(await adminCheck())) throw new Error("Unauthorized");
getPostHogClient().capture({
distinctId: "admin",
event: "donation_admin_goal_reset",
return resetDonationGoal({
...commonDependencies(),
now: () => new Date(),
async resetGoal(at) {
await db
.insert(settings)
.values({ donateGoalStarting: at })
.onConflictDoUpdate({
target: settings.id,
set: { donateGoalStarting: at },
});
},
});
await db
.insert(settings)
.values({ donateGoalStarting: new Date() })
.onConflictDoUpdate({
target: settings.id,
set: { donateGoalStarting: new Date() },
});
sse.donate.pub("update", null);
}
export async function testPopup() {
if (!(await adminCheck())) throw new Error("Unauthorized");
getPostHogClient().capture({
distinctId: "admin",
event: "donation_admin_test_popup",
});
sse.donate.pub("ping", {
id: "test",
name: "Mr. Buzz",
message: "นี่คือข้อความทดสอบโดเนท",
amount: 67,
});
return publishTestPopup(commonDependencies());
}
export async function reloadWidget() {
if (!(await adminCheck())) throw new Error("Unauthorized");
getPostHogClient().capture({
distinctId: "admin",
event: "donation_admin_widget_reload",
});
sse.donate.pub("refresh", null);
return reloadDonationWidget(commonDependencies());
}
export async function resendPopup(id: string) {
if (!(await adminCheck())) throw new Error("Unauthorized");
const [sub] = await db
.update(donations)
.set({ sent: false })
.where(eq(donations.id, id))
.returning();
if (!sub) throw new Error("not found");
getPostHogClient().capture({
distinctId: "admin",
event: "donation_admin_resend",
properties: { id },
});
sse.donate.pub("ping", {
...sub,
message: sub.message ?? "",
image: sub.image
? await fileToDataUrl(new File([Buffer.from(sub.image)], "abc.jpeg"))
: undefined,
return resendDonationPopup(id, {
...commonDependencies(),
async resetSent(donationId) {
const [donation] = await db
.update(donations)
.set({ sent: false })
.where(eq(donations.id, donationId))
.returning();
return donation;
},
imageToDataUrl: (image) =>
fileToDataUrl(new File([Buffer.from(image)], "abc.jpeg")),
});
}
export async function getImage(id: string) {
if (!(await adminCheck())) throw new Error("Unauthorized");
const [sub] = await db
.select({ image: donations.image })
.from(donations)
.where(eq(donations.id, id));
if (!sub) throw new Error("not found");
if (!sub.image) throw new Error("no image");
return new Blob([Buffer.from(sub.image)], { type: "image/jpeg" });
const image = await getDonationImage(id, {
isAdmin: adminCheck,
async findImage(donationId) {
const [donation] = await db
.select({ image: donations.image })
.from(donations)
.where(eq(donations.id, donationId));
return donation?.image;
},
});
return new Blob([Buffer.from(image)], { type: "image/jpeg" });
}
+16 -13
View File
@@ -1,16 +1,16 @@
import { adminCheck } from "@/lib/auth";
import { redirect } from "next/navigation";
import { DonateWatcher } from "../client";
import { db } from "@/lib/db";
import { donations } from "@/lib/db/schema";
import { desc } from "drizzle-orm";
import { Gavel, ImageOff, RefreshCw } from "lucide-react";
import { fileToDataUrl } from "@/lib/utils";
import { redirect } from "next/navigation";
import { markDone } from "@/app/widget/donate/api";
import { ActionButton } from "@/components/action-button";
import Image from "@/components/image";
import { Spinner } from "@/components/ui/spinner";
import { ActionButton } from "@/components/action-button";
import { adminCheck } from "@/lib/auth";
import { db } from "@/lib/db";
import { donations } from "@/lib/db/schema";
import { fileToDataUrl } from "@/lib/utils";
import { reloadWidget } from "../api";
import { markDone } from "@/app/widget/donate/api";
import { DonateWatcher } from "../client";
import { ClientTracker, PIP, VolumeChanger } from "./client";
export default async function DonateModeratorPage() {
@@ -26,8 +26,9 @@ export default async function DonateModeratorPage() {
async function reject() {
"use server";
markDone(sub.id);
reloadWidget();
if (!(await adminCheck())) throw new Error("Unauthorized");
await markDone(sub.id, process.env.DONATE_WIDGET_KEY ?? null);
await reloadWidget();
}
return (
@@ -37,9 +38,11 @@ export default async function DonateModeratorPage() {
{sub.image ? (
<div className="relative flex aspect-square w-full max-w-100 items-center justify-center overflow-hidden rounded-lg border bg-black/50">
<Image
src={await fileToDataUrl(
new File([Buffer.from(sub.image)], "abc.jpeg"),
)}
src={
await fileToDataUrl(
new File([Buffer.from(sub.image)], "abc.jpeg"),
)
}
alt="donation image"
fill
className="z-10"
+139
View File
@@ -0,0 +1,139 @@
import { describe, expect, mock, test } from "bun:test";
import {
getDonationImage,
publishTestPopup,
reloadDonationWidget,
resendDonationPopup,
resetDonationGoal,
} from "./service";
function common(isAdmin = true) {
return {
isAdmin: mock(async () => isAdmin),
capture: mock(() => {}),
publish: mock(() => {}),
};
}
describe("donation admin guards", () => {
test("all admin operations reject before mutation, analytics, or SSE", async () => {
const deps = common(false);
const resetGoal = mock(async () => {});
const resetSent = mock(async () => undefined);
const findImage = mock(async () => Buffer.from("image"));
const actions = [
() => resetDonationGoal({ ...deps, resetGoal, now: () => new Date(0) }),
() => publishTestPopup(deps),
() => reloadDonationWidget(deps),
() =>
resendDonationPopup("id", {
...deps,
resetSent,
imageToDataUrl: async () => "data:image/jpeg;base64,aW1hZ2U=",
}),
() => getDonationImage("id", { isAdmin: deps.isAdmin, findImage }),
];
for (const action of actions)
await expect(action()).rejects.toThrow("Unauthorized");
expect(resetGoal).not.toHaveBeenCalled();
expect(resetSent).not.toHaveBeenCalled();
expect(findImage).not.toHaveBeenCalled();
expect(deps.capture).not.toHaveBeenCalled();
expect(deps.publish).not.toHaveBeenCalled();
});
});
describe("donation admin operations", () => {
test("resets the goal at one timestamp and publishes an update", async () => {
const deps = common();
const at = new Date("2026-08-30T12:00:00Z");
const resetGoal = mock(async () => {});
await resetDonationGoal({ ...deps, resetGoal, now: () => at });
expect(resetGoal).toHaveBeenCalledWith(at);
expect(deps.capture).toHaveBeenCalledWith({
distinctId: "admin",
event: "donation_admin_goal_reset",
});
expect(deps.publish).toHaveBeenCalledWith("update", null);
});
test("publishes the stable test popup payload", async () => {
const deps = common();
await publishTestPopup(deps);
expect(deps.publish).toHaveBeenCalledWith("ping", {
id: "test",
name: "Mr. Buzz",
message: "นี่คือข้อความทดสอบโดเนท",
amount: 67,
});
});
test("publishes a widget refresh and analytics", async () => {
const deps = common();
await reloadDonationWidget(deps);
expect(deps.capture).toHaveBeenCalledWith({
distinctId: "admin",
event: "donation_admin_widget_reload",
});
expect(deps.publish).toHaveBeenCalledWith("refresh", null);
});
test("resets and republishes a donation with normalized message and image", async () => {
const deps = common();
const resetSent = mock(async () => ({
id: "donation-1",
name: "Buzz",
amount: 50,
message: null,
image: Buffer.from("image"),
}));
const imageToDataUrl = mock(async () => "data:image/jpeg;base64,aW1hZ2U=");
await resendDonationPopup("donation-1", {
...deps,
resetSent,
imageToDataUrl,
});
expect(resetSent).toHaveBeenCalledWith("donation-1");
expect(deps.publish).toHaveBeenCalledWith("ping", {
id: "donation-1",
name: "Buzz",
amount: 50,
message: "",
image: "data:image/jpeg;base64,aW1hZ2U=",
});
expect(deps.capture).toHaveBeenCalledWith({
distinctId: "admin",
event: "donation_admin_resend",
properties: { id: "donation-1" },
});
});
test("fails resend when the donation does not exist", async () => {
const deps = common();
await expect(
resendDonationPopup("missing", {
...deps,
resetSent: async () => undefined,
imageToDataUrl: async () => "unused",
}),
).rejects.toThrow("not found");
expect(deps.capture).not.toHaveBeenCalled();
expect(deps.publish).not.toHaveBeenCalled();
});
test("returns an image and distinguishes missing rows from empty images", async () => {
const isAdmin = async () => true;
expect(
await getDonationImage("id", {
isAdmin,
findImage: async () => Buffer.from("image"),
}),
).toEqual(Buffer.from("image"));
await expect(
getDonationImage("id", { isAdmin, findImage: async () => undefined }),
).rejects.toThrow("not found");
await expect(
getDonationImage("id", { isAdmin, findImage: async () => null }),
).rejects.toThrow("no image");
});
});
+96
View File
@@ -0,0 +1,96 @@
type AdminDependencies = {
isAdmin: () => Promise<unknown>;
capture: (event: {
distinctId: string;
event: string;
properties?: Record<string, unknown>;
}) => unknown;
publish: (event: "update" | "ping" | "refresh", data: unknown) => unknown;
};
async function authorize(dependencies: AdminDependencies) {
if (!(await dependencies.isAdmin())) throw new Error("Unauthorized");
}
export async function resetDonationGoal(
dependencies: AdminDependencies & {
resetGoal: (at: Date) => Promise<unknown>;
now: () => Date;
},
) {
await authorize(dependencies);
dependencies.capture({
distinctId: "admin",
event: "donation_admin_goal_reset",
});
await dependencies.resetGoal(dependencies.now());
dependencies.publish("update", null);
}
export async function publishTestPopup(dependencies: AdminDependencies) {
await authorize(dependencies);
dependencies.capture({
distinctId: "admin",
event: "donation_admin_test_popup",
});
dependencies.publish("ping", {
id: "test",
name: "Mr. Buzz",
message: "นี่คือข้อความทดสอบโดเนท",
amount: 67,
});
}
export async function reloadDonationWidget(dependencies: AdminDependencies) {
await authorize(dependencies);
dependencies.capture({
distinctId: "admin",
event: "donation_admin_widget_reload",
});
dependencies.publish("refresh", null);
}
export async function resendDonationPopup<
T extends {
id: string;
name: string;
amount: number;
message: string | null;
image: Buffer | null;
},
>(
id: string,
dependencies: AdminDependencies & {
resetSent: (id: string) => Promise<T | undefined>;
imageToDataUrl: (image: Buffer) => Promise<string>;
},
) {
await authorize(dependencies);
const donation = await dependencies.resetSent(id);
if (!donation) throw new Error("not found");
dependencies.capture({
distinctId: "admin",
event: "donation_admin_resend",
properties: { id },
});
dependencies.publish("ping", {
...donation,
message: donation.message ?? "",
image: donation.image
? await dependencies.imageToDataUrl(donation.image)
: undefined,
});
}
export async function getDonationImage(
id: string,
dependencies: Pick<AdminDependencies, "isAdmin"> & {
findImage: (id: string) => Promise<Buffer | null | undefined>;
},
) {
if (!(await dependencies.isAdmin())) throw new Error("Unauthorized");
const image = await dependencies.findImage(id);
if (image === undefined) throw new Error("not found");
if (image === null) throw new Error("no image");
return image;
}