Files
CoaxysPanel/backend/tests/e2e/tier2.spec.ts
T
arsam 56f544efe1 Import tests: backend/tests/e2e/tier2
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:09:38 +03:30

350 lines
11 KiB
TypeScript

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();
});
});
});