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

feat(web): POST /admin/themes/:rkey/duplicate — proxy duplicate to AppView (ATB-58)

+66
+66
apps/web/src/routes/__tests__/admin.test.tsx
··· 2361 2361 expect(res.headers.get("location")).toContain("/admin/themes?error="); 2362 2362 }); 2363 2363 }); 2364 + 2365 + describe("createAdminRoutes — POST /admin/themes/:rkey/duplicate", () => { 2366 + beforeEach(() => { 2367 + vi.stubGlobal("fetch", mockFetch); 2368 + vi.stubEnv("APPVIEW_URL", "http://localhost:3000"); 2369 + vi.resetModules(); 2370 + }); 2371 + 2372 + afterEach(() => { 2373 + vi.unstubAllGlobals(); 2374 + vi.unstubAllEnvs(); 2375 + mockFetch.mockReset(); 2376 + }); 2377 + 2378 + function mockResponse(body: unknown, ok = true, status = 200) { 2379 + return { ok, status, json: () => Promise.resolve(body) }; 2380 + } 2381 + 2382 + function setupAuthenticatedSession(permissions: string[]) { 2383 + mockFetch.mockResolvedValueOnce( 2384 + mockResponse({ authenticated: true, did: "did:plc:user", handle: "alice.bsky.social" }) 2385 + ); 2386 + mockFetch.mockResolvedValueOnce(mockResponse({ permissions })); 2387 + } 2388 + 2389 + async function loadAdminRoutes() { 2390 + const { createAdminRoutes } = await import("../admin.js"); 2391 + return createAdminRoutes("http://localhost:3000"); 2392 + } 2393 + 2394 + it("duplicates theme and redirects to /admin/themes on success", async () => { 2395 + setupAuthenticatedSession(["space.atbb.permission.manageThemes"]); 2396 + mockFetch.mockResolvedValueOnce( 2397 + mockResponse( 2398 + { uri: "at://...", rkey: "newrkey", name: "Neobrutal Light (Copy)" }, 2399 + true, 2400 + 201 2401 + ) 2402 + ); 2403 + 2404 + const routes = await loadAdminRoutes(); 2405 + const res = await routes.request("/admin/themes/3lbltheme1aa/duplicate", { 2406 + method: "POST", 2407 + headers: { cookie: "atbb_session=token" }, 2408 + }); 2409 + 2410 + expect(res.status).toBe(302); 2411 + expect(res.headers.get("location")).toBe("/admin/themes"); 2412 + }); 2413 + 2414 + it("redirects with error on AppView failure", async () => { 2415 + setupAuthenticatedSession(["space.atbb.permission.manageThemes"]); 2416 + mockFetch.mockResolvedValueOnce( 2417 + mockResponse({ error: "Theme not found" }, false, 404) 2418 + ); 2419 + 2420 + const routes = await loadAdminRoutes(); 2421 + const res = await routes.request("/admin/themes/nonexistent/duplicate", { 2422 + method: "POST", 2423 + headers: { cookie: "atbb_session=token" }, 2424 + }); 2425 + 2426 + expect(res.status).toBe(302); 2427 + expect(res.headers.get("location")).toContain("/admin/themes?error="); 2428 + }); 2429 + });