import { describe, expect, it } from "vitest"; import { apiFetch, clearCookieJar } from "./setup.js"; import type { User, Website, Domain, DnsRecord } from "../../../src/api/contracts/models.js"; describe("Tier 1 E2E - Happy Paths", () => { // --- Feature 1: Authentication --- describe("Auth Happy Paths", () => { it("should successfully log in standard credentials", async () => { const sessionId = "tier1_auth_1"; clearCookieJar(sessionId); const loginRes = await apiFetch<{ user: User; challenge: string | null; sessionExpiresAt: string }>( "/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "correct-password", }), } ); expect(loginRes.$status).toBe(200); expect(loginRes.error).toBeNull(); expect(loginRes.data).toBeDefined(); expect(loginRes.data?.user.email).toBe("owner@example.com"); expect(loginRes.data?.user.role).toBe("owner"); expect(loginRes.data?.challenge).toBeNull(); expect(loginRes.data?.sessionExpiresAt).toBeDefined(); }); it("should trigger a 2FA challenge when logging in with 2FA email", async () => { const sessionId = "tier1_auth_2"; clearCookieJar(sessionId); const loginRes = await apiFetch<{ user: User | null; challenge: string | null }>( "/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner-2fa@example.com", password: "2fa-password", }), } ); expect(loginRes.$status).toBe(200); expect(loginRes.error).toBeNull(); expect(loginRes.data?.challenge).toBe("totp"); expect(loginRes.data?.user).toBeNull(); }); it("should complete login when providing valid 2FA code", async () => { const sessionId = "tier1_auth_3"; clearCookieJar(sessionId); const loginRes = await apiFetch<{ user: User; challenge: string | null }>( "/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner-2fa@example.com", password: "2fa-password", code: "123456", }), } ); expect(loginRes.$status).toBe(200); expect(loginRes.error).toBeNull(); expect(loginRes.data?.user).toBeDefined(); expect(loginRes.data?.user.twoFactorEnabled).toBe(true); }); it("should successfully fetch profile details via /api/auth/me", async () => { const sessionId = "tier1_auth_4"; clearCookieJar(sessionId); // Login first await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), }); // Get profile const meRes = await apiFetch("/api/auth/me", { method: "GET", sessionId, }); expect(meRes.$status).toBe(200); expect(meRes.error).toBeNull(); expect(meRes.data?.email).toBe("owner@example.com"); expect(meRes.data?.role).toBe("owner"); }); it("should successfully log out and invalidate session cookie", async () => { const sessionId = "tier1_auth_5"; clearCookieJar(sessionId); // Login await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), }); // Logout const logoutRes = await apiFetch<{ ok: boolean }>("/api/auth/logout", { method: "POST", sessionId, }); expect(logoutRes.$status).toBe(200); expect(logoutRes.error).toBeNull(); expect(logoutRes.data?.ok).toBe(true); // Subsequent /api/auth/me should fail const meRes = await apiFetch("/api/auth/me", { method: "GET", sessionId, }); expect(meRes.$status).toBe(401); expect(meRes.error?.code).toBe("PERMISSION_DENIED"); }); }); // --- Feature 2: Websites CRUD --- describe("Websites Happy Paths", () => { const sessionId = "tier1_websites"; it("should successfully list websites", async () => { clearCookieJar(sessionId); await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), }); const listRes = await apiFetch("/api/websites", { method: "GET", sessionId, }); expect(listRes.$status).toBe(200); expect(listRes.error).toBeNull(); expect(listRes.data).toBeInstanceOf(Array); expect(listRes.data!.length).toBeGreaterThan(0); }); it("should successfully create a new website", async () => { const createRes = await apiFetch("/api/websites", { method: "POST", sessionId, body: JSON.stringify({ name: "Test Happy Site", domain: "happy.com", runtime: "Node.js", environment: "production", }), }); expect(createRes.$status).toBe(201); expect(createRes.error).toBeNull(); expect(createRes.data?.id).toBeDefined(); expect(createRes.data?.name).toBe("Test Happy Site"); expect(createRes.data?.domain).toBe("happy.com"); }); it("should successfully retrieve website details by ID", async () => { // List to get the ID we just created const listRes = await apiFetch("/api/websites", { method: "GET", sessionId, }); const created = listRes.data!.find((w) => w.name === "Test Happy Site"); expect(created).toBeDefined(); const getRes = await apiFetch(`/api/websites/${created!.id}`, { method: "GET", sessionId, }); expect(getRes.$status).toBe(200); expect(getRes.error).toBeNull(); expect(getRes.data?.name).toBe("Test Happy Site"); }); it("should successfully update website settings", async () => { const listRes = await apiFetch("/api/websites", { method: "GET", sessionId, }); const created = listRes.data!.find((w) => w.name === "Test Happy Site"); const updateRes = await apiFetch(`/api/websites/${created!.id}`, { method: "PUT", sessionId, body: JSON.stringify({ runtime: "Docker", environment: "staging", }), }); expect(updateRes.$status).toBe(200); expect(updateRes.error).toBeNull(); expect(updateRes.data?.runtime).toBe("Docker"); expect(updateRes.data?.environment).toBe("staging"); }); it("should successfully delete a website", async () => { const listRes = await apiFetch("/api/websites", { method: "GET", sessionId, }); const created = listRes.data!.find((w) => w.name === "Test Happy Site"); const deleteRes = await apiFetch<{ deleted: string }>(`/api/websites/${created!.id}`, { method: "DELETE", sessionId, }); expect(deleteRes.$status).toBe(200); expect(deleteRes.error).toBeNull(); expect(deleteRes.data?.deleted).toBe(created!.id); // Confirm deletion const confirmRes = await apiFetch(`/api/websites/${created!.id}`, { method: "GET", sessionId, }); expect(confirmRes.$status).toBe(404); }); }); // --- Feature 3: Domains CRUD --- describe("Domains Happy Paths", () => { const sessionId = "tier1_domains"; it("should successfully list domains", async () => { clearCookieJar(sessionId); await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), }); const listRes = await apiFetch("/api/domains", { method: "GET", sessionId, }); expect(listRes.$status).toBe(200); expect(listRes.error).toBeNull(); expect(listRes.data).toBeInstanceOf(Array); expect(listRes.data!.length).toBeGreaterThan(0); }); it("should successfully create a new domain", async () => { const createRes = await apiFetch("/api/domains", { method: "POST", sessionId, body: JSON.stringify({ name: "happydomain.com", type: "primary", documentRoot: "/home/coaxys/happydomain.com", }), }); expect(createRes.$status).toBe(201); expect(createRes.error).toBeNull(); expect(createRes.data?.id).toBeDefined(); expect(createRes.data?.name).toBe("happydomain.com"); }); it("should successfully update domain properties", async () => { const listRes = await apiFetch("/api/domains", { method: "GET", sessionId, }); const created = listRes.data!.find((d) => d.name === "happydomain.com"); const updateRes = await apiFetch(`/api/domains/${created!.id}`, { method: "PUT", sessionId, body: JSON.stringify({ documentRoot: "/home/coaxys/happydomain.com/public", dnssec: true, }), }); expect(updateRes.$status).toBe(200); expect(updateRes.error).toBeNull(); expect(updateRes.data?.documentRoot).toBe("/home/coaxys/happydomain.com/public"); expect(updateRes.data?.dnssec).toBe(true); }); it("should successfully list DNS records for a domain", async () => { const listRes = await apiFetch("/api/domains", { method: "GET", sessionId, }); const domain = listRes.data!.find((d) => d.name === "happydomain.com"); const dnsRes = await apiFetch(`/api/domains/${domain!.id}/dns`, { method: "GET", sessionId, }); expect(dnsRes.$status).toBe(200); expect(dnsRes.error).toBeNull(); expect(dnsRes.data).toBeInstanceOf(Array); }); it("should successfully create, update, and delete a DNS record on a domain", async () => { const listRes = await apiFetch("/api/domains", { method: "GET", sessionId, }); const domain = listRes.data!.find((d) => d.name === "happydomain.com"); // 1. Create const createDns = await apiFetch(`/api/domains/${domain!.id}/dns`, { method: "POST", sessionId, body: JSON.stringify({ name: "web", ttl: 3600, type: "A", record: "192.0.2.55", }), }); expect(createDns.$status).toBe(201); expect(createDns.data?.id).toBeDefined(); expect(createDns.data?.record).toBe("192.0.2.55"); // 2. Update const updateDns = await apiFetch( `/api/domains/${domain!.id}/dns/${createDns.data!.id}`, { method: "PUT", sessionId, body: JSON.stringify({ record: "192.0.2.56", ttl: 7200, }), } ); expect(updateDns.$status).toBe(200); expect(updateDns.data?.record).toBe("192.0.2.56"); expect(updateDns.data?.ttl).toBe(7200); // 3. Delete const deleteDns = await apiFetch<{ deleted: string }>( `/api/domains/${domain!.id}/dns/${createDns.data!.id}`, { method: "DELETE", sessionId, } ); expect(deleteDns.$status).toBe(200); expect(deleteDns.data?.deleted).toBe(createDns.data!.id); }); it("should successfully delete a domain", async () => { const listRes = await apiFetch("/api/domains", { method: "GET", sessionId, }); const created = listRes.data!.find((d) => d.name === "happydomain.com"); const deleteRes = await apiFetch<{ deleted: string }>(`/api/domains/${created!.id}`, { method: "DELETE", sessionId, }); expect(deleteRes.$status).toBe(200); expect(deleteRes.error).toBeNull(); // Confirm deletion const confirmRes = await apiFetch(`/api/domains/${created!.id}`, { method: "GET", sessionId, }); expect(confirmRes.$status).toBe(404); }); }); // --- Feature 4: Envelope & Metadata Validation --- describe("Envelope & Metadata Happy Paths", () => { const sessionId = "tier1_envelope"; it("should conform to strict envelope formatting on success", async () => { clearCookieJar(sessionId); await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), }); const res = await apiFetch("/api/auth/me", { method: "GET", sessionId }); // Envelope layout validation expect(res).toBeDefined(); expect(res).toHaveProperty("data"); expect(res).toHaveProperty("meta"); expect(res).toHaveProperty("error"); expect(res.error).toBeNull(); expect(res.data).not.toBeNull(); expect(typeof res.meta.requestId).toBe("string"); expect(res.meta.requestId.length).toBeGreaterThan(0); }); it("should conform to strict envelope formatting on failure", async () => { // Bad credentials login const res = await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "wrong-password" }), }); // Envelope layout validation expect(res).toBeDefined(); expect(res).toHaveProperty("data"); expect(res).toHaveProperty("meta"); expect(res).toHaveProperty("error"); expect(res.data).toBeNull(); expect(res.error).not.toBeNull(); expect(typeof res.error!.code).toBe("string"); expect(typeof res.error!.message).toBe("string"); expect(typeof res.meta.requestId).toBe("string"); }); it("should provide pagination metadata on list endpoints", async () => { const res = await apiFetch("/api/websites?page=1&pageSize=1", { method: "GET", sessionId, }); expect(res.meta.pagination).toBeDefined(); expect(res.meta.pagination?.page).toBe(1); expect(res.meta.pagination?.pageSize).toBe(1); expect(typeof res.meta.pagination?.total).toBe("number"); expect(typeof res.meta.pagination?.pageCount).toBe("number"); }); it("should include rate limit metadata when appropriate", async () => { // Perform request to rate limited simulator or trigger rate limit headers const res = await apiFetch("/api/websites", { method: "GET", sessionId, headers: { "x-test-rate-limit": "true" }, }); expect(res.$status).toBe(429); expect(res.error?.code).toBe("RATE_LIMIT_ERROR"); expect((res.meta as any).rateLimit).toBeDefined(); }); }); });