import { describe, expect, it } from "vitest"; import { apiFetch, clearCookieJar } from "./setup.js"; describe("Tier 2 E2E - Boundaries and Errors", () => { // --- Feature 1: Authentication --- describe("Auth Boundary and Errors", () => { const sessionId = "tier2_auth"; it("should return 400 validation error when email is missing", async () => { clearCookieJar(sessionId); const res = await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ password: "correct-password" }), }); expect(res.$status).toBe(400); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.email).toBeDefined(); }); it("should return 400 validation error when password is missing", async () => { clearCookieJar(sessionId); const res = await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com" }), }); expect(res.$status).toBe(400); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.password).toBeDefined(); }); it("should return 401 when password is incorrect", async () => { clearCookieJar(sessionId); const res = await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "wrong-password" }), }); expect(res.$status).toBe(401); expect(res.error?.code).toBe("PERMISSION_DENIED"); expect(res.data).toBeNull(); }); it("should return 401 unauthorized when fetching profile without session", async () => { clearCookieJar(sessionId); const res = await apiFetch("/api/auth/me", { method: "GET", sessionId, }); expect(res.$status).toBe(401); expect(res.error?.code).toBe("PERMISSION_DENIED"); expect(res.data).toBeNull(); }); it("should return 401 when providing invalid 2FA verification code", async () => { clearCookieJar(sessionId); const res = await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner-2fa@example.com", password: "2fa-password", code: "999999", // wrong code }), }); expect(res.$status).toBe(401); expect(res.error?.code).toBe("PERMISSION_DENIED"); expect(res.error?.fields?.code).toBeDefined(); }); }); // --- Feature 2: Websites CRUD --- describe("Websites Boundary and Errors", () => { const sessionId = "tier2_websites"; it("should fail validation when name is shorter than 3 characters", 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/websites", { method: "POST", sessionId, body: JSON.stringify({ name: "ab", domain: "valid.com", }), }); expect(res.$status).toBe(400); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.name).toBeDefined(); }); it("should fail validation when domain is syntactically invalid", async () => { const res = await apiFetch("/api/websites", { method: "POST", sessionId, body: JSON.stringify({ name: "ValidName", domain: "invaliddomain", // no dot }), }); expect(res.$status).toBe(400); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.domain).toBeDefined(); }); it("should fail with 409 conflict when creating a duplicate website name", async () => { // Create first await apiFetch("/api/websites", { method: "POST", sessionId, body: JSON.stringify({ name: "UniqueSite", domain: "site1.com" }), }); // Create duplicate const res = await apiFetch("/api/websites", { method: "POST", sessionId, body: JSON.stringify({ name: "UniqueSite", domain: "site2.com" }), }); expect(res.$status).toBe(409); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.name).toBeDefined(); }); it("should return 404 when fetching a non-existent website details", async () => { const res = await apiFetch("/api/websites/non-existent-id", { method: "GET", sessionId, }); expect(res.$status).toBe(404); expect(res.error?.code).toBe("NOT_FOUND"); }); it("should return 404 when updating a non-existent website", async () => { const res = await apiFetch("/api/websites/non-existent-id", { method: "PUT", sessionId, body: JSON.stringify({ runtime: "PHP" }), }); expect(res.$status).toBe(404); expect(res.error?.code).toBe("NOT_FOUND"); }); it("should return 404 when deleting a non-existent website", async () => { const res = await apiFetch("/api/websites/non-existent-id", { method: "DELETE", sessionId, }); expect(res.$status).toBe(404); expect(res.error?.code).toBe("NOT_FOUND"); }); }); // --- Feature 3: Domains CRUD --- describe("Domains Boundary and Errors", () => { const sessionId = "tier2_domains"; it("should fail validation when creating a domain with invalid format", 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/domains", { method: "POST", sessionId, body: JSON.stringify({ name: "nodotdomain", type: "primary", }), }); expect(res.$status).toBe(400); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.name).toBeDefined(); }); it("should fail with 409 conflict when domain already exists", async () => { // Create first await apiFetch("/api/domains", { method: "POST", sessionId, body: JSON.stringify({ name: "conflict.com", type: "primary" }), }); // Duplicate const res = await apiFetch("/api/domains", { method: "POST", sessionId, body: JSON.stringify({ name: "conflict.com", type: "alias" }), }); expect(res.$status).toBe(409); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.name).toBeDefined(); }); it("should fail validation when creating a DNS record with TTL below minimum", async () => { const res = await apiFetch("/api/domains/dom-1/dns", { method: "POST", sessionId, body: JSON.stringify({ name: "mail", ttl: 30, // below 60 type: "A", record: "1.1.1.1", }), }); expect(res.$status).toBe(400); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.ttl).toBeDefined(); }); it("should fail validation when creating a DNS record with invalid type", async () => { const res = await apiFetch("/api/domains/dom-1/dns", { method: "POST", sessionId, body: JSON.stringify({ name: "mail", ttl: 3600, type: "INVALID_TYPE", record: "1.1.1.1", }), }); expect(res.$status).toBe(400); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.type).toBeDefined(); }); it("should return 409 CNAME conflict if a CNAME record coexists with other records of same name", async () => { // Create CNAME record 'web' await apiFetch("/api/domains/dom-1/dns", { method: "POST", sessionId, body: JSON.stringify({ name: "web", ttl: 3600, type: "A", record: "192.0.2.1", }), }); // Try creating a CNAME record with same name 'web' const res = await apiFetch("/api/domains/dom-1/dns", { method: "POST", sessionId, body: JSON.stringify({ name: "web", ttl: 3600, type: "CNAME", record: "example.com", }), }); expect(res.$status).toBe(409); expect(res.error?.code).toBe("VALIDATION_ERROR"); expect(res.error?.fields?.name).toBeDefined(); }); }); // --- Feature 4: Envelope & HTTP Specifications --- describe("Envelope & HTTP Errors", () => { const sessionId = "tier2_envelope"; it("should return 405 Method Not Allowed when requesting with invalid HTTP method", async () => { const res = await apiFetch("/api/auth/login", { method: "GET", sessionId, }); expect(res.$status).toBe(405); expect(res.error?.code).toBe("METHOD_NOT_ALLOWED"); }); it("should return 404 Not Found when requesting non-existent path", async () => { const res = await apiFetch("/api/invalid-route-path", { method: "GET", sessionId, }); expect(res.$status).toBe(404); expect(res.error?.code).toBe("NOT_FOUND"); }); it("should return 429 Too Many Requests when rate limit is exceeded", async () => { // We send a header or request a endpoint that triggers rate limit const res = await apiFetch("/api/rate-limited", { method: "GET", sessionId, }); expect(res.$status).toBe(429); expect(res.error?.code).toBe("RATE_LIMIT_ERROR"); }); it("should handle SQL injection attempts safely without crashing", async () => { clearCookieJar(sessionId); await apiFetch("/api/auth/login", { method: "POST", sessionId, body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), }); // Send SQL injection payload const sqliSearch = "' OR '1'='1' --"; const res = await apiFetch(`/api/websites?search=${encodeURIComponent(sqliSearch)}`, { method: "GET", sessionId, }); expect(res.$status).toBe(200); expect(res.error).toBeNull(); expect(res.data).toBeInstanceOf(Array); expect(res.data?.length).toBe(0); // Safely treated as literal query, returning no matches }); it("should handle simulated 500 internal server errors gracefully", async () => { const res = await apiFetch("/api/simulate-500", { method: "GET", sessionId, }); expect(res.$status).toBe(500); expect(res.error?.code).toBe("NETWORK_ERROR"); expect(res.data).toBeNull(); }); }); });