167 lines
6.1 KiB
JavaScript
167 lines
6.1 KiB
JavaScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const root = process.cwd();
|
|
|
|
const normalizeSource = (source) => source.replace(/\r\n/g, "\n");
|
|
const readSource = (relativePath) => normalizeSource(readFileSync(join(root, relativePath), "utf8"));
|
|
|
|
const sessionUserSource = readSource("src/components/session/token/SessionUser.vue");
|
|
const superUserObjectSource = readSource("src/components/session/token/superUserObject.vue");
|
|
|
|
const extractBlock = (source, startToken, endToken) => {
|
|
const startIndex = source.indexOf(startToken);
|
|
if (startIndex === -1) {
|
|
throw new Error(`Unable to find start token: ${startToken}`);
|
|
}
|
|
|
|
const contentStartIndex = startIndex + startToken.length;
|
|
const endIndex = source.indexOf(endToken, contentStartIndex);
|
|
if (endIndex === -1) {
|
|
throw new Error(`Unable to find end token: ${endToken}`);
|
|
}
|
|
|
|
return source.slice(contentStartIndex, endIndex);
|
|
};
|
|
|
|
const parseDirectBindings = (sourceBlock) => {
|
|
const bindings = new Map();
|
|
const pattern = /^\s*([A-Za-z0-9_]+)\s*:\s*([A-Za-z0-9_]+)\s*,\s*$/gm;
|
|
|
|
for (const match of sourceBlock.matchAll(pattern)) {
|
|
bindings.set(match[1], match[2]);
|
|
}
|
|
|
|
return bindings;
|
|
};
|
|
|
|
const parseGetterBindings = (sourceBlock) => {
|
|
const bindings = new Map();
|
|
const pattern = /^\s*get\s+([A-Za-z0-9_]+)\s*\(\)\s*{\s*return\s+([A-Za-z0-9_]+);\s*},?\s*$/gm;
|
|
|
|
for (const match of sourceBlock.matchAll(pattern)) {
|
|
bindings.set(match[1], match[2]);
|
|
}
|
|
|
|
return bindings;
|
|
};
|
|
|
|
const expectedSessionObjectBindings = {
|
|
categories: "Categories",
|
|
products: "Products",
|
|
product_options: "ProductOptions",
|
|
departments: "Departments",
|
|
department_categories: "DepartmentCategories",
|
|
economic_departments: "EconomicDepartments",
|
|
economic_products: "EconomicProducts",
|
|
roles: "Roles",
|
|
permissions: "Permissions",
|
|
collectedOrderInvoices: "CollectedOrderInvoices",
|
|
department_daily_reports: "DepartmentDailyReports",
|
|
goals: "Goals",
|
|
notifications: "Notifications",
|
|
orders: "Orders",
|
|
forms: "Forms",
|
|
bookings: "Bookings",
|
|
Branding: "Branding",
|
|
vehicles: "Vehicles",
|
|
department_notification_sms: "DepartmentNotificationSms",
|
|
department_time_bookings_opening_hours: "DepartmentTimeBookingsOpeningHours",
|
|
department_time_bookings_types: "DepartmentTimeBookingsTypes",
|
|
department_time_bookings_entries: "DepartmentTimeBookingsEntries",
|
|
department_variables: "DepartmentVariables",
|
|
xlvask_vehicle_types: "XLVaskVehicleTypes",
|
|
openai: "OpenAI",
|
|
department_lanes: "DepartmentLanes",
|
|
department_gates: "DepartmentGates",
|
|
department_relays: "DepartmentRelays",
|
|
order_bookings: "OrderBookings",
|
|
module_action_logs: "ModuleActionLogs",
|
|
self_serve_questions: "SelfServeQuestions",
|
|
self_serve_tasks: "SelfServeTasks",
|
|
self_serve_conditions: "SelfServeConditions",
|
|
self_serve_vehicle_conditions: "SelfServeVehicleConditions",
|
|
self_serve_condition_rules: "SelfServeConditionRules",
|
|
self_serve_machine_types: "SelfServeMachineTypes",
|
|
subuser_grants: "SubuserGrants",
|
|
global: "ObjectsGlobal",
|
|
};
|
|
|
|
const expectedSuperUserModuleBindings = {
|
|
economic: "Economic",
|
|
reCAPTCHA: "reCAPTCHA",
|
|
email: "Email",
|
|
backups: "Backups",
|
|
motorapi: "MotorAPI",
|
|
stripe: "Stripe",
|
|
fxratesapi: "FXRatesAPI",
|
|
weatherapi: "WeatherAPI",
|
|
gatewayapi: "GatewayAPI",
|
|
xlvask: "XLVask",
|
|
entra: "Entra",
|
|
limble: "Limble",
|
|
ocrspace: "OcrSpace",
|
|
openai: "OpenAI",
|
|
licenseplaterecognizer: "LicensePlateRecognizer",
|
|
virkdata: "VirkData",
|
|
shelly: "Shelly",
|
|
selfserve: "SelfServe",
|
|
bird: "Bird",
|
|
};
|
|
|
|
const superUserModulesBlock = extractBlock(superUserObjectSource, "modules: {", "\n },\n /** Intimidate a user */");
|
|
const superUserSystemBlock = extractBlock(superUserObjectSource, "system: {", "\n },\n modules: {");
|
|
|
|
const sessionObjectsContentBlock = extractBlock(
|
|
sessionUserSource,
|
|
"objects: {",
|
|
"\n },\n /** The user's token and authentication status */"
|
|
);
|
|
|
|
describe("session token initialization regression contract", () => {
|
|
it("keeps department id lookup decoupled from router module initialization", () => {
|
|
expect(sessionUserSource).not.toContain('import router from "@/router.js";');
|
|
expect(sessionUserSource).not.toContain("router.currentRoute.value.params.departmentId");
|
|
expect(sessionUserSource).toMatch(/const path = window\.location\.pathname \|\| ["']["'];/);
|
|
expect(sessionUserSource).toContain("const match = path.match(/^\\/admin\\/(\\d+)(?:\\/|$)/);");
|
|
expect(sessionUserSource).toContain("const departmentId = Number.parseInt(match[1], 10);");
|
|
expect(sessionUserSource).toContain(
|
|
"return Number.isInteger(departmentId) && departmentId > 0 ? departmentId : undefined;"
|
|
);
|
|
});
|
|
|
|
it("keeps SessionUser.objects eagerly bound for all shared object modules", () => {
|
|
const directBindings = parseDirectBindings(sessionObjectsContentBlock);
|
|
const getterBindings = parseGetterBindings(sessionObjectsContentBlock);
|
|
|
|
expect(getterBindings.size).toBe(0);
|
|
|
|
for (const [key, expectedBinding] of Object.entries(expectedSessionObjectBindings)) {
|
|
expect(directBindings.get(key)).toBe(expectedBinding);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("superuser module initialization regression contract", () => {
|
|
it("keeps superuser system binding lazy to avoid circular-import TDZ errors", () => {
|
|
const systemBindings = parseGetterBindings(superUserSystemBlock);
|
|
expect(systemBindings.get("database")).toBe("DatabaseSystemObject");
|
|
expect(systemBindings.get("redis")).toBe("RedisSystemObject");
|
|
expect(systemBindings.get("minio")).toBe("MinioSystemObject");
|
|
});
|
|
|
|
it("keeps all superuser module bindings lazy at object construction time", () => {
|
|
const moduleGetterBindings = parseGetterBindings(superUserModulesBlock);
|
|
const moduleDirectBindings = parseDirectBindings(superUserModulesBlock);
|
|
|
|
expect(moduleDirectBindings.size).toBe(0);
|
|
|
|
for (const [key, expectedBinding] of Object.entries(expectedSuperUserModuleBindings)) {
|
|
expect(moduleGetterBindings.get(key)).toBe(expectedBinding);
|
|
}
|
|
|
|
expect(superUserObjectSource).toMatch(/get\s+cron\s*\(\)\s*{\s*return\s+Cron;\s*}/);
|
|
});
|
|
});
|