Files
pleno-vue/tests/unit/session-token-initialization-contract.spec.js
T
Jeppe Bundgaard 6a8e19b3e2 Refactor and optimize component logic and tests:
- Consolidate route search handling in `PosDepartmentMVP.vue` using `applyPosRouteSearch` for improved maintainability.
- Refactor tab visibility logic in `ElementTabsBox.vue` with reusable `isHidden` utility, removing redundant code.
- Enhance `SessionUser.vue` and `superUserObject.vue` modules with lazy bindings for improved initialization and performance.
- Update `NavigationMenuItemsAdmin.vue` to validate department IDs with `hasValidDepartmentId` and watchers for reactive booking count updates.
- Improve error handling logic in `HandleGlobalError.vue` and `loadOrderItems` for robustness against invalid inputs.
- Add and update regression tests for session and superuser initialization to ensure compliance with binding contracts.
- Refactor POS step components to streamline order-related logic and support immediate reactivity.
2026-03-19 11:25:17 +01:00

178 lines
5.9 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).toContain("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');
});
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).toContain('get cron() { return Cron; }');
});
});