From b373e074851c73a21508885fb21b191e55bbaa8d Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:09:33 +0330 Subject: [PATCH] Import tests: backend/tests/e2e/mock-server Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- backend/tests/e2e/mock-server.ts | 883 +++++++++++++++++++++++++++++++ 1 file changed, 883 insertions(+) create mode 100644 backend/tests/e2e/mock-server.ts diff --git a/backend/tests/e2e/mock-server.ts b/backend/tests/e2e/mock-server.ts new file mode 100644 index 0000000..1e5fb61 --- /dev/null +++ b/backend/tests/e2e/mock-server.ts @@ -0,0 +1,883 @@ +import http from "http"; +import url from "url"; + +// In-memory state (reset on server start) +let websites: any[] = []; +let domains: any[] = []; +let dnsRecords: Record = {}; +let activeSessions: Record = + {}; +let rateLimitTracker: Record = {}; + +function initMockDb() { + websites = [ + { + id: "site-1", + name: "Main Production", + domain: "example.com", + runtime: "PHP", + status: "healthy", + ssl: "healthy", + environment: "production", + cpuLimit: "1 vCPU", + memoryLimit: "1 GB", + lastDeployment: new Date().toISOString(), + }, + { + id: "site-2", + name: "Staging Area", + domain: "staging.example.com", + runtime: "Node.js", + status: "healthy", + ssl: "warning", + environment: "staging", + cpuLimit: "0.5 vCPU", + memoryLimit: "512 MB", + lastDeployment: new Date().toISOString(), + }, + ]; + + domains = [ + { + id: "dom-1", + name: "example.com", + type: "primary", + documentRoot: "/home/coaxys/public_html", + sslStatus: "healthy", + dnssec: true, + propagation: 100, + }, + { + id: "dom-2", + name: "staging.example.com", + type: "subdomain", + documentRoot: "/home/coaxys/staging", + sslStatus: "warning", + dnssec: false, + propagation: 90, + }, + ]; + + dnsRecords = { + "dom-1": [ + { id: "dns-1", name: "@", ttl: 3600, class: "IN", type: "A", record: "192.0.2.1" }, + { + id: "dns-2", + name: "www", + ttl: 3600, + class: "IN", + type: "CNAME", + record: "example.com", + }, + ], + "dom-2": [ + { id: "dns-3", name: "@", ttl: 3600, class: "IN", type: "A", record: "192.0.2.2" }, + ], + }; + + activeSessions = {}; + rateLimitTracker = {}; +} + +function getRequestBody(req: http.IncomingMessage): Promise { + return new Promise((resolve) => { + let body = ""; + req.on("data", (chunk) => { + body += chunk.toString(); + }); + req.on("end", () => { + resolve(body); + }); + }); +} + +function parseCookies(cookieHeader?: string): Record { + const list: Record = {}; + if (!cookieHeader) return list; + cookieHeader.split(";").forEach((cookie) => { + const parts = cookie.split("="); + const key = parts.shift()?.trim(); + if (key) { + list[key] = decodeURIComponent(parts.join("=").trim()); + } + }); + return list; +} + +function sendEnvelope( + res: http.ServerResponse, + status: number, + data: any, + metaExtra: any = {}, + error: any = null, + headers: Record = {}, +) { + const responseObj = { + data, + meta: { + requestId: `req_${Math.random().toString(16).slice(2, 10)}`, + ...metaExtra, + }, + error, + }; + res.writeHead(status, { + "Content-Type": "application/json", + ...headers, + }); + res.end(JSON.stringify(responseObj)); +} + +// Simple IP/Session rate limiter: max 5 requests per 100ms for testing rate limits +function _isRateLimited(key: string): boolean { + const now = Date.now(); + if (!rateLimitTracker[key]) { + rateLimitTracker[key] = []; + } + rateLimitTracker[key] = rateLimitTracker[key].filter((t) => now - t < 100); + if (rateLimitTracker[key].length >= 5) { + return true; + } + rateLimitTracker[key].push(now); + return false; +} + +export function startMockServer(port: number): http.Server { + initMockDb(); + + const server = http.createServer(async (req, res) => { + const parsedUrl = url.parse(req.url || "", true); + const pathname = parsedUrl.pathname || ""; + const method = req.method || "GET"; + const cookies = parseCookies(req.headers.cookie); + const _clientIp = + req.headers["x-forwarded-for"]?.toString() || req.socket.remoteAddress || "unknown"; + + // Rate limiting trigger (explicit trigger by path/header) + if (pathname === "/api/rate-limited" || req.headers["x-test-rate-limit"] === "true") { + return sendEnvelope( + res, + 429, + null, + { + rateLimit: { + limit: 5, + remaining: 0, + reset: 100, + }, + }, + { + code: "RATE_LIMIT_ERROR", + message: "Too many requests. Please try again later.", + }, + ); + } + + // 500 error simulation + if (pathname === "/api/simulate-500" || req.headers["x-simulate-500"] === "true") { + return sendEnvelope( + res, + 500, + null, + {}, + { + code: "NETWORK_ERROR", + message: "Internal server error simulated successfully.", + }, + ); + } + + // Authentication session extraction + const session = cookies.session_id ? activeSessions[cookies.session_id] : null; + + // Helper to enforce auth + const requireAuth = () => { + if (!session) { + sendEnvelope( + res, + 401, + null, + {}, + { + code: "PERMISSION_DENIED", + message: "Authentication required to access this resource.", + }, + ); + return false; + } + return true; + }; + + // Helper to enforce permission checks (for role-based scope comparison tests) + const requireRole = (roles: string[]) => { + if (!requireAuth() || !session) return false; + if (!roles.includes(session.role)) { + sendEnvelope( + res, + 403, + null, + {}, + { + code: "PERMISSION_DENIED", + message: `Role ${session.role} is not authorized for this operation.`, + }, + ); + return false; + } + return true; + }; + + // Routing Logic + try { + // 1. AUTHENTICATION ENDPOINTS + if (pathname === "/api/auth/login" && method === "POST") { + const bodyStr = await getRequestBody(req); + let body: any = {}; + try { + body = JSON.parse(bodyStr); + } catch { + return sendEnvelope( + res, + 400, + null, + {}, + { code: "VALIDATION_ERROR", message: "Invalid JSON body." }, + ); + } + + const { email, password, code } = body; + if (!email) { + return sendEnvelope( + res, + 400, + null, + {}, + { + code: "VALIDATION_ERROR", + message: "Email is required.", + fields: { email: "Email is required." }, + }, + ); + } + if (!password) { + return sendEnvelope( + res, + 400, + null, + {}, + { + code: "VALIDATION_ERROR", + message: "Password is required.", + fields: { password: "Password is required." }, + }, + ); + } + + // Simulating invalid password + if (password === "wrong-password") { + return sendEnvelope( + res, + 401, + null, + {}, + { + code: "PERMISSION_DENIED", + message: "Invalid credentials.", + }, + ); + } + + // Two-factor simulation + if ((email.includes("2fa") || password === "2fa-password") && !code) { + return sendEnvelope(res, 200, { + user: null, + challenge: "totp", + sessionExpiresAt: null, + }); + } + + if ( + (email.includes("2fa") || password === "2fa-password") && + code && + code !== "123456" + ) { + return sendEnvelope( + res, + 401, + null, + {}, + { + code: "PERMISSION_DENIED", + message: "Invalid 2FA code.", + fields: { code: "Invalid 2FA code." }, + }, + ); + } + + // Determine role + let role = "owner"; + if (email.includes("operator")) role = "operator"; + if (email.includes("developer")) role = "developer"; + if (email.includes("auditor")) role = "auditor"; + + const sessionId = `sess_${Math.random().toString(36).substring(2, 15)}`; + const user = { + id: `usr-${Math.random().toString(36).substring(2, 9)}`, + name: email.split("@")[0], + email, + role, + permissions: [], + lastLogin: new Date().toISOString(), + avatarInitials: email.substring(0, 2).toUpperCase(), + twoFactorEnabled: email.includes("2fa"), + }; + + activeSessions[sessionId] = user; + + return sendEnvelope( + res, + 200, + { + user, + challenge: null, + sessionExpiresAt: new Date(Date.now() + 45 * 60 * 1000).toISOString(), + }, + {}, + null, + { + "Set-Cookie": `session_id=${sessionId}; Path=/; HttpOnly`, + }, + ); + } + + if (pathname === "/api/auth/logout" && method === "POST") { + if (cookies.session_id) { + delete activeSessions[cookies.session_id]; + } + return sendEnvelope(res, 200, { ok: true }, {}, null, { + "Set-Cookie": "session_id=; Path=/; HttpOnly; Max-Age=0", + }); + } + + if (pathname === "/api/auth/me" && method === "GET") { + if (!requireAuth()) return; + return sendEnvelope(res, 200, session); + } + + // 2. WEBSITES ENDPOINTS + if (pathname === "/api/websites" && method === "GET") { + if (!requireAuth()) return; + + // SQL injection check: if search query contains common SQL Injection patterns, + // we safely return 200 with an empty list, verifying SQL injection safety. + const search = parsedUrl.query.search as string; + if ( + search && + (search.includes("'") || + search.includes("OR 1=") || + search.includes("UNION") || + search.includes("--")) + ) { + return sendEnvelope(res, 200, [], { + pagination: { page: 1, pageSize: 10, total: 0, pageCount: 0 }, + }); + } + + let filtered = [...websites]; + if (search) { + filtered = filtered.filter( + (w) => + w.name.toLowerCase().includes(search.toLowerCase()) || + w.domain.toLowerCase().includes(search.toLowerCase()), + ); + } + + // Pagination metadata + const page = parseInt(parsedUrl.query.page as string) || 1; + const pageSize = parseInt(parsedUrl.query.pageSize as string) || 10; + const total = filtered.length; + const pageCount = Math.ceil(total / pageSize); + const paginated = filtered.slice((page - 1) * pageSize, page * pageSize); + + return sendEnvelope(res, 200, paginated, { + pagination: { page, pageSize, total, pageCount }, + }); + } + + if (pathname === "/api/websites" && method === "POST") { + if (!requireRole(["owner", "operator", "developer"])) return; + + const bodyStr = await getRequestBody(req); + let body: any = {}; + try { + body = JSON.parse(bodyStr); + } catch { + return sendEnvelope( + res, + 400, + null, + {}, + { code: "VALIDATION_ERROR", message: "Invalid JSON body." }, + ); + } + + const { name, domain, runtime, environment } = body; + + // Input validation + const fields: Record = {}; + if (!name || name.length < 3) { + fields.name = "Use at least three characters."; + } + if (!domain || !domain.includes(".")) { + fields.domain = "Invalid domain syntax."; + } + if (Object.keys(fields).length > 0) { + return sendEnvelope( + res, + 400, + null, + {}, + { + code: "VALIDATION_ERROR", + message: "Validation failed.", + fields, + }, + ); + } + + // Conflict check + if (websites.some((w) => w.name.toLowerCase() === name.toLowerCase())) { + return sendEnvelope( + res, + 409, + null, + {}, + { + code: "VALIDATION_ERROR", + message: "Website name already exists.", + fields: { name: "Website name already exists." }, + }, + ); + } + + // Domain mapping to website CRUD check (for tier3 cross-feature logic) + // Ensure domain exists if it's not a new mapping configuration + if (domain && !domains.some((d) => d.name.toLowerCase() === domain.toLowerCase())) { + // In actual system, you might warning or allow it, but we can enforce mapping checks + // let's just make it healthy + } + + const newSite = { + id: `site-${Math.random().toString(36).substring(2, 9)}`, + name, + domain, + runtime: runtime || "PHP", + status: "healthy", + ssl: "healthy", + environment: environment || "production", + cpuLimit: "1 vCPU", + memoryLimit: "1 GB", + lastDeployment: new Date().toISOString(), + }; + + websites.push(newSite); + + return sendEnvelope(res, 201, newSite); + } + + // Website detail endpoints: /api/websites/:id + if (pathname.startsWith("/api/websites/") && !pathname.endsWith("/dns")) { + const id = pathname.substring("/api/websites/".length); + const index = websites.findIndex((w) => w.id === id); + + if (index === -1) { + return sendEnvelope( + res, + 404, + null, + {}, + { + code: "NOT_FOUND", + message: `Website with ID ${id} not found.`, + }, + ); + } + + if (method === "GET") { + if (!requireAuth()) return; + return sendEnvelope(res, 200, websites[index]); + } + + if (method === "PUT") { + if (!requireRole(["owner", "operator", "developer"])) return; + const bodyStr = await getRequestBody(req); + let body: any = {}; + try { + body = JSON.parse(bodyStr); + } catch { + return sendEnvelope( + res, + 400, + null, + {}, + { code: "VALIDATION_ERROR", message: "Invalid JSON body." }, + ); + } + + websites[index] = { + ...websites[index], + ...body, + }; + return sendEnvelope(res, 200, websites[index]); + } + + if (method === "DELETE") { + if (!requireRole(["owner", "operator"])) return; + const deleted = websites.splice(index, 1)[0]; + return sendEnvelope(res, 200, { deleted: deleted.id }); + } + } + + // 3. DOMAINS ENDPOINTS + if (pathname === "/api/domains" && method === "GET") { + if (!requireAuth()) return; + return sendEnvelope(res, 200, domains); + } + + if (pathname === "/api/domains" && method === "POST") { + if (!requireRole(["owner", "operator"])) return; + + const bodyStr = await getRequestBody(req); + let body: any = {}; + try { + body = JSON.parse(bodyStr); + } catch { + return sendEnvelope( + res, + 400, + null, + {}, + { code: "VALIDATION_ERROR", message: "Invalid JSON body." }, + ); + } + + const { name, type, documentRoot } = body; + const fields: Record = {}; + if (!name || !name.includes(".")) { + fields.name = "Invalid domain syntax."; + } + if (!type) { + fields.type = "Type is required."; + } + if (Object.keys(fields).length > 0) { + return sendEnvelope( + res, + 400, + null, + {}, + { + code: "VALIDATION_ERROR", + message: "Validation failed.", + fields, + }, + ); + } + + // Conflict check + if (domains.some((d) => d.name.toLowerCase() === name.toLowerCase())) { + return sendEnvelope( + res, + 409, + null, + {}, + { + code: "VALIDATION_ERROR", + message: "Domain already exists.", + fields: { name: "Domain already exists." }, + }, + ); + } + + const newDom = { + id: `dom-${Math.random().toString(36).substring(2, 9)}`, + name, + type, + documentRoot: documentRoot || `/home/coaxys/${name}`, + sslStatus: "healthy", + dnssec: false, + propagation: 0, + }; + + domains.push(newDom); + dnsRecords[newDom.id] = [ + { + id: `dns-${Math.random().toString(36).substring(2, 9)}`, + name: "@", + ttl: 14400, + class: "IN", + type: "NS", + record: "ns1.coaxys.com.", + }, + ]; + + return sendEnvelope(res, 201, newDom); + } + + // Domain detail / DNS endpoints + if (pathname.startsWith("/api/domains/")) { + // Parse paths like: + // /api/domains/:id + // /api/domains/:id/dns + // /api/domains/:id/dns/:recordId + const remainingPath = pathname.substring("/api/domains/".length); + const parts = remainingPath.split("/"); + const id = parts[0]; + const domainIndex = domains.findIndex((d) => d.id === id); + + if (domainIndex === -1) { + return sendEnvelope( + res, + 404, + null, + {}, + { + code: "NOT_FOUND", + message: `Domain with ID ${id} not found.`, + }, + ); + } + + const domainObj = domains[domainIndex]; + + // Case: /api/domains/:id + if (parts.length === 1) { + if (method === "GET") { + if (!requireAuth()) return; + return sendEnvelope(res, 200, domainObj); + } + + if (method === "PUT") { + if (!requireRole(["owner", "operator"])) return; + const bodyStr = await getRequestBody(req); + let body: any = {}; + try { + body = JSON.parse(bodyStr); + } catch { + return sendEnvelope( + res, + 400, + null, + {}, + { code: "VALIDATION_ERROR", message: "Invalid JSON body." }, + ); + } + + domains[domainIndex] = { + ...domainObj, + ...body, + }; + return sendEnvelope(res, 200, domains[domainIndex]); + } + + if (method === "DELETE") { + if (!requireRole(["owner", "operator"])) return; + const deleted = domains.splice(domainIndex, 1)[0]; + delete dnsRecords[id]; + + // Clean up dependent websites domains if matching mapping + websites.forEach((w) => { + if (w.domain === deleted.name) { + w.status = "warning"; // domain mapping severed + } + }); + + return sendEnvelope(res, 200, { deleted: deleted.id }); + } + } + + // Case: /api/domains/:id/dns + if (parts[1] === "dns") { + const recordsList = dnsRecords[id] || []; + + if (parts.length === 2) { + if (method === "GET") { + if (!requireAuth()) return; + return sendEnvelope(res, 200, recordsList); + } + + if (method === "POST") { + if (!requireRole(["owner", "operator", "developer"])) return; + const bodyStr = await getRequestBody(req); + let body: any = {}; + try { + body = JSON.parse(bodyStr); + } catch { + return sendEnvelope( + res, + 400, + null, + {}, + { code: "VALIDATION_ERROR", message: "Invalid JSON body." }, + ); + } + + const { name: recName, ttl, type: recType, record, priority } = body; + const fields: Record = {}; + if (!recName) fields.name = "Record name is required."; + if (!ttl || ttl < 60) fields.ttl = "TTL must be at least 60 seconds."; + if ( + !recType || + !["A", "AAAA", "CNAME", "MX", "TXT", "SRV", "CAA", "NS", "PTR"].includes( + recType, + ) + ) { + fields.type = "Invalid DNS record type."; + } + if (!record) fields.record = "Record value is required."; + + if (Object.keys(fields).length > 0) { + return sendEnvelope( + res, + 400, + null, + {}, + { + code: "VALIDATION_ERROR", + message: "Validation failed.", + fields, + }, + ); + } + + // MX exclusivity check + if (recType === "CNAME" && recordsList.some((r) => r.name === recName)) { + return sendEnvelope( + res, + 409, + null, + {}, + { + code: "VALIDATION_ERROR", + message: + "CNAME conflict: A CNAME record cannot coexist with other records for the same name.", + fields: { name: "CNAME record conflict." }, + }, + ); + } + + const newRecord = { + id: `dns-${Math.random().toString(36).substring(2, 9)}`, + name: recName, + ttl, + class: "IN", + type: recType, + record, + priority, + }; + + recordsList.push(newRecord); + dnsRecords[id] = recordsList; + + return sendEnvelope(res, 201, newRecord); + } + } + + // Case: /api/domains/:id/dns/:recordId + if (parts.length === 3) { + const recordId = parts[2]; + const recordIndex = recordsList.findIndex((r) => r.id === recordId); + + if (recordIndex === -1) { + return sendEnvelope( + res, + 404, + null, + {}, + { + code: "NOT_FOUND", + message: `DNS Record with ID ${recordId} not found.`, + }, + ); + } + + if (method === "PUT") { + if (!requireRole(["owner", "operator", "developer"])) return; + const bodyStr = await getRequestBody(req); + let body: any = {}; + try { + body = JSON.parse(bodyStr); + } catch { + return sendEnvelope( + res, + 400, + null, + {}, + { code: "VALIDATION_ERROR", message: "Invalid JSON body." }, + ); + } + + recordsList[recordIndex] = { + ...recordsList[recordIndex], + ...body, + }; + dnsRecords[id] = recordsList; + + return sendEnvelope(res, 200, recordsList[recordIndex]); + } + + if (method === "DELETE") { + if (!requireRole(["owner", "operator", "developer"])) return; + const deleted = recordsList.splice(recordIndex, 1)[0]; + dnsRecords[id] = recordsList; + + return sendEnvelope(res, 200, { deleted: deleted.id }); + } + } + } + } + + // 4. METHOD NOT ALLOWED FALLBACK + // If a route is recognized but method is invalid + if ( + (pathname === "/api/auth/login" || pathname === "/api/auth/logout") && + method !== "POST" + ) { + res.writeHead(405, { "Content-Type": "application/json" }); + return res.end( + JSON.stringify({ + data: null, + meta: { requestId: "req_method_not_allowed" }, + error: { + code: "METHOD_NOT_ALLOWED", + message: `Method ${method} not allowed on this path.`, + }, + }), + ); + } + + // Default Not Found + res.writeHead(404, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + data: null, + meta: { requestId: "req_not_found" }, + error: { code: "NOT_FOUND", message: "Path not found." }, + }), + ); + } catch (err: any) { + // 500 fallback + return sendEnvelope( + res, + 500, + null, + {}, + { + code: "NETWORK_ERROR", + message: err.message || "An unexpected server error occurred.", + }, + ); + } + }); + + server.listen(port); + return server; +}