Files
CoaxysPanel/backend/tests/e2e/mock-server.ts
T
arsam b373e07485 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.
2026-09-18 23:09:33 +03:30

884 lines
25 KiB
TypeScript

import http from "http";
import url from "url";
// In-memory state (reset on server start)
let websites: any[] = [];
let domains: any[] = [];
let dnsRecords: Record<string, any[]> = {};
let activeSessions: Record<string, { id: string; name: string; email: string; role: string }> =
{};
let rateLimitTracker: Record<string, number[]> = {};
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<string> {
return new Promise((resolve) => {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
resolve(body);
});
});
}
function parseCookies(cookieHeader?: string): Record<string, string> {
const list: Record<string, string> = {};
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<string, string> = {},
) {
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<string, string> = {};
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<string, string> = {};
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<string, string> = {};
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;
}