WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 123 lines 4.3 kB view raw
1import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 2import { logger } from "../../lib/logger.js"; 3import { createAuthRoutes } from "../auth.js"; 4 5vi.mock("../../lib/logger.js", () => ({ 6 logger: { 7 debug: vi.fn(), 8 info: vi.fn(), 9 warn: vi.fn(), 10 error: vi.fn(), 11 fatal: vi.fn(), 12 }, 13})); 14 15const mockFetch = vi.fn(); 16 17describe("createAuthRoutes", () => { 18 beforeEach(() => { 19 vi.stubGlobal("fetch", mockFetch); 20 vi.stubEnv("APPVIEW_URL", "http://localhost:3000"); 21 vi.mocked(logger.error).mockClear(); 22 }); 23 24 afterEach(() => { 25 vi.unstubAllGlobals(); 26 vi.unstubAllEnvs(); 27 mockFetch.mockReset(); 28 }); 29 30 function loadAuthRoutes() { 31 return createAuthRoutes("http://localhost:3000"); 32 } 33 34 describe("POST /logout", () => { 35 it("calls AppView logout, clears cookie, and redirects to /", async () => { 36 mockFetch.mockResolvedValueOnce({ 37 ok: true, 38 status: 200, 39 }); 40 41 const authRoutes = await loadAuthRoutes(); 42 const res = await authRoutes.request("/logout", { 43 method: "POST", 44 headers: { cookie: "atbb_session=user-token" }, 45 }); 46 47 expect(res.status).toBe(303); 48 expect(res.headers.get("location")).toBe("/"); 49 50 // Verify AppView logout was called with the forwarded cookie 51 expect(mockFetch).toHaveBeenCalledOnce(); 52 const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit]; 53 expect(url).toBe("http://localhost:3000/api/auth/logout"); 54 // Full Cookie header forwarded verbatim 55 expect((init.headers as Record<string, string>)["Cookie"]).toBe( 56 "atbb_session=user-token" 57 ); 58 }); 59 60 it("clears atbb_session cookie via Set-Cookie header", async () => { 61 mockFetch.mockResolvedValueOnce({ ok: true, status: 200 }); 62 63 const authRoutes = await loadAuthRoutes(); 64 const res = await authRoutes.request("/logout", { method: "POST" }); 65 66 const setCookie = res.headers.get("set-cookie"); 67 expect(setCookie).toContain("atbb_session="); 68 expect(setCookie).toContain("Max-Age=0"); 69 }); 70 71 it("still clears cookie and redirects even if AppView logout fails", async () => { 72 mockFetch.mockRejectedValueOnce(new Error("fetch failed: ECONNREFUSED")); 73 74 const authRoutes = await loadAuthRoutes(); 75 const res = await authRoutes.request("/logout", { method: "POST" }); 76 77 // Should still redirect home (graceful degradation) 78 expect(res.status).toBe(303); 79 expect(res.headers.get("location")).toBe("/"); 80 const setCookie = res.headers.get("set-cookie"); 81 expect(setCookie).toContain("Max-Age=0"); 82 }); 83 84 it("logs error when AppView logout returns non-ok status", async () => { 85 mockFetch.mockResolvedValueOnce({ ok: false, status: 500 }); 86 87 const authRoutes = await loadAuthRoutes(); 88 const res = await authRoutes.request("/logout", { method: "POST" }); 89 90 // Still redirects home despite non-ok response 91 expect(res.status).toBe(303); 92 expect(logger.error).toHaveBeenCalledWith( 93 expect.stringContaining("non-ok status"), 94 expect.objectContaining({ status: 500 }) 95 ); 96 }); 97 98 it("re-throws programming errors (ReferenceError) without clearing the cookie", async () => { 99 mockFetch.mockRejectedValueOnce(new ReferenceError("fetch is not defined")); 100 101 const authRoutes = await loadAuthRoutes(); 102 const res = await authRoutes.request("/logout", { method: "POST" }); 103 104 // Programming error escapes the catch block — Hono's default handler returns 500 105 // rather than the expected 303 redirect, and the cookie is never cleared 106 expect(res.status).toBe(500); 107 expect(res.headers.get("set-cookie")).toBeNull(); 108 }); 109 110 it("logs error when AppView logout throws a network error", async () => { 111 mockFetch.mockRejectedValueOnce(new Error("fetch failed: ECONNREFUSED")); 112 113 const authRoutes = await loadAuthRoutes(); 114 const res = await authRoutes.request("/logout", { method: "POST" }); 115 116 expect(res.status).toBe(303); 117 expect(logger.error).toHaveBeenCalledWith( 118 expect.stringContaining("Failed to call AppView logout"), 119 expect.objectContaining({ error: expect.stringContaining("ECONNREFUSED") }) 120 ); 121 }); 122 }); 123});