feat: public events/polls, friends graph, captcha, SSE hardening, and admin UX
Ship product gaps: register/vote pages, friend add/accept/decline/remove, email verify TTL, captcha on login/forgot, soft-fail user actions, SSE abort/shared client, Commando Centrum error toasts, admin delete for events/polls, and IT/NL i18n fills. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
2ff08e5127
commit
ed7db6e048
76 files changed
+4834
-1376
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockGet = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@/lib/services/site-settings", () => ({
|
||||
siteSettings: { get: mockGet },
|
||||
}));
|
||||
|
||||
import { captchaConfig, verifyCaptcha } from "./captcha";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("verifyCaptcha", () => {
|
||||
it("allows when provider is none", async () => {
|
||||
mockGet.mockImplementation(async (key: string, fallback?: string) => {
|
||||
if (key === "captcha_provider") return "none";
|
||||
return fallback ?? "";
|
||||
});
|
||||
expect(await verifyCaptcha(null)).toBe(true);
|
||||
});
|
||||
|
||||
it("fails closed when provider set but secret missing", async () => {
|
||||
mockGet.mockImplementation(async (key: string, fallback?: string) => {
|
||||
if (key === "captcha_provider") return "turnstile";
|
||||
if (key === "turnstile_site_key") return "site-key";
|
||||
if (key === "turnstile_secret") return "";
|
||||
return fallback ?? "";
|
||||
});
|
||||
expect(await verifyCaptcha("tok")).toBe(false);
|
||||
});
|
||||
|
||||
it("fails closed on provider API/network errors", async () => {
|
||||
mockGet.mockImplementation(async (key: string, fallback?: string) => {
|
||||
if (key === "captcha_provider") return "turnstile";
|
||||
if (key === "turnstile_site_key") return "site-key";
|
||||
if (key === "turnstile_secret") return "secret";
|
||||
return fallback ?? "";
|
||||
});
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockRejectedValue(new Error("network down")),
|
||||
);
|
||||
expect(await verifyCaptcha("tok", "1.2.3.4")).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts a successful provider response", async () => {
|
||||
mockGet.mockImplementation(async (key: string, fallback?: string) => {
|
||||
if (key === "captcha_provider") return "recaptcha";
|
||||
if (key === "recaptcha_site_key") return "site-key";
|
||||
if (key === "recaptcha_secret") return "secret";
|
||||
return fallback ?? "";
|
||||
});
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ success: true }),
|
||||
}),
|
||||
);
|
||||
expect(await verifyCaptcha("good-token")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("captchaConfig", () => {
|
||||
it("returns none by default", async () => {
|
||||
mockGet.mockImplementation(async (_key: string, fallback?: string) => {
|
||||
return fallback ?? "none";
|
||||
});
|
||||
const cfg = await captchaConfig();
|
||||
expect(cfg.provider).toBe("none");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user