From 8f135b8a8902eae3aed845e881aee9eafa1633ab Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:09:40 +0330 Subject: [PATCH] Import tests: backend/tests/e2e/tier3 Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- backend/tests/e2e/tier3.spec.ts | 181 ++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 backend/tests/e2e/tier3.spec.ts diff --git a/backend/tests/e2e/tier3.spec.ts b/backend/tests/e2e/tier3.spec.ts new file mode 100644 index 0000000..8d9ddf2 --- /dev/null +++ b/backend/tests/e2e/tier3.spec.ts @@ -0,0 +1,181 @@ +import { describe, expect, it } from "vitest"; +import { apiFetch, clearCookieJar } from "./setup.js"; +import type { Website, Domain } from "../../../src/api/contracts/models.js"; + +describe("Tier 3 E2E - Cross-Feature Logic", () => { + // --- 1. Session Expiration vs CRUD --- + describe("Session Expiration vs CRUD", () => { + it("should prevent CRUD operations once the session has expired or was cleared", async () => { + const sessionId = "tier3_expiration"; + clearCookieJar(sessionId); + + // Log in + await apiFetch("/api/auth/login", { + method: "POST", + sessionId, + body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), + }); + + // Verify CRUD works while logged in + const listBefore = await apiFetch("/api/websites", { method: "GET", sessionId }); + expect(listBefore.$status).toBe(200); + + // Simulate session expiration by clearing the cookies + clearCookieJar(sessionId); + + // Attempt GET should fail + const listAfter = await apiFetch("/api/websites", { method: "GET", sessionId }); + expect(listAfter.$status).toBe(401); + expect(listAfter.error?.code).toBe("PERMISSION_DENIED"); + + // Attempt POST should fail + const createAfter = await apiFetch("/api/websites", { + method: "POST", + sessionId, + body: JSON.stringify({ name: "ExpiredSite", domain: "expired.com" }), + }); + expect(createAfter.$status).toBe(401); + + // Attempt DELETE should fail + const deleteAfter = await apiFetch("/api/websites/site-1", { + method: "DELETE", + sessionId, + }); + expect(deleteAfter.$status).toBe(401); + }); + }); + + // --- 2. Role Scope Comparisons --- + describe("Role Scope Comparisons", () => { + it("should allow Owner full read/write privileges", async () => { + const sessionId = "tier3_role_owner"; + clearCookieJar(sessionId); + + await apiFetch("/api/auth/login", { + method: "POST", + sessionId, + body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), + }); + + // Owner creates website + const createRes = await apiFetch("/api/websites", { + method: "POST", + sessionId, + body: JSON.stringify({ name: "OwnerSite", domain: "owner.com" }), + }); + expect(createRes.$status).toBe(201); + + // Owner deletes website + const deleteRes = await apiFetch(`/api/websites/${createRes.data!.id}`, { + method: "DELETE", + sessionId, + }); + expect(deleteRes.$status).toBe(200); + }); + + it("should restrict Auditor role to read-only actions and block mutations", async () => { + const sessionId = "tier3_role_auditor"; + clearCookieJar(sessionId); + + await apiFetch("/api/auth/login", { + method: "POST", + sessionId, + body: JSON.stringify({ email: "auditor@example.com", password: "correct-password" }), + }); + + // Read actions should pass + const listRes = await apiFetch("/api/websites", { method: "GET", sessionId }); + expect(listRes.$status).toBe(200); + + // Write action (Create) should fail with 403 Forbidden/Permission Denied + const createRes = await apiFetch("/api/websites", { + method: "POST", + sessionId, + body: JSON.stringify({ name: "AuditorSite", domain: "auditor.com" }), + }); + expect(createRes.$status).toBe(403); + expect(createRes.error?.code).toBe("PERMISSION_DENIED"); + + // Mutation action (Delete) should fail with 403 + const deleteRes = await apiFetch("/api/websites/site-1", { + method: "DELETE", + sessionId, + }); + expect(deleteRes.$status).toBe(403); + expect(deleteRes.error?.code).toBe("PERMISSION_DENIED"); + }); + + it("should allow Developer to create/update websites but restrict them from deleting websites", async () => { + const sessionId = "tier3_role_developer"; + clearCookieJar(sessionId); + + await apiFetch("/api/auth/login", { + method: "POST", + sessionId, + body: JSON.stringify({ email: "developer@example.com", password: "correct-password" }), + }); + + // Developer can create website + const createRes = await apiFetch("/api/websites", { + method: "POST", + sessionId, + body: JSON.stringify({ name: "DevSite", domain: "dev.com" }), + }); + expect(createRes.$status).toBe(201); + + // Developer cannot delete website (returns 403) + const deleteRes = await apiFetch(`/api/websites/${createRes.data!.id}`, { + method: "DELETE", + sessionId, + }); + expect(deleteRes.$status).toBe(403); + expect(deleteRes.error?.code).toBe("PERMISSION_DENIED"); + }); + }); + + // --- 3. Domain Mapping to Website CRUD --- + describe("Domain Mapping to Website CRUD", () => { + it("should update dependent website state when its mapped domain is deleted", async () => { + const sessionId = "tier3_mapping"; + clearCookieJar(sessionId); + + await apiFetch("/api/auth/login", { + method: "POST", + sessionId, + body: JSON.stringify({ email: "owner@example.com", password: "correct-password" }), + }); + + // 1. Create a domain + const domainRes = await apiFetch("/api/domains", { + method: "POST", + sessionId, + body: JSON.stringify({ name: "mapped-domain.com", type: "primary" }), + }); + expect(domainRes.$status).toBe(201); + + // 2. Create a website mapped to that domain + const siteRes = await apiFetch("/api/websites", { + method: "POST", + sessionId, + body: JSON.stringify({ name: "MappedSite", domain: "mapped-domain.com" }), + }); + expect(siteRes.$status).toBe(201); + expect(siteRes.data?.status).toBe("healthy"); + + // 3. Delete the domain + const deleteDomRes = await apiFetch(`/api/domains/${domainRes.data!.id}`, { + method: "DELETE", + sessionId, + }); + expect(deleteDomRes.$status).toBe(200); + + // 4. Retrieve website details and check if status degraded/warning due to severed domain + const siteDetailRes = await apiFetch(`/api/websites/${siteRes.data!.id}`, { + method: "GET", + sessionId, + }); + expect(siteDetailRes.$status).toBe(200); + expect(siteDetailRes.data?.status).toBe("warning"); + }); + }); +});