test: cover share authentication and copy behavior

This commit is contained in:
2026-08-16 01:20:59 +07:00 Unverified
parent f32171a576
commit 649eac1776
5 changed files with 86 additions and 8 deletions
+5 -2
View File
@@ -31,11 +31,14 @@ import {
import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea";
import { MAX_SHARE_COMMENT_LENGTH } from "@/lib/share/limits";
import { getShareContentForClipboard } from "@/lib/share/copy";
export function CopyShareActions() {
async function copyText() {
const content = document.getElementById("share-content")?.textContent;
if (content === undefined) return;
const content = getShareContentForClipboard((id) =>
document.getElementById(id),
);
if (content === null) return;
await navigator.clipboard.writeText(content);
toast.success("คัดลอกข้อความแล้ว");
}
+5
View File
@@ -0,0 +1,5 @@
export type ShareContentLookup = (id: string) => { textContent: string | null } | null;
export function getShareContentForClipboard(lookup: ShareContentLookup) {
return lookup("share-content")?.textContent ?? null;
}
+5 -6
View File
@@ -4,17 +4,16 @@ import { eq } from "drizzle-orm";
import { getServerSession } from "next-auth";
import { db } from "@/db";
import { users } from "@/db/schema";
import { getSessionDiscordId, type SessionWithDiscord } from "@/lib/auth/auth";
import type { SessionWithDiscord } from "@/lib/auth/auth";
import { authOptions } from "@/lib/auth/auth-options";
import { ShareHttpError } from "@/lib/share/http-error";
import { requireShareSessionIdentity } from "@/lib/share/session";
export async function requireShareAuthor() {
const session = await getServerSession(authOptions);
const discordId = getSessionDiscordId(session);
const userId = (session as SessionWithDiscord | null)?.user?.id;
if (!discordId || !userId) {
throw new ShareHttpError("Unauthorized", 401);
}
const { discordId, userId } = requireShareSessionIdentity(
session as SessionWithDiscord | null,
);
const [user] = await db
.select({ id: users.id, name: users.name, image: users.image })
+17
View File
@@ -0,0 +1,17 @@
import { ShareHttpError } from "@/lib/share/http-error";
export type ShareSession = {
user?: {
id?: string | null;
discordId?: string | null;
} | null;
} | null;
export function requireShareSessionIdentity(session: ShareSession) {
const userId = session?.user?.id;
const discordId = session?.user?.discordId;
if (!userId || !discordId) {
throw new ShareHttpError("Unauthorized", 401);
}
return { userId, discordId };
}
+54
View File
@@ -0,0 +1,54 @@
import { describe, expect, test } from "bun:test";
import { getShareContentForClipboard } from "@/lib/share/copy";
import { requireShareSessionIdentity } from "@/lib/share/session";
describe("share UI contracts", () => {
test("copies exact share content without reading the description", () => {
const requestedIds: string[] = [];
const exactContent = " first line\r\n\tsecond line \n";
const content = getShareContentForClipboard((id) => {
requestedIds.push(id);
if (id === "share-content") return { textContent: exactContent };
if (id === "share-description") return { textContent: "description" };
return null;
});
expect(content).toBe(exactContent);
expect(requestedIds).toEqual(["share-content"]);
});
test("rejects an unauthenticated web upload identity with 401", () => {
expect(() => requireShareSessionIdentity(null)).toThrow("Unauthorized");
try {
requireShareSessionIdentity({ user: { id: "user-1" } });
throw new Error("Expected a missing Discord ID to fail");
} catch (error) {
expect(error).toMatchObject({ status: 401 });
}
expect(
requireShareSessionIdentity({
user: { id: "user-1", discordId: "123456789012345678" },
}),
).toEqual({ userId: "user-1", discordId: "123456789012345678" });
});
test("keeps the extracted login dialog non-dismissible and shared", async () => {
const [dialog, sharePage, formPage] = await Promise.all([
Bun.file(new URL("../../components/login-dialog.tsx", import.meta.url)).text(),
Bun.file(new URL("../../app/share/page.tsx", import.meta.url)).text(),
Bun.file(new URL("../../app/form/[id]/page.tsx", import.meta.url)).text(),
]);
expect(dialog).toContain("เข้าสู่ระบบก่อนกรอกแบบฟอร์ม");
expect(dialog).toContain("showCloseButton={false}");
expect(dialog).toContain("onEscapeKeyDown");
expect(dialog).toContain("onInteractOutside");
expect(dialog).toContain("callbackUrl: window.location.href");
expect(sharePage).toContain('from "@/components/login-dialog"');
expect(sharePage).toContain("if (!discordId)");
expect(sharePage.indexOf("if (!discordId)")).toBeLessThan(
sharePage.indexOf("<ShareUploadClient />"),
);
expect(formPage).toContain('from "@/components/login-dialog"');
});
});