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.

test(web): add failing tests for board proxy routes (ATB-47)

Malpercio 83483054 6acea50b

+325
+325
apps/web/src/routes/__tests__/admin.test.tsx
··· 1306 1306 expect(location).toContain("error="); 1307 1307 }); 1308 1308 }); 1309 + 1310 + describe("createAdminRoutes — POST /admin/structure/boards", () => { 1311 + beforeEach(() => { 1312 + vi.stubGlobal("fetch", mockFetch); 1313 + vi.stubEnv("APPVIEW_URL", "http://localhost:3000"); 1314 + vi.resetModules(); 1315 + }); 1316 + 1317 + afterEach(() => { 1318 + vi.unstubAllGlobals(); 1319 + vi.unstubAllEnvs(); 1320 + mockFetch.mockReset(); 1321 + }); 1322 + 1323 + function mockResponse(body: unknown, ok = true, status = 200) { 1324 + return { ok, status, statusText: ok ? "OK" : "Error", json: () => Promise.resolve(body) }; 1325 + } 1326 + 1327 + function setupSession(permissions: string[]) { 1328 + mockFetch.mockResolvedValueOnce( 1329 + mockResponse({ authenticated: true, did: "did:plc:admin", handle: "admin.bsky.social" }) 1330 + ); 1331 + mockFetch.mockResolvedValueOnce(mockResponse({ permissions })); 1332 + } 1333 + 1334 + async function loadAdminRoutes() { 1335 + const { createAdminRoutes } = await import("../admin.js"); 1336 + return createAdminRoutes("http://localhost:3000"); 1337 + } 1338 + 1339 + function postForm(body: Record<string, string>) { 1340 + const params = new URLSearchParams(body); 1341 + return { 1342 + method: "POST", 1343 + headers: { 1344 + cookie: "atbb_session=token", 1345 + "content-type": "application/x-www-form-urlencoded", 1346 + }, 1347 + body: params.toString(), 1348 + }; 1349 + } 1350 + 1351 + it("redirects to /login when unauthenticated", async () => { 1352 + mockFetch.mockResolvedValueOnce(mockResponse({ authenticated: false })); 1353 + const routes = await loadAdminRoutes(); 1354 + const res = await routes.request( 1355 + "/admin/structure/boards", 1356 + postForm({ name: "General Chat", categoryUri: "at://did:plc:forum/space.atbb.forum.category/abc" }) 1357 + ); 1358 + expect(res.status).toBe(302); 1359 + expect(res.headers.get("location")).toBe("/login"); 1360 + }); 1361 + 1362 + it("returns 403 without manageCategories permission", async () => { 1363 + setupSession(["space.atbb.permission.manageMembers"]); 1364 + const routes = await loadAdminRoutes(); 1365 + const res = await routes.request( 1366 + "/admin/structure/boards", 1367 + postForm({ name: "General Chat", categoryUri: "at://did:plc:forum/space.atbb.forum.category/abc" }) 1368 + ); 1369 + expect(res.status).toBe(403); 1370 + }); 1371 + 1372 + it("redirects to /admin/structure on success", async () => { 1373 + setupSession(["space.atbb.permission.manageCategories"]); 1374 + mockFetch.mockResolvedValueOnce( 1375 + mockResponse({ uri: "at://did:plc:forum/space.atbb.forum.board/xyz", cid: "bafyrei..." }, true, 201) 1376 + ); 1377 + 1378 + const routes = await loadAdminRoutes(); 1379 + const res = await routes.request( 1380 + "/admin/structure/boards", 1381 + postForm({ 1382 + name: "General Chat", 1383 + description: "Chat about anything", 1384 + sortOrder: "1", 1385 + categoryUri: "at://did:plc:forum/space.atbb.forum.category/abc", 1386 + }) 1387 + ); 1388 + 1389 + expect(res.status).toBe(302); 1390 + expect(res.headers.get("location")).toBe("/admin/structure"); 1391 + }); 1392 + 1393 + it("redirects with ?error= when name is missing", async () => { 1394 + setupSession(["space.atbb.permission.manageCategories"]); 1395 + const routes = await loadAdminRoutes(); 1396 + const res = await routes.request( 1397 + "/admin/structure/boards", 1398 + postForm({ name: "", categoryUri: "at://did:plc:forum/space.atbb.forum.category/abc" }) 1399 + ); 1400 + expect(res.status).toBe(302); 1401 + const location = res.headers.get("location") ?? ""; 1402 + expect(location).toContain("/admin/structure"); 1403 + expect(location).toContain("error="); 1404 + }); 1405 + 1406 + it("redirects with ?error= when categoryUri is missing", async () => { 1407 + setupSession(["space.atbb.permission.manageCategories"]); 1408 + const routes = await loadAdminRoutes(); 1409 + const res = await routes.request( 1410 + "/admin/structure/boards", 1411 + postForm({ name: "General Chat", categoryUri: "" }) 1412 + ); 1413 + expect(res.status).toBe(302); 1414 + const location = res.headers.get("location") ?? ""; 1415 + expect(location).toContain("error="); 1416 + }); 1417 + 1418 + it("redirects with ?error= on AppView error", async () => { 1419 + setupSession(["space.atbb.permission.manageCategories"]); 1420 + mockFetch.mockResolvedValueOnce( 1421 + mockResponse({ error: "Category not found" }, false, 404) 1422 + ); 1423 + 1424 + const routes = await loadAdminRoutes(); 1425 + const res = await routes.request( 1426 + "/admin/structure/boards", 1427 + postForm({ name: "General Chat", categoryUri: "at://did:plc:forum/space.atbb.forum.category/abc" }) 1428 + ); 1429 + 1430 + expect(res.status).toBe(302); 1431 + const location = res.headers.get("location") ?? ""; 1432 + expect(location).toContain("error="); 1433 + }); 1434 + 1435 + it("redirects with ?error= on network error", async () => { 1436 + setupSession(["space.atbb.permission.manageCategories"]); 1437 + mockFetch.mockRejectedValueOnce(new Error("fetch failed")); 1438 + 1439 + const routes = await loadAdminRoutes(); 1440 + const res = await routes.request( 1441 + "/admin/structure/boards", 1442 + postForm({ name: "General Chat", categoryUri: "at://did:plc:forum/space.atbb.forum.category/abc" }) 1443 + ); 1444 + 1445 + expect(res.status).toBe(302); 1446 + const location = res.headers.get("location") ?? ""; 1447 + expect(location).toContain("error="); 1448 + }); 1449 + }); 1450 + 1451 + describe("createAdminRoutes — POST /admin/structure/boards/:id/edit", () => { 1452 + beforeEach(() => { 1453 + vi.stubGlobal("fetch", mockFetch); 1454 + vi.stubEnv("APPVIEW_URL", "http://localhost:3000"); 1455 + vi.resetModules(); 1456 + }); 1457 + 1458 + afterEach(() => { 1459 + vi.unstubAllGlobals(); 1460 + vi.unstubAllEnvs(); 1461 + mockFetch.mockReset(); 1462 + }); 1463 + 1464 + function mockResponse(body: unknown, ok = true, status = 200) { 1465 + return { ok, status, statusText: ok ? "OK" : "Error", json: () => Promise.resolve(body) }; 1466 + } 1467 + 1468 + function setupSession(permissions: string[]) { 1469 + mockFetch.mockResolvedValueOnce( 1470 + mockResponse({ authenticated: true, did: "did:plc:admin", handle: "admin.bsky.social" }) 1471 + ); 1472 + mockFetch.mockResolvedValueOnce(mockResponse({ permissions })); 1473 + } 1474 + 1475 + async function loadAdminRoutes() { 1476 + const { createAdminRoutes } = await import("../admin.js"); 1477 + return createAdminRoutes("http://localhost:3000"); 1478 + } 1479 + 1480 + function postForm(body: Record<string, string>) { 1481 + const params = new URLSearchParams(body); 1482 + return { 1483 + method: "POST", 1484 + headers: { 1485 + cookie: "atbb_session=token", 1486 + "content-type": "application/x-www-form-urlencoded", 1487 + }, 1488 + body: params.toString(), 1489 + }; 1490 + } 1491 + 1492 + it("redirects to /login when unauthenticated", async () => { 1493 + mockFetch.mockResolvedValueOnce(mockResponse({ authenticated: false })); 1494 + const routes = await loadAdminRoutes(); 1495 + const res = await routes.request("/admin/structure/boards/10/edit", postForm({ name: "Updated" })); 1496 + expect(res.status).toBe(302); 1497 + expect(res.headers.get("location")).toBe("/login"); 1498 + }); 1499 + 1500 + it("returns 403 without manageCategories", async () => { 1501 + setupSession(["space.atbb.permission.manageMembers"]); 1502 + const routes = await loadAdminRoutes(); 1503 + const res = await routes.request("/admin/structure/boards/10/edit", postForm({ name: "Updated" })); 1504 + expect(res.status).toBe(403); 1505 + }); 1506 + 1507 + it("redirects to /admin/structure on success", async () => { 1508 + setupSession(["space.atbb.permission.manageCategories"]); 1509 + mockFetch.mockResolvedValueOnce(mockResponse({ uri: "at://...", cid: "bafyrei..." }, true, 200)); 1510 + 1511 + const routes = await loadAdminRoutes(); 1512 + const res = await routes.request( 1513 + "/admin/structure/boards/10/edit", 1514 + postForm({ name: "Updated Board", description: "", sortOrder: "3" }) 1515 + ); 1516 + 1517 + expect(res.status).toBe(302); 1518 + expect(res.headers.get("location")).toBe("/admin/structure"); 1519 + }); 1520 + 1521 + it("redirects with ?error= when name is missing", async () => { 1522 + setupSession(["space.atbb.permission.manageCategories"]); 1523 + const routes = await loadAdminRoutes(); 1524 + const res = await routes.request("/admin/structure/boards/10/edit", postForm({ name: "" })); 1525 + expect(res.status).toBe(302); 1526 + const location = res.headers.get("location") ?? ""; 1527 + expect(location).toContain("error="); 1528 + }); 1529 + 1530 + it("redirects with ?error= on AppView error", async () => { 1531 + setupSession(["space.atbb.permission.manageCategories"]); 1532 + mockFetch.mockResolvedValueOnce(mockResponse({ error: "Board not found" }, false, 404)); 1533 + const routes = await loadAdminRoutes(); 1534 + const res = await routes.request("/admin/structure/boards/10/edit", postForm({ name: "Updated" })); 1535 + expect(res.status).toBe(302); 1536 + const location = res.headers.get("location") ?? ""; 1537 + expect(location).toContain("error="); 1538 + }); 1539 + }); 1540 + 1541 + describe("createAdminRoutes — POST /admin/structure/boards/:id/delete", () => { 1542 + beforeEach(() => { 1543 + vi.stubGlobal("fetch", mockFetch); 1544 + vi.stubEnv("APPVIEW_URL", "http://localhost:3000"); 1545 + vi.resetModules(); 1546 + }); 1547 + 1548 + afterEach(() => { 1549 + vi.unstubAllGlobals(); 1550 + vi.unstubAllEnvs(); 1551 + mockFetch.mockReset(); 1552 + }); 1553 + 1554 + function mockResponse(body: unknown, ok = true, status = 200) { 1555 + return { ok, status, statusText: ok ? "OK" : "Error", json: () => Promise.resolve(body) }; 1556 + } 1557 + 1558 + function setupSession(permissions: string[]) { 1559 + mockFetch.mockResolvedValueOnce( 1560 + mockResponse({ authenticated: true, did: "did:plc:admin", handle: "admin.bsky.social" }) 1561 + ); 1562 + mockFetch.mockResolvedValueOnce(mockResponse({ permissions })); 1563 + } 1564 + 1565 + async function loadAdminRoutes() { 1566 + const { createAdminRoutes } = await import("../admin.js"); 1567 + return createAdminRoutes("http://localhost:3000"); 1568 + } 1569 + 1570 + function postForm(body: Record<string, string> = {}) { 1571 + const params = new URLSearchParams(body); 1572 + return { 1573 + method: "POST", 1574 + headers: { 1575 + cookie: "atbb_session=token", 1576 + "content-type": "application/x-www-form-urlencoded", 1577 + }, 1578 + body: params.toString(), 1579 + }; 1580 + } 1581 + 1582 + it("redirects to /login when unauthenticated", async () => { 1583 + mockFetch.mockResolvedValueOnce(mockResponse({ authenticated: false })); 1584 + const routes = await loadAdminRoutes(); 1585 + const res = await routes.request("/admin/structure/boards/10/delete", postForm()); 1586 + expect(res.status).toBe(302); 1587 + expect(res.headers.get("location")).toBe("/login"); 1588 + }); 1589 + 1590 + it("returns 403 without manageCategories", async () => { 1591 + setupSession(["space.atbb.permission.manageMembers"]); 1592 + const routes = await loadAdminRoutes(); 1593 + const res = await routes.request("/admin/structure/boards/10/delete", postForm()); 1594 + expect(res.status).toBe(403); 1595 + }); 1596 + 1597 + it("redirects to /admin/structure on success", async () => { 1598 + setupSession(["space.atbb.permission.manageCategories"]); 1599 + mockFetch.mockResolvedValueOnce(mockResponse({}, true, 200)); 1600 + 1601 + const routes = await loadAdminRoutes(); 1602 + const res = await routes.request("/admin/structure/boards/10/delete", postForm()); 1603 + 1604 + expect(res.status).toBe(302); 1605 + expect(res.headers.get("location")).toBe("/admin/structure"); 1606 + }); 1607 + 1608 + it("redirects with ?error= on AppView error", async () => { 1609 + setupSession(["space.atbb.permission.manageCategories"]); 1610 + mockFetch.mockResolvedValueOnce( 1611 + mockResponse({ error: "Board has active topics" }, false, 409) 1612 + ); 1613 + 1614 + const routes = await loadAdminRoutes(); 1615 + const res = await routes.request("/admin/structure/boards/10/delete", postForm()); 1616 + 1617 + expect(res.status).toBe(302); 1618 + const location = res.headers.get("location") ?? ""; 1619 + expect(location).toContain("error="); 1620 + }); 1621 + 1622 + it("redirects with ?error= on network error", async () => { 1623 + setupSession(["space.atbb.permission.manageCategories"]); 1624 + mockFetch.mockRejectedValueOnce(new Error("fetch failed")); 1625 + 1626 + const routes = await loadAdminRoutes(); 1627 + const res = await routes.request("/admin/structure/boards/10/delete", postForm()); 1628 + 1629 + expect(res.status).toBe(302); 1630 + const location = res.headers.get("location") ?? ""; 1631 + expect(location).toContain("error="); 1632 + }); 1633 + });