const API_HOST = /https?:\/\/(?:api\.truckwash\.io(?::\d+)?\/.*|localhost(?::\d+)?\/api\/.*|127\.0\.0\.1(?::\d+)?\/api\/.*)/i; const TINY_PNG = Buffer.from( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAukB9pY9ZxQAAAAASUVORK5CYII=", "base64" ); const TINY_PDF = Buffer.from( "%PDF-1.1\n1 0 obj<>endobj\n2 0 obj<>endobj\n3 0 obj<>endobj\ntrailer<>\n%%EOF", "utf8" ); function json(body, status = 200) { return { status, contentType: "application/json", body: JSON.stringify(body), }; } function binary(body, contentType = "image/png", status = 200) { return { status, contentType, body, }; } function getAttachmentPreviewContentType(attachment = null) { const filename = String( attachment?.content?.image || attachment?.content?.document || attachment?.content?.other || "" ).toLowerCase(); if (filename.endsWith(".pdf")) { return { body: TINY_PDF, contentType: "application/pdf", }; } return { body: TINY_PNG, contentType: "image/png", }; } function mergeFixture(base, overrides = {}) { return { ...base, ...overrides, previewByKey: { ...(base.previewByKey || {}), ...(overrides.previewByKey || {}), }, summaryBySessionId: { ...(base.summaryBySessionId || {}), ...(overrides.summaryBySessionId || {}), }, summaryByKey: { ...(base.summaryByKey || {}), ...(overrides.summaryByKey || {}), }, answerResponseByKey: { ...(base.answerResponseByKey || {}), ...(overrides.answerResponseByKey || {}), }, attachmentsByTaskId: { ...(base.attachmentsByTaskId || {}), ...(overrides.attachmentsByTaskId || {}), }, attachmentDownloadByKey: { ...(base.attachmentDownloadByKey || {}), ...(overrides.attachmentDownloadByKey || {}), }, }; } function toSqlDateTime(value = new Date()) { return new Date(value).toISOString().slice(0, 19).replace("T", " "); } function normalizeCreatedAtValue(value) { if (value === null || value === undefined || value === "") { return value; } const normalized = String(value).trim(); const dateTimeMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})(?::(\d{2}))?$/); if (dateTimeMatch) { return `${dateTimeMatch[1]} ${dateTimeMatch[2]}:${dateTimeMatch[3] || "00"}`; } return normalized; } function normalizeRegistrationValue(value) { if (value === null || value === undefined) { return ""; } return String(value) .trim() .toUpperCase() .replace(/[^A-Z0-9]/g, ""); } function normalizeIncludeInInvoiceValue(value) { if (value === null || value === undefined || value === "" || value === "use_department" || value === "null") { return null; } if (value === true || value === 1 || value === "1" || value === "true" || value === "include") { return true; } if (value === false || value === 0 || value === "0" || value === "false" || value === "exclude") { return false; } return null; } function normalizeSafetySealValue(value) { if (value === null || value === undefined) { return ""; } return String(value).trim(); } function normalizePositiveIntegerValue(value) { const parsed = Number.parseInt(String(value ?? ""), 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : null; } function isWashCertificateProduct(product) { const productId = Number(product?.id ?? product?.product_id ?? product?.product?.id ?? 0); if (productId === 41) { return true; } return /vaskecertifikat|wash certificate|safety seal/i.test(String(product?.name ?? product?.product?.name ?? "")); } function orderContainsWashCertificate(posFixture, orderId) { return (posFixture.orderItemsByOrderId[orderId] || []).some((item) => isWashCertificateProduct(item?.product || item) ); } function ensureWashCertificateAttachment(posFixture, orderId) { if (!Array.isArray(posFixture.attachmentsByOrderId[orderId])) { posFixture.attachmentsByOrderId[orderId] = []; } const existingAttachment = (posFixture.attachmentsByOrderId[orderId] || []).find((attachment) => { return String(attachment?.content?.other || "").toUpperCase() === "WASH_CERTIFICATE"; }) || null; if (existingAttachment) { return existingAttachment; } const attachmentId = posFixture.nextAttachmentId++; const attachment = { id: attachmentId, object_type: "orders", object_id: orderId, content: { image: null, document: `wash_certificate_${orderId}.pdf`, relation: null, other: "WASH_CERTIFICATE", src: null, }, created_at: toSqlDateTime(), updated_at: toSqlDateTime(), deleted_at: null, }; posFixture.attachmentsByOrderId[orderId].push(attachment); return attachment; } function resolveDepartmentIncludeInInvoice(posFixture, departmentId) { const department = (posFixture.departments || []).find((entry) => Number(entry.id) === Number(departmentId)); if (!department) { return true; } if (typeof department.exclude_from_invoicing === "boolean") { return !department.exclude_from_invoicing; } if (typeof department.include_in_invoice === "boolean") { return department.include_in_invoice; } return true; } function withEffectiveOrderState(posFixture, order) { if (!order) { return order; } const includeInInvoice = normalizeIncludeInInvoiceValue(order.include_in_invoice); const orderId = Number(order.id || 0); const attachments = Array.isArray(order.attachments) ? order.attachments : Array.isArray(posFixture.attachmentsByOrderId?.[orderId]) ? posFixture.attachmentsByOrderId[orderId] : []; return { ...order, attachments, include_in_invoice: includeInInvoice, include_in_invoice_effective: includeInInvoice === null ? resolveDepartmentIncludeInInvoice(posFixture, order.department_id) : includeInInvoice, }; } function parseFilterExpressions(filters) { return String(filters || "") .split(",") .map((entry) => entry.trim()) .filter(Boolean) .reduce((result, entry) => { const separatorIndex = entry.indexOf(":"); if (separatorIndex === -1) { return result; } const key = entry.slice(0, separatorIndex); const value = entry.slice(separatorIndex + 1); if (key) { result[key] = value; } return result; }, {}); } function toOrderBookingListEntry(booking, stripDetails = false) { if (!stripDetails || !booking || typeof booking !== "object") { return booking; } const summaryBooking = { ...booking }; delete summaryBooking.items; delete summaryBooking.parsed_services; return summaryBooking; } function filterPosOrders(posFixture, filters) { const filterMap = parseFilterExpressions(filters); return Object.values(posFixture.ordersById || {}).filter((order) => { if (filterMap.id && Number(order.id) !== Number(filterMap.id)) { return false; } if (filterMap.customer_id && Number(order.customer_id) !== Number(filterMap.customer_id)) { return false; } if (filterMap.department_id && Number(order.department_id) !== Number(filterMap.department_id)) { return false; } if (filterMap.reg_1 && String(order.reg_1 || "").toUpperCase() !== String(filterMap.reg_1).toUpperCase()) { return false; } const createdAtDate = String(order.created_at || "").slice(0, 10); if (filterMap["created_at-date_from"] && (!createdAtDate || createdAtDate < filterMap["created_at-date_from"])) { return false; } if (filterMap["created_at-date_to"] && (!createdAtDate || createdAtDate > filterMap["created_at-date_to"])) { return false; } return true; }); } function filterNumberPlateScans(posFixture, { filters = "", search = "", order = "created_at:desc" } = {}) { const filterMap = parseFilterExpressions(filters); const searchValue = String(search || "") .trim() .toUpperCase(); const [orderBy = "created_at", orderDirection = "desc"] = String(order || "created_at:desc").split(":"); const filteredRows = (posFixture.numberPlateScans || []).filter((scan) => { if (filterMap.department_id && Number(scan.department_id) !== Number(filterMap.department_id)) { return false; } if ( searchValue && !String(scan.plate || "") .toUpperCase() .includes(searchValue) ) { return false; } return true; }); return filteredRows.sort((left, right) => { const leftValue = left?.[orderBy]; const rightValue = right?.[orderBy]; const leftComparable = typeof leftValue === "string" ? leftValue.toUpperCase() : leftValue; const rightComparable = typeof rightValue === "string" ? rightValue.toUpperCase() : rightValue; if (leftComparable === rightComparable) { return 0; } if (leftComparable > rightComparable) { return orderDirection === "desc" ? -1 : 1; } return orderDirection === "desc" ? 1 : -1; }); } function paginateRows(rows, page = 1, limit = 10) { const currentPage = Math.max(1, Number.parseInt(page, 10) || 1); const perPage = Math.max(1, Number.parseInt(limit, 10) || 10); const startIndex = (currentPage - 1) * perPage; const pagedRows = rows.slice(startIndex, startIndex + perPage); return { rows: pagedRows, meta: { pagination: { current_page: currentPage, per_page: perPage, total: rows.length, }, }, }; } function createSelfServeFixture(overrides = {}) { const departments = [ { id: 1, name: "Copenhagen", address: "Alpha 1", latitude: 55.6761, longitude: 12.5683, self_serve_enabled: true, lanes: [ { id: 7, department: 1, name: "7", status: "AVAILABLE", machine_available: true, products: [2, 3], relay_in_id: "IN-7", relay_out_id: "OUT-7", relay_machine_id: "M-7", relay_machine_program_picker_id: "MPP-7", relay_machine_cleaner_id: "MC-7", dynamic_image_id: 77, machine_type_id: null, }, { id: 8, department: 1, name: "8", status: "FAULT", machine_available: false, products: [2, 3], relay_in_id: "IN-8", relay_out_id: "OUT-8", relay_machine_id: "M-8", relay_machine_program_picker_id: "MPP-8", relay_machine_cleaner_id: "MC-8", dynamic_image_id: 78, machine_type_id: null, }, ], }, { id: 2, name: "Odense", address: "Beta 2", latitude: 55.4038, longitude: 10.4024, self_serve_enabled: true, lanes: [ { id: 9, department: 2, name: "9", status: "AVAILABLE", machine_available: true, products: [2, 4], relay_in_id: "IN-9", relay_out_id: "OUT-9", relay_machine_id: "M-9", relay_machine_program_picker_id: "MPP-9", relay_machine_cleaner_id: "MC-9", dynamic_image_id: 79, machine_type_id: null, }, ], }, { id: 3, name: "Aarhus", address: "Gamma 3", latitude: 56.1629, longitude: 10.2039, self_serve_enabled: true, lanes: [ { id: 10, department: 3, name: "10", status: "AVAILABLE", machine_available: false, products: [3, 4], relay_in_id: "IN-10", relay_out_id: "OUT-10", relay_machine_id: "M-10", relay_machine_program_picker_id: "MPP-10", relay_machine_cleaner_id: "MC-10", dynamic_image_id: 80, machine_type_id: null, }, ], }, ]; const products = [ { id: 2, name: "Truck", price: 100, description: "Large truck", piktogram: "truck", is_wash: true, subscription_allowed: true, category: 4, }, { id: 3, name: "Van", price: 80, description: "Medium van", piktogram: "van", is_wash: true, subscription_allowed: true, category: 4, }, { id: 4, name: "Car", price: 60, description: "Small car", piktogram: "car", is_wash: true, subscription_allowed: true, category: 4, }, ]; const baseFixture = { departments, departmentLanes: departments.flatMap((department) => department.lanes), products, customerVehicles: [ { id: 1, reg: "AB12345", type: 2 }, { id: 2, reg: "CD67890", type: 3 }, ], previewByKey: { "7:AB12345": { allowed: true, machine_available: true, lane: { id: 7, name: "7" }, session: { id: 501, status: "IN_PROGRESS", allowed: true }, questions: [ { id: 11, question: "Is the tarp removed?", description: "Required before machine wash.", answer: null, order_priority: 1, }, ], conditions: [], rules: [], tasks: [ { id: 9001, task: "Prepare the truck", description: "Complete the pre-wash checks.", order_priority: 1, services: ["MACHINE"], buttons: [1], }, ], allowed_services: ["MACHINE"], }, "7:ZZ00000": { allowed: true, machine_available: true, lane: { id: 7, name: "7" }, session: { id: 601, status: "IN_PROGRESS", allowed: true }, questions: [{ id: 21, question: "Ready for direct wash?", answer: null, order_priority: 1 }], conditions: [], rules: [], tasks: [], allowed_services: ["MACHINE"], }, }, summaryBySessionId: { 501: { session: { id: 501, status: "IN_PROGRESS", allowed: true }, lane: { id: 7, name: "7" }, questions: [ { id: 11, question: "Is the tarp removed?", description: "Required before machine wash.", answer: null, order_priority: 1, }, ], conditions: [], rules: [], tasks: [ { id: 9001, task: "Prepare the truck", description: "Complete the pre-wash checks.", order_priority: 1, services: ["MACHINE"], buttons: [1], }, ], events: [{ id: 1, type: "STARTED", created_at: "2026-01-01T10:00:00.000Z" }], }, 601: { session: { id: 601, status: "IN_PROGRESS", allowed: true }, lane: { id: 7, name: "7" }, questions: [{ id: 21, question: "Ready for direct wash?", answer: null, order_priority: 1 }], conditions: [], rules: [], tasks: [], events: [{ id: 2, type: "STARTED", created_at: "2026-01-01T11:00:00.000Z" }], }, }, summaryByKey: {}, answerResponseByKey: { "7:AB12345:11:true": { session: { id: 501, status: "IN_PROGRESS", allowed: true }, lane: { id: 7, name: "7" }, questions: [ { id: 11, question: "Is the tarp removed?", description: "Required before machine wash.", answer: true, order_priority: 1, }, ], conditions: [], rules: [], tasks: [ { id: 9001, task: "Prepare the truck", description: "Complete the pre-wash checks.", order_priority: 1, services: ["MACHINE"], buttons: [1], }, ], events: [{ id: 3, type: "QUESTION_ANSWERED", created_at: "2026-01-01T10:01:00.000Z" }], }, "7:ZZ00000:21:true": { session: { id: 601, status: "IN_PROGRESS", allowed: true }, lane: { id: 7, name: "7" }, questions: [{ id: 21, question: "Ready for direct wash?", answer: true, order_priority: 1 }], conditions: [], rules: [], tasks: [], events: [{ id: 4, type: "QUESTION_ANSWERED", created_at: "2026-01-01T11:01:00.000Z" }], }, }, attachmentsByTaskId: { 9001: [ { id: 301, content: { other: "prep.pdf" } }, { id: 302, content: { other: "prep.jpg" } }, ], }, attachmentDownloadByKey: { "9001:301": { download_link: "https://cdn.example.test/prep.pdf" }, "9001:302": { download_link: "https://cdn.example.test/prep.jpg" }, }, laneAllowedServices: ["MACHINE"], commandResponse: { success: true }, relayResponse: { success: true }, dynamicImage: TINY_PNG, }; const fixture = mergeFixture(baseFixture, overrides); fixture.departmentLanes = fixture.departments.flatMap((department) => department.lanes || []); fixture.laneById = fixture.departmentLanes.reduce((accumulator, lane) => { accumulator[String(lane.id)] = lane; return accumulator; }, {}); return fixture; } function mergeEdgeGatewayFixtureRecord(baseGateway, overrides = {}) { return { ...baseGateway, ...overrides, metadata: { ...(baseGateway.metadata || {}), ...(overrides.metadata || {}), system_metrics: { ...(baseGateway.metadata?.system_metrics || {}), ...(overrides.metadata?.system_metrics || {}), }, }, inventory: Array.isArray(overrides.inventory) ? overrides.inventory : baseGateway.inventory, bindings: Array.isArray(overrides.bindings) ? overrides.bindings : baseGateway.bindings, recent_commands: Array.isArray(overrides.recent_commands) ? overrides.recent_commands : baseGateway.recent_commands, audit_logs: Array.isArray(overrides.audit_logs) ? overrides.audit_logs : baseGateway.audit_logs, operations: Array.isArray(overrides.operations) ? overrides.operations : baseGateway.operations || [], }; } function createFixtureOperation(type, request = {}, overrides = {}) { const startedAt = overrides.started_at || null; const completedAt = overrides.completed_at || null; return { id: overrides.id || Date.now(), type, status: overrides.status || "PENDING", request, summary: overrides.summary || { label: overrides.status === "COMPLETED" ? "Completed" : overrides.status === "FAILED" ? "Failed" : "Queued", progress: overrides.status === "COMPLETED" ? 100 : overrides.status === "FAILED" ? 100 : 0, retryable: overrides.status !== "FAILED", }, result: overrides.result || {}, error_code: overrides.error_code || null, error_message: overrides.error_message || null, requested_at: overrides.requested_at || toSqlDateTime(), started_at: startedAt, completed_at: completedAt, created_at: overrides.created_at || toSqlDateTime(), updated_at: overrides.updated_at || completedAt || startedAt || toSqlDateTime(), events: Array.isArray(overrides.events) ? overrides.events : [], }; } function buildEdgeGatewayOperationSummary(operations = []) { return operations.reduce( (summary, operation, index) => { const status = String(operation.status || "PENDING"); if (status === "PENDING") summary.pending += 1; if (status === "IN_PROGRESS") summary.in_progress += 1; if (status === "COMPLETED") { summary.completed += 1; summary.latest_completed_at = summary.latest_completed_at || operation.completed_at || null; } if (status === "FAILED") { summary.failed += 1; summary.latest_failed_at = summary.latest_failed_at || operation.completed_at || null; } if (index === 0) { summary.latest_type = operation.type || null; summary.latest_status = operation.status || null; } return summary; }, { total: operations.length, pending: 0, in_progress: 0, completed: 0, failed: 0, latest_completed_at: null, latest_failed_at: null, latest_type: null, latest_status: null, } ); } function buildEdgeGatewayRuntimeFixture(gateway) { const relayHealth = (gateway.bindings || []).map((binding) => { const fallbackMode = binding.fallback_mode || "PREFER_LOCAL"; const executionPath = gateway.department_transport_mode === "cloud" || fallbackMode === "CLOUD_ONLY" ? "cloud" : "local"; const reason = gateway.department_transport_mode === "cloud" ? "department_cutover" : fallbackMode === "CLOUD_ONLY" ? "binding_cloud_only" : null; return { binding_id: binding.id, relay_id: binding.relay_id, fallback_mode: fallbackMode, execution_path: executionPath, reason, recommended_action: reason ? "review_binding_override" : null, device_freshness_state: "READY", }; }); const cloudRelays = relayHealth.filter((relay) => relay.execution_path === "cloud"); const operations = Array.isArray(gateway.operations) ? gateway.operations : []; const activeOperation = operations.find((operation) => ["PENDING", "IN_PROGRESS"].includes(String(operation.status))) || null; const versionDrift = gateway.installed_version && gateway.target_version && gateway.installed_version !== gateway.target_version; const credentialFreshnessState = gateway.metadata?.credentials_rotated_at ? "FRESH" : "UNKNOWN"; const diagnostics = []; if (gateway.status === "OFFLINE") { diagnostics.push({ code: "EDGE_GATEWAY_OFFLINE", message: "Gateway heartbeat has expired and the gateway is offline.", recommended_action: "restart_agent", }); } if (versionDrift) { diagnostics.push({ code: "EDGE_GATEWAY_VERSION_DRIFT", message: "Installed gateway version differs from the target version.", recommended_action: "queue_update", }); } return { ...gateway, channel_status: gateway.channel_status || { command: { preferred: gateway.metadata?.broker_connected ? "BROKER_FAST_PATH" : "API_POLLING", active: gateway.metadata?.broker_connected ? "BROKER_FAST_PATH" : "API_POLLING", state: gateway.status === "OFFLINE" ? "OFFLINE" : gateway.metadata?.broker_connected ? "ONLINE" : "DEGRADED", backlog_depth: 0, last_success_at: gateway.last_heartbeat_at, }, broker: { connected: Boolean(gateway.metadata?.broker_connected), state: gateway.metadata?.broker_connected ? "ONLINE" : "OFFLINE", last_error: gateway.metadata?.broker_connected ? null : "Broker unavailable", }, }, transport_health: gateway.transport_health || { status: cloudRelays.length ? "DEGRADED" : gateway.status, summary: cloudRelays.length ? `${cloudRelays.length} relæ(er) kører via cloud fallback` : "Broker fast path er aktiv med API polling som fallback", recommended_action: cloudRelays.length ? "review_binding_override" : null, }, fallback_summary: gateway.fallback_summary || { local_relays: relayHealth.filter((relay) => relay.execution_path === "local").length, cloud_relays: cloudRelays.length, local_only_relays: relayHealth.filter((relay) => relay.fallback_mode === "LOCAL_ONLY").length, cloud_only_relays: relayHealth.filter((relay) => relay.fallback_mode === "CLOUD_ONLY").length, affected_relays: cloudRelays.map((relay) => relay.relay_id), recommended_action: cloudRelays.length ? "review_binding_override" : null, }, relay_health: gateway.relay_health || relayHealth, last_successful_command_at: gateway.last_successful_command_at || gateway.last_heartbeat_at, last_successful_discovery_at: gateway.last_successful_discovery_at || gateway.last_heartbeat_at, active_operation: gateway.active_operation || activeOperation, recent_operations_summary: gateway.recent_operations_summary || buildEdgeGatewayOperationSummary(operations), version_drift: gateway.version_drift || { installed_version: gateway.installed_version || null, target_version: gateway.target_version || null, release_channel: gateway.release_channel || "stable", is_drifted: Boolean(versionDrift), status: versionDrift ? "UPDATE_AVAILABLE" : "IN_SYNC", }, credential_freshness: gateway.credential_freshness || { rotated_at: gateway.metadata?.credentials_rotated_at || null, age_days: gateway.metadata?.credentials_rotated_at ? 1 : null, state: credentialFreshnessState, }, diagnostics: gateway.diagnostics || diagnostics, error_state: gateway.error_state || (diagnostics[0] ? { ...diagnostics[0] } : activeOperation?.error_code ? { code: activeOperation.error_code, message: activeOperation.error_message } : null), }; } function createEdgeGatewayFixture(options = {}) { const primaryBrokerConnected = options.brokerConnected ?? true; const gatewayOverridesById = new Map( (Array.isArray(options.gatewayOverrides) ? options.gatewayOverrides : []).map((gateway) => [ Number(gateway.id), gateway, ]) ); const baseGateways = [ { id: 701, department_id: 1, label: "CPH Edge 01", hostname: "cph-edge-01", is_primary: true, status: "ONLINE", transport_mode: "gateway", department_transport_mode: "gateway", release_channel: "stable", installed_version: "1.2.0", target_version: "1.2.1", last_heartbeat_at: "2026-04-08 08:15:00", last_seen_ip: "10.1.0.14", discovery_status: "READY", metadata: { broker_connected: primaryBrokerConnected, system_metrics: { latency_ms: 184, cpu_usage_pct: 27, memory_usage_pct: 61, memory_used_bytes: 2621440000, memory_total_bytes: 4294967296, disk_usage_pct: 58, disk_used_bytes: 249108103168, disk_total_bytes: 429496729600, disk_mount: "/", }, }, inventory: [ { id: 1, device_id: "shelly-plus-01", local_ip: "10.1.0.31", model: "Shelly Plus 2PM", channel_count: 2, online: true, capabilities: { generation: 2 }, }, { id: 2, device_id: "shelly-mini-offline", local_ip: "10.1.0.34", model: "Shelly Mini 1", channel_count: 1, online: false, capabilities: { generation: 3 }, }, ], bindings: [ { id: 1, relay_id: "M-7", device_id: "shelly-plus-01", local_ip: "10.1.0.31", channel: 0, binding_source: "MANUAL", fallback_mode: "PREFER_LOCAL", }, { id: 2, relay_id: "M-7-LEGACY", device_id: "shelly-missing-legacy", local_ip: "10.1.0.99", channel: 1, binding_source: "MANUAL", fallback_mode: "CLOUD_ONLY", }, ], recent_commands: [], operations: [ createFixtureOperation("DISCOVERY", {}, { id: 8801, status: "COMPLETED", started_at: "2026-04-08 08:14:20", completed_at: "2026-04-08 08:14:38", events: [ { id: 1, level: "INFO", code: "OPERATION_STARTED", message: "Gateway started processing the operation", created_at: "2026-04-08 08:14:20" }, { id: 2, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: "2026-04-08 08:14:38" }, ], }), ], audit_logs: [{ id: 501, created_at: "2026-04-08 08:16:00", action: "GATEWAY_CLAIMED", actor_type: "USER" }], }, { id: 702, department_id: 2, label: "ODE Edge 01", hostname: "ode-edge-01", is_primary: true, status: "OFFLINE", transport_mode: "gateway", department_transport_mode: "cloud", release_channel: "stable", installed_version: "1.1.0", target_version: "1.1.0", last_heartbeat_at: "2026-04-07 21:04:00", last_seen_ip: "10.2.0.14", discovery_status: "STALE", metadata: { broker_connected: false, system_metrics: { latency_ms: 412, cpu_usage_pct: 9, memory_usage_pct: 42, memory_used_bytes: 1073741824, memory_total_bytes: 2147483648, disk_usage_pct: 76, disk_used_bytes: 163208757248, disk_total_bytes: 214748364800, disk_mount: "/", }, }, inventory: [], bindings: [], recent_commands: [], operations: [], audit_logs: [{ id: 502, created_at: "2026-04-07 21:05:00", action: "HEARTBEAT_TIMEOUT", actor_type: "SYSTEM" }], }, ].map((gateway) => buildEdgeGatewayRuntimeFixture( mergeEdgeGatewayFixtureRecord(gateway, gatewayOverridesById.get(Number(gateway.id)) || {}) ) ); return { pendingDiscoveryByGatewayId: { ...(options.pendingDiscoveryByGatewayId || {}), }, gateways: baseGateways, }; } function settleEdgeGatewayWork(edgeGatewayFixture, gatewayId) { const gateway = edgeGatewayFixture.gateways.find((entry) => entry.id === gatewayId) || null; if (!gateway) { return null; } const pendingDiscovery = edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId]; if (pendingDiscovery) { pendingDiscovery.fetchCount = (pendingDiscovery.fetchCount || 0) + 1; if (pendingDiscovery.fetchCount >= 2) { gateway.discovery_status = "READY"; gateway.last_successful_discovery_at = toSqlDateTime(); if (!gateway.inventory.some((device) => device.device_id === pendingDiscovery.device.device_id)) { gateway.inventory = [...gateway.inventory, pendingDiscovery.device]; } gateway.operations = (gateway.operations || []).map((operation) => Number(operation.id) === Number(pendingDiscovery.operationId) ? { ...operation, status: "COMPLETED", completed_at: gateway.last_successful_discovery_at, updated_at: gateway.last_successful_discovery_at, summary: { ...(operation.summary || {}), label: "Completed", progress: 100, retryable: true }, result: { inventory: cloneJson(gateway.inventory) }, events: [ ...(operation.events || []), { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: gateway.last_successful_discovery_at, }, ], } : operation ); delete edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId]; } } Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); return gateway; } function cloneJson(value) { return JSON.parse(JSON.stringify(value)); } function buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, includeDetail = false) { const inventory = cloneJson(gateway.inventory || []); const bindings = cloneJson(gateway.bindings || []); const operations = cloneJson(gateway.operations || []); return { ...cloneJson(gateway), readiness: { status: gateway.status, last_heartbeat_at: gateway.last_heartbeat_at, heartbeat_age_seconds: null, discovery_status: gateway.discovery_status, }, inventory_summary: { total: inventory.length, online: inventory.filter((device) => device.online !== false).length, offline: inventory.filter((device) => device.online === false).length, last_discovery_at: gateway.last_successful_discovery_at || null, }, binding_summary: { total: bindings.length, fallback_overrides: bindings.filter((binding) => String(binding.fallback_mode || "PREFER_LOCAL") !== "PREFER_LOCAL").length, }, agent_runtime: { hostname: gateway.hostname, installed_version: gateway.installed_version, target_version: gateway.target_version, last_seen_ip: gateway.last_seen_ip, last_heartbeat_at: gateway.last_heartbeat_at, system_metrics: cloneJson(gateway.metadata?.system_metrics || {}), }, operations, active_operation: cloneJson(gateway.active_operation || null), recent_operations_summary: cloneJson(gateway.recent_operations_summary || buildEdgeGatewayOperationSummary(operations)), version_drift: cloneJson(gateway.version_drift || null), credential_freshness: cloneJson(gateway.credential_freshness || null), diagnostics: cloneJson(gateway.diagnostics || []), error_state: cloneJson(gateway.error_state || null), inventory: includeDetail ? inventory : undefined, bindings: includeDetail ? bindings : undefined, audit_logs: includeDetail ? cloneJson(gateway.audit_logs || []) : undefined, }; } function processPendingEdgeGatewayClaims(edgeGatewayFixture) { edgeGatewayFixture.pendingClaims = (edgeGatewayFixture.pendingClaims || []).filter((claim) => { claim.pollsRemaining -= 1; if (claim.pollsRemaining > 0) { return true; } const gatewayId = edgeGatewayFixture.nextGatewayId++; edgeGatewayFixture.gateways.push({ id: gatewayId, department_id: Number(claim.department_id || 1), label: claim.label || `Gateway ${gatewayId}`, hostname: `edge-${gatewayId}`, is_primary: true, status: "ONLINE", transport_mode: "gateway", department_transport_mode: "gateway", installed_version: "php-agent-v1", target_version: "php-agent-v1", last_heartbeat_at: toSqlDateTime(), last_seen_ip: "10.9.0.14", discovery_status: "READY", metadata: { broker_connected: true, system_metrics: { latency_ms: 73, cpu_usage_pct: 19, memory_usage_pct: 44, disk_usage_pct: 52, }, }, inventory: [], bindings: [], recent_commands: [], operations: [], audit_logs: [ { id: Date.now(), created_at: toSqlDateTime(), action: "GATEWAY_CLAIMED", actor_type: "USER", }, ], }); return false; }); } function createHttpEdgeGatewayFixture(options = {}) { const gatewayOverridesById = new Map( (Array.isArray(options.gatewayOverrides) ? options.gatewayOverrides : []).map((gateway) => [Number(gateway.id), gateway]) ); const extraGateways = Array.isArray(options.gateways) ? options.gateways : []; const baseGateways = options.empty ? [] : [ { id: 701, department_id: 1, label: "CPH Edge 01", hostname: "cph-edge-01", is_primary: true, status: "ONLINE", transport_mode: "gateway", department_transport_mode: "gateway", installed_version: "php-agent-v1", target_version: "php-agent-v1.1", last_heartbeat_at: "2026-04-08 08:15:00", last_seen_ip: "10.1.0.14", discovery_status: "READY", metadata: { broker_connected: true, system_metrics: { latency_ms: 184, cpu_usage_pct: 27, memory_usage_pct: 61, disk_usage_pct: 58, }, }, inventory: [ { id: 1, device_id: "shelly-plus-01", local_ip: "10.1.0.31", model: "Shelly Plus 2PM", channel_count: 2, online: true }, { id: 2, device_id: "shelly-mini-offline", local_ip: "10.1.0.34", model: "Shelly Mini 1", channel_count: 1, online: false }, ], bindings: [ { id: 1, relay_id: "M-7", device_id: "shelly-plus-01", local_ip: "10.1.0.31", channel: 0, fallback_mode: "PREFER_LOCAL" }, { id: 2, relay_id: "M-7-LEGACY", device_id: "shelly-missing-legacy", local_ip: "10.1.0.99", channel: 1, fallback_mode: "CLOUD_ONLY" }, ], recent_commands: [], operations: [ createFixtureOperation("DISCOVERY", {}, { id: 8801, status: "COMPLETED", started_at: "2026-04-08 08:14:20", completed_at: "2026-04-08 08:14:38", events: [ { id: 1, level: "INFO", code: "OPERATION_STARTED", message: "Gateway started processing the operation", created_at: "2026-04-08 08:14:20" }, { id: 2, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: "2026-04-08 08:14:38" }, ], }), ], audit_logs: [{ id: 501, created_at: "2026-04-08 08:16:00", action: "GATEWAY_CLAIMED", actor_type: "USER" }], }, { id: 702, department_id: 2, label: "ODE Edge 01", hostname: "ode-edge-01", is_primary: true, status: "OFFLINE", transport_mode: "gateway", department_transport_mode: "cloud", installed_version: "php-agent-v1", target_version: "php-agent-v1", last_heartbeat_at: "2026-04-07 21:04:00", last_seen_ip: "10.2.0.14", discovery_status: "STALE", metadata: { broker_connected: false, system_metrics: { latency_ms: 412, cpu_usage_pct: 9, memory_usage_pct: 42, disk_usage_pct: 76, }, }, inventory: [], bindings: [], recent_commands: [], operations: [], audit_logs: [{ id: 502, created_at: "2026-04-07 21:05:00", action: "HEARTBEAT_TIMEOUT", actor_type: "SYSTEM" }], }, ] .map((gateway) => mergeEdgeGatewayFixtureRecord(gateway, gatewayOverridesById.get(Number(gateway.id)) || {})) .concat(extraGateways.map((gateway) => cloneJson(gateway))); return { nextGatewayId: Math.max(703, ...baseGateways.map((gateway) => Number(gateway.id) + 1)), nextOperationId: 9901, nextOperationEventId: 19901, claimPollsRemaining: Number(options.claimPollsRemaining || 2), pendingClaims: [], pendingDiscoveryByGatewayId: {}, gateways: baseGateways, }; } export function createPosFixture(overrides = {}) { const defaultCustomer = { id: 1, customerNumber: 12345679, name: "(TEST) Pleno Vognmandsforretning", address: "Demo Street 1", zip: "2630", city: "Taastrup", mobilePhone: "12345678", email: "2jepp9350@gmail.com", corporateIdentificationNumber: "12345678", economic_customer: 12345679, barred: false, }; const cardCustomer = { id: 2, customerNumber: 999, name: "Card Terminal Customer", address: "Terminal Street 9", zip: "2630", city: "Taastrup", mobilePhone: "87654321", email: "card@example.com", corporateIdentificationNumber: "99999999", economic_customer: 999, barred: false, }; const products = [ { id: 53, name: "Forvogn", description: "Primary wash product", price: 649, subscription_allowed: true, category: 4, piktogram: "truck", apply_category_discount: true, requires_note: false, is_wash: true, display_in_booking_form: true, order_priority: 1, addons: [], }, { id: 63, name: "Indvendig vask Forvogn", description: "Add-on wash service", price: 399, subscription_allowed: true, category: 4, piktogram: "truck", apply_category_discount: true, requires_note: false, is_wash: false, display_in_booking_form: true, order_priority: 2, addons: [], }, { id: 64, name: "Vaskecertifikat - Safety Seal", description: "Safety seal", price: 25, subscription_allowed: true, category: 8, piktogram: "certificate", apply_category_discount: false, requires_note: false, is_wash: false, display_in_booking_form: true, order_priority: 3, addons: [], }, { id: 65, name: "Ekstraordinær pr. 10 min inkl. kemi", description: "Additional time", price: 299, subscription_allowed: true, category: 8, piktogram: "timer", apply_category_discount: false, requires_note: false, is_wash: false, display_in_booking_form: true, order_priority: 4, addons: [], }, ]; const baseFixture = { departments: [ { id: 1, name: "Taastrup", exclude_from_invoicing: true }, { id: 12, name: "Demo", exclude_from_invoicing: false }, ], customersByNumber: { [defaultCustomer.customerNumber]: defaultCustomer, [cardCustomer.customerNumber]: cardCustomer, }, collectedInvoices: [ { id: 101, customer_number: defaultCustomer.customerNumber, customer_name: defaultCustomer.name, total_net_amount: 100, created_at: "2026-04-10", closed_at: "2026-04-10", }, { id: 200, customer_number: cardCustomer.customerNumber, customer_name: cardCustomer.name, total_net_amount: 250, created_at: "2026-04-30", closed_at: "2026-04-30", }, { id: 201, customer_number: cardCustomer.customerNumber, customer_name: cardCustomer.name, total_net_amount: 325, created_at: "2026-05-01", closed_at: "2026-05-01", }, ...[102, 103, 104, 105, 106].map((invoiceId, index) => ({ id: invoiceId, customer_number: 1002 + index, customer_name: `Customer ${invoiceId}`, total_net_amount: 110 + index * 10, })), ], nextCollectedInvoiceId: 300, customerAttributesByNumber: { [defaultCustomer.customerNumber]: [ { id: 1, customer_number: defaultCustomer.customerNumber, attribute: "invoiceAllOrdersIndividually" }, ], [cardCustomer.customerNumber]: [ { id: 2, customer_number: cardCustomer.customerNumber, attribute: "invoiceWithStripe" }, ], }, customerNotesByNumber: {}, products, departmentCategories: [ { id: 11, department_id: 12, category: { id: 4, name: "Udvendig", meta: { products: [53, 63] } } }, { id: 12, department_id: 12, category: { id: 8, name: "Tillæg", meta: { products: [64, 65] } } }, ], vehicles: [ { id: 7001, reg: "EC21235", customer_id: defaultCustomer.customerNumber, customer_name: defaultCustomer.name, type: 53, status: "verified", barred: false, wash_subscription: false, addons: { enabled: 0, available: 0, list: [] }, reference: "EC21233 - Test Ref. / Intern nummer", last_order_id: 54518, }, ], unknownVehicles: [], orderBookings: [], bookingOrderAssignments: [], completedBookingIds: [], nextOrderBookingId: 9001, numberPlateScanners: [ { id: 1, name: "North scanner" }, { id: 2, name: "South scanner" }, ], numberPlateScans: [ { id: 801, department_id: 12, plate: "AB12345", plate_scanner_id: 1, created_at: "2026-04-08 08:44:07", customer_number: defaultCustomer.customerNumber, customer_name: defaultCustomer.name, seen_before: true, barred: false, }, { id: 802, department_id: 12, plate: "CD67890", plate_scanner_id: 2, created_at: "2026-04-08 08:31:05", customer_number: null, customer_name: "", seen_before: false, barred: false, }, ], motorApiLookupByPlate: { AB12345: { make: "RENAULT", model: "Captur", variant: "dCi 90", type: "Personbil", use: "Privat personkørsel", }, CD67890: { make: "SCANIA", model: "R500", variant: "Highline", type: "Lastbil", use: "Godstransport", }, }, ordersById: { 54518: { id: 54518, customer_id: defaultCustomer.customerNumber, department_id: 12, reference: "EC21233 - Test Ref. / Intern nummer", po: "", safety_seal: "", notes: "", reg_1: "EC21235", reg_2: "", reg_3: "", invoice_collection_id: null, booking_id: null, completed_at: null, closed_at: null, created_at: "2026-04-08 08:44:07", include_in_invoice: null, }, }, orderItemsByOrderId: { 54518: [ { id: 9101, order_id: 54518, product_id: 53, product: products[0], quantity: 1, notes: "", reference: "", related_item_id: null, price: 649, }, { id: 9102, order_id: 54518, product_id: 63, product: products[1], quantity: 1, notes: "", reference: "", related_item_id: 9101, price: 399, }, { id: 9103, order_id: 54518, product_id: 64, product: products[2], quantity: 1, notes: "", reference: "", related_item_id: 9101, price: 25, }, { id: 9104, order_id: 54518, product_id: 65, product: products[3], quantity: 1, notes: "", reference: "", related_item_id: 9101, price: 299, }, ], }, attachmentsByOrderId: { 54518: [ { id: 301, object_type: "orders", object_id: 54518, content: { image: null, document: null, relation: null, other: "safety-seal.pdf", src: null, }, created_at: "2026-04-08 08:44:07", updated_at: "2026-04-08 08:44:07", deleted_at: null, }, ], }, economicModuleOrdersByOrderId: { 54518: { invoice_id: null, invoice_draft_id: null, }, }, stripeModuleOrdersByOrderId: { 54518: {}, }, paymentIntentsByOrderId: {}, stripeReadersError: null, readers: [{ id: "reader_online_1", label: "Mobile Reader", status: "online", action: null }], nextOrderId: 54519, nextOrderItemId: 9200, nextAttachmentId: 400, }; return { ...baseFixture, ...overrides, customersByNumber: { ...baseFixture.customersByNumber, ...(overrides.customersByNumber || {}), }, customerAttributesByNumber: { ...baseFixture.customerAttributesByNumber, ...(overrides.customerAttributesByNumber || {}), }, customerNotesByNumber: { ...baseFixture.customerNotesByNumber, ...(overrides.customerNotesByNumber || {}), }, numberPlateScanners: overrides.numberPlateScanners || baseFixture.numberPlateScanners, numberPlateScans: overrides.numberPlateScans || baseFixture.numberPlateScans, motorApiLookupByPlate: { ...baseFixture.motorApiLookupByPlate, ...(overrides.motorApiLookupByPlate || {}), }, ordersById: { ...baseFixture.ordersById, ...(overrides.ordersById || {}), }, orderItemsByOrderId: { ...baseFixture.orderItemsByOrderId, ...(overrides.orderItemsByOrderId || {}), }, attachmentsByOrderId: { ...baseFixture.attachmentsByOrderId, ...(overrides.attachmentsByOrderId || {}), }, economicModuleOrdersByOrderId: { ...baseFixture.economicModuleOrdersByOrderId, ...(overrides.economicModuleOrdersByOrderId || {}), }, stripeModuleOrdersByOrderId: { ...baseFixture.stripeModuleOrdersByOrderId, ...(overrides.stripeModuleOrdersByOrderId || {}), }, paymentIntentsByOrderId: { ...baseFixture.paymentIntentsByOrderId, ...(overrides.paymentIntentsByOrderId || {}), }, }; } function buildPosOrderItem(product, body, id) { const quantity = Number(body.quantity || 1); return { id, order_id: Number(body.order_id), product_id: product.id, product, quantity, notes: body.notes || "", reference: body.reference || "", related_item_id: body.related_item_id ?? null, price: Number(body.price ?? product.price ?? 0), }; } function matchesProductCategory(product, category) { const normalizedCategory = Number(category || 0); const productCategory = Number(product?.category || 0); const productName = String(product?.name || ""); if (normalizedCategory === 6) { return productCategory === 6 || (product?.is_wash && productCategory === 4); } if (normalizedCategory === 2) { return productCategory === 2 || /indvendig/i.test(productName); } return productCategory === normalizedCategory; } function buildPosOrderResponse(posFixture, orderId) { const order = withEffectiveOrderState(posFixture, posFixture.ordersById[orderId] || null); if (!order) { return { success: true, data: null, includes: {} }; } const customer = posFixture.customersByNumber[order.customer_id] || null; return { success: true, data: order, includes: { orderItems: posFixture.orderItemsByOrderId[orderId] || [], customer: customer ? { ...customer, economic_customer: customer.customerNumber } : null, cashier: { id: 7, display_name: "Backoffice User" }, economicModuleOrders: posFixture.economicModuleOrdersByOrderId[orderId] || {}, stripeModuleOrders: posFixture.stripeModuleOrdersByOrderId[orderId] || {}, }, }; } async function handlePosRoute({ route, request, parsedUrl, pathname, method, posFixture }) { if (!posFixture) { return false; } if (pathname.endsWith("/departments") && method === "GET") { await route.fulfill(json({ success: true, data: posFixture.departments })); return true; } if (pathname.endsWith("/departments/categories") && method === "GET") { await route.fulfill(json({ success: true, data: posFixture.departmentCategories || [] })); return true; } if (pathname.endsWith("/bookings") && method === "GET") { await route.fulfill( json({ success: true, data: Array.isArray(posFixture.orderBookings) ? posFixture.orderBookings : [] }) ); return true; } if (pathname.endsWith("/order-bookings") && method === "POST") { const body = request.postDataJSON?.() || {}; const bookingId = Number(posFixture.nextOrderBookingId || 9001); const customerNumber = Number(body.customer_number || 0); const departmentId = Number(body.department || 0); const createdAt = toSqlDateTime(); const bookingDate = String(body.datetime || createdAt).slice(0, 10); const customer = posFixture.customersByNumber?.[customerNumber] || null; const serviceNames = Array.isArray(body.items) ? body.items.map((item) => String(item?.name || "").trim()).filter(Boolean) : []; const createdBooking = { id: bookingId, customer_number: customerNumber, customer_name: customer?.name || "E2E User", department: departmentId, date: bookingDate, datetime: body.datetime || createdAt, regNrTraekker: body.reg_1 || "", regNrTrailer: body.reg_2 || "", reg_1: body.reg_1 || "", reg_2: body.reg_2 || "", reg_3: body.reg_3 || "", reference_number: body.reference || "", reference: body.reference || "", notes: body.note || "", note: body.note || "", po: body.po || "", pickup_bool: body.pickup ? 1 : 0, pickup: Boolean(body.pickup), created_at: createdAt, status: "pending", wash_type: serviceNames.join(", "), parsed_services: { string: serviceNames.join(", "), array: serviceNames, }, wash_certificate_pdf: null, washCertificateStatus: null, }; posFixture.nextOrderBookingId = bookingId + 1; posFixture.orderBookings = [createdBooking, ...(posFixture.orderBookings || [])]; await route.fulfill(json({ success: true, data: createdBooking })); return true; } if (pathname.endsWith("/order-bookings") && method === "GET") { const id = Number(parsedUrl.searchParams.get("id") || 0); const filters = String(parsedUrl.searchParams.get("filters") || ""); const page = Number(parsedUrl.searchParams.get("page") || 1); const limit = Number(parsedUrl.searchParams.get("limit") || 100); let bookings = Array.isArray(posFixture.orderBookings) ? posFixture.orderBookings : []; if (id > 0) { await route.fulfill(json({ success: true, data: bookings.find((booking) => booking.id === id) || null })); return true; } if (filters.includes("department:")) { const department = filters.split("department:")[1]?.split(",")[0] || ""; bookings = bookings.filter( (booking) => Number(booking.department ?? booking.department_id ?? 0) === Number(department) ); } if (filters.includes("order_id:null") || filters.includes("order_id:is null")) { bookings = bookings.filter((booking) => !booking.order_id); } if (filters.includes("reg_1:")) { const reg = filters.split("reg_1:")[1]?.split(",")[0] || ""; bookings = bookings.filter((booking) => String(booking.reg_1 || "").toUpperCase() === String(reg).toUpperCase()); } if (filters.includes("reg_2:")) { const reg = filters.split("reg_2:")[1]?.split(",")[0] || ""; bookings = bookings.filter((booking) => String(booking.reg_2 || "").toUpperCase() === String(reg).toUpperCase()); } const normalizedPage = Number.isFinite(page) && page > 0 ? page : 1; const normalizedLimit = Number.isFinite(limit) && limit > 0 ? limit : bookings.length || 1; const offset = (normalizedPage - 1) * normalizedLimit; bookings = bookings.slice(offset, offset + normalizedLimit); await route.fulfill( json({ success: true, data: bookings.map((booking) => toOrderBookingListEntry(booking, posFixture.orderBookingListStripsDetails === true) ), }) ); return true; } if (pathname.endsWith("/order-bookings") && method === "PUT") { const body = request.postDataJSON?.() || {}; const bookingId = Number(body.id || 0); const bookings = Array.isArray(posFixture.orderBookings) ? posFixture.orderBookings : []; const bookingIndex = bookings.findIndex((booking) => Number(booking.id) === bookingId); if (bookingIndex >= 0) { bookings[bookingIndex] = { ...bookings[bookingIndex], order_id: body.order_id ?? body.value ?? null, }; posFixture.bookingOrderAssignments = Array.isArray(posFixture.bookingOrderAssignments) ? posFixture.bookingOrderAssignments : []; posFixture.bookingOrderAssignments.push({ id: bookingId, order_id: bookings[bookingIndex].order_id, }); } await route.fulfill(json({ success: true, data: bookingIndex >= 0 ? bookings[bookingIndex] : null })); return true; } if (/\/order-bookings\/\d+$/.test(pathname) && method === "GET") { const bookingId = Number(pathname.split("/").pop()); const booking = (posFixture.orderBookings || []).find((entry) => entry.id === bookingId) || null; await route.fulfill(json({ success: true, data: booking })); return true; } if (pathname.endsWith("/order-bookings/complete") && method === "POST") { const body = request.postDataJSON?.() || {}; const bookingId = Number(body.id || 0); const bookings = Array.isArray(posFixture.orderBookings) ? posFixture.orderBookings : []; const bookingIndex = bookings.findIndex((booking) => Number(booking.id) === bookingId); if (bookingIndex >= 0) { bookings[bookingIndex] = { ...bookings[bookingIndex], status: "completed", }; posFixture.completedBookingIds = Array.isArray(posFixture.completedBookingIds) ? posFixture.completedBookingIds : []; posFixture.completedBookingIds.push(bookingId); } await route.fulfill( json({ success: true, data: bookingIndex >= 0 ? bookings[bookingIndex] : { id: bookingId, status: "completed" }, }) ); return true; } if (pathname.endsWith("/department/numberplatescanners") && method === "GET") { await route.fulfill(json({ success: true, data: posFixture.numberPlateScanners || [] })); return true; } if (pathname.endsWith("/numberplatescans") && method === "GET") { const filteredScans = filterNumberPlateScans(posFixture, { filters: parsedUrl.searchParams.get("filters") || "", search: parsedUrl.searchParams.get("search") || "", order: parsedUrl.searchParams.get("order") || "created_at:desc", }); const paginated = paginateRows( filteredScans, parsedUrl.searchParams.get("page") || 1, parsedUrl.searchParams.get("limit") || 10 ); await route.fulfill( json({ success: true, data: paginated.rows, meta: paginated.meta, }) ); return true; } if (pathname.endsWith("/vehicles/search") && method === "GET") { const search = String(parsedUrl.searchParams.get("search") || "").toUpperCase(); const vehicles = (posFixture.vehicles || []).filter( (vehicle) => !search || String(vehicle.reg || "") .toUpperCase() .includes(search) ); await route.fulfill(json({ success: true, data: vehicles })); return true; } if (pathname.endsWith("/vehicles") && method === "GET") { const search = String(parsedUrl.searchParams.get("search") || "").toUpperCase(); const vehicles = (posFixture.vehicles || []).filter( (vehicle) => !search || String(vehicle.reg || "") .toUpperCase() .includes(search) ); await route.fulfill(json({ success: true, data: vehicles })); return true; } if (pathname.endsWith("/department/vehicles/unknown-customer") && method === "GET") { await route.fulfill(json({ success: true, data: posFixture.unknownVehicles || [] })); return true; } if (pathname.endsWith("/users/customer") && method === "GET") { const customerNumber = Number(parsedUrl.searchParams.get("customer_number") || 0); const customer = posFixture.customersByNumber[customerNumber] || Object.values(posFixture.customersByNumber)[0]; await route.fulfill( json({ success: true, data: { customer_name: customer?.name || "", economic_customer: customer || null, }, }) ); return true; } if (pathname.endsWith("/modules/motorapi/lookup") && method === "GET") { const licensePlate = String(parsedUrl.searchParams.get("license_plate") || "").toUpperCase(); const vehicleData = posFixture.motorApiLookupByPlate?.[licensePlate]; if (!vehicleData) { await route.fulfill( json( { success: false, data: null, }, 404 ) ); return true; } await route.fulfill( json({ success: true, data: vehicleData, }) ); return true; } if (pathname.endsWith("/customers") && method === "GET") { const search = String(parsedUrl.searchParams.get("search") || "").toLowerCase(); const customers = Object.values(posFixture.customersByNumber).filter((customer) => { if (!search) { return true; } return ( String(customer.customerNumber).includes(search) || String(customer.name || "") .toLowerCase() .includes(search) ); }); await route.fulfill( json({ success: true, data: customers, meta: { pagination: { page: 1, limit: 10, total: customers.length, }, }, }) ); return true; } if (pathname.endsWith("/customer/attributes") && method === "GET") { const customerNumber = Number(parsedUrl.searchParams.get("customer_number") || 0); await route.fulfill(json({ success: true, data: posFixture.customerAttributesByNumber[customerNumber] || [] })); return true; } if (pathname.endsWith("/customer/notes") && method === "GET") { const customerNumber = Number(parsedUrl.searchParams.get("customer_number") || 0); await route.fulfill(json({ success: true, data: posFixture.customerNotesByNumber[customerNumber] || [] })); return true; } if (pathname.endsWith("/superuser/user/discounts") && method === "GET") { await route.fulfill(json({ success: true, data: [] })); return true; } if (pathname.endsWith("/products") && method === "GET") { const productId = Number(parsedUrl.searchParams.get("id") || 0); const category = Number(parsedUrl.searchParams.get("category") || 0); if (productId > 0) { await route.fulfill( json({ success: true, data: (posFixture.products || []).find((product) => product.id === productId) || null }) ); return true; } const products = category > 0 ? (posFixture.products || []).filter((product) => matchesProductCategory(product, category)) : posFixture.products || []; await route.fulfill(json({ success: true, data: products })); return true; } if (pathname.endsWith("/orders") && method === "GET") { const filters = String(parsedUrl.searchParams.get("filters") || ""); const orders = filterPosOrders(posFixture, filters) .map((order) => withEffectiveOrderState(posFixture, order)) .sort((a, b) => Number(b.id) - Number(a.id)); await route.fulfill( json({ success: true, data: orders, meta: { pagination: { page: 1, limit: orders.length || 20, total: orders.length, }, }, }) ); return true; } if (pathname.endsWith("/orders") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = posFixture.nextOrderId++; posFixture.ordersById[orderId] = { id: orderId, customer_id: Number(body.customer_id), department_id: Number(body.department_id || body.department || 12), reference: body.reference || "", po: body.po || "", safety_seal: normalizeSafetySealValue(body.safety_seal), notes: body.notes || "", reg_1: normalizeRegistrationValue(body.reg_1), reg_2: normalizeRegistrationValue(body.reg_2), reg_3: normalizeRegistrationValue(body.reg_3), invoice_collection_id: null, booking_id: normalizePositiveIntegerValue(body.booking_id), completed_at: null, closed_at: null, created_at: normalizeCreatedAtValue(body.created_at) || toSqlDateTime(), include_in_invoice: normalizeIncludeInInvoiceValue(body.include_in_invoice), }; posFixture.orderItemsByOrderId[orderId] = []; posFixture.economicModuleOrdersByOrderId[orderId] = { invoice_id: null, invoice_draft_id: null }; posFixture.stripeModuleOrdersByOrderId[orderId] = {}; await route.fulfill(json({ success: true, data: { id: orderId } })); return true; } if (pathname.endsWith("/order") && method === "GET") { const orderId = Number(parsedUrl.searchParams.get("id") || 0); await route.fulfill(json(buildPosOrderResponse(posFixture, orderId))); return true; } if (pathname.endsWith("/orders") && method === "PUT") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id || 0); if (posFixture.ordersById[orderId]) { posFixture.ordersById[orderId] = { ...posFixture.ordersById[orderId], ...body, ...(Object.prototype.hasOwnProperty.call(body, "reg_1") ? { reg_1: normalizeRegistrationValue(body.reg_1) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "reg_2") ? { reg_2: normalizeRegistrationValue(body.reg_2) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "reg_3") ? { reg_3: normalizeRegistrationValue(body.reg_3) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "created_at") ? { created_at: normalizeCreatedAtValue(body.created_at) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "safety_seal") ? { safety_seal: normalizeSafetySealValue(body.safety_seal) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "include_in_invoice") ? { include_in_invoice: normalizeIncludeInInvoiceValue(body.include_in_invoice) } : {}), }; } await route.fulfill( json({ success: true, data: withEffectiveOrderState(posFixture, posFixture.ordersById[orderId] || null) }) ); return true; } if (pathname.endsWith("/order") && method === "PUT") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id || 0); if (posFixture.ordersById[orderId]) { if (body.field) { posFixture.ordersById[orderId][body.field] = body.field === "include_in_invoice" ? normalizeIncludeInInvoiceValue(body.value) : body.field === "reg_1" || body.field === "reg_2" || body.field === "reg_3" ? normalizeRegistrationValue(body.value) : body.field === "safety_seal" ? normalizeSafetySealValue(body.value) : body.field === "created_at" ? normalizeCreatedAtValue(body.value) : body.value; } else { posFixture.ordersById[orderId] = { ...posFixture.ordersById[orderId], ...body, ...(Object.prototype.hasOwnProperty.call(body, "reg_1") ? { reg_1: normalizeRegistrationValue(body.reg_1) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "reg_2") ? { reg_2: normalizeRegistrationValue(body.reg_2) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "reg_3") ? { reg_3: normalizeRegistrationValue(body.reg_3) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "created_at") ? { created_at: normalizeCreatedAtValue(body.created_at) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "safety_seal") ? { safety_seal: normalizeSafetySealValue(body.safety_seal) } : {}), ...(Object.prototype.hasOwnProperty.call(body, "include_in_invoice") ? { include_in_invoice: normalizeIncludeInInvoiceValue(body.include_in_invoice) } : {}), }; } } await route.fulfill( json({ success: true, data: withEffectiveOrderState(posFixture, posFixture.ordersById[orderId] || null) }) ); return true; } if (pathname.endsWith("/order/items") && method === "GET") { const orderId = Number(parsedUrl.searchParams.get("order_id") || 0); await route.fulfill(json({ success: true, data: posFixture.orderItemsByOrderId[orderId] || [] })); return true; } if (pathname.endsWith("/order/items") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.order_id || 0); const productId = Number(body.product_id || 0); const product = (posFixture.products || []).find((entry) => entry.id === productId); if (!product || !posFixture.ordersById[orderId]) { await route.fulfill(json({ success: false, data: { message: "Order or product not found" } }, 422)); return true; } const orderItem = buildPosOrderItem(product, body, posFixture.nextOrderItemId++); if (!Array.isArray(posFixture.orderItemsByOrderId[orderId])) { posFixture.orderItemsByOrderId[orderId] = []; } posFixture.orderItemsByOrderId[orderId].push(orderItem); await route.fulfill(json({ success: true, data: orderItem })); return true; } if (pathname.endsWith("/order/items") && method === "PUT") { const body = request.postDataJSON?.() || {}; const targetId = Number(body.id || 0); Object.keys(posFixture.orderItemsByOrderId).forEach((orderIdKey) => { posFixture.orderItemsByOrderId[orderIdKey] = (posFixture.orderItemsByOrderId[orderIdKey] || []).map((item) => { if (item.id !== targetId) { return item; } return { ...item, price: Number(body.price ?? item.price), notes: body.notes ?? item.notes, reference: body.reference ?? item.reference, quantity: Number(body.quantity ?? item.quantity), }; }); }); await route.fulfill(json({ success: true, data: { id: targetId } })); return true; } if (pathname.endsWith("/order/items") && method === "DELETE") { const orderItemId = Number(parsedUrl.searchParams.get("id") || 0); Object.keys(posFixture.orderItemsByOrderId).forEach((orderIdKey) => { posFixture.orderItemsByOrderId[orderIdKey] = (posFixture.orderItemsByOrderId[orderIdKey] || []).filter( (item) => item.id !== orderItemId ); }); await route.fulfill(json({ success: true, data: true })); return true; } if (pathname.endsWith("/orders/attachments") && method === "GET") { const orderId = Number(parsedUrl.searchParams.get("id") || 0); await route.fulfill(json({ success: true, data: posFixture.attachmentsByOrderId[orderId] || [] })); return true; } if (pathname.endsWith("/orders/attachments/upload") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.order_id || body.id || 0); if (!Array.isArray(posFixture.attachmentsByOrderId[orderId])) { posFixture.attachmentsByOrderId[orderId] = []; } const attachmentId = posFixture.nextAttachmentId++; const attachment = { id: attachmentId, object_type: "orders", object_id: orderId, content: { image: null, document: null, relation: null, other: `attachment-${attachmentId}.jpg`, src: null, }, created_at: toSqlDateTime(), updated_at: toSqlDateTime(), deleted_at: null, }; posFixture.attachmentsByOrderId[orderId].push(attachment); await route.fulfill(json({ success: true, data: attachment })); return true; } if (pathname.endsWith("/orders/attachments") && method === "DELETE") { const orderId = Number(parsedUrl.searchParams.get("order_id") || 0); const attachmentId = Number(parsedUrl.searchParams.get("attachment_id") || 0); posFixture.attachmentsByOrderId[orderId] = (posFixture.attachmentsByOrderId[orderId] || []).filter( (attachment) => attachment.id !== attachmentId ); await route.fulfill(json({ success: true, data: true })); return true; } if (pathname.endsWith("/orders/attachments/download") && method === "GET") { const orderId = Number(parsedUrl.searchParams.get("order_id") || 0); const attachmentId = Number(parsedUrl.searchParams.get("attachment_id") || 0); await route.fulfill( json({ success: true, data: { download_link: `https://cdn.example.test/orders/${orderId}/attachments/${attachmentId}`, }, }) ); return true; } if (pathname.endsWith("/order/wash-certificate") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id || 0); if (posFixture.ordersById[orderId]) { posFixture.ordersById[orderId].safety_seal = normalizeSafetySealValue(body.safety_seal); } if (!Array.isArray(posFixture.attachmentsByOrderId[orderId])) { posFixture.attachmentsByOrderId[orderId] = []; } const existingAttachment = (posFixture.attachmentsByOrderId[orderId] || []).find((attachment) => { return String(attachment?.content?.other || "").toUpperCase() === "WASH_CERTIFICATE"; }) || null; if (existingAttachment) { await route.fulfill( json({ success: true, data: { order_id: orderId, created: false, already_existed: true, attachment_id: existingAttachment.id, }, }) ); return true; } const attachmentId = posFixture.nextAttachmentId++; const attachment = { id: attachmentId, object_type: "orders", object_id: orderId, content: { image: null, document: `wash_certificate_${orderId}.pdf`, relation: null, other: "WASH_CERTIFICATE", src: null, }, created_at: toSqlDateTime(), updated_at: toSqlDateTime(), deleted_at: null, }; posFixture.attachmentsByOrderId[orderId].push(attachment); await route.fulfill( json({ success: true, data: { order_id: orderId, created: true, already_existed: false, attachment_id: attachmentId, }, }) ); return true; } if (pathname.endsWith("/departments/order/recommended") && method === "GET") { await route.fulfill( json({ success: true, data: { reg_1: { motorapi: [53], order_history: { 2: [], 3: [], 4: [], 5: [] }, }, }, }) ); return true; } if (pathname.endsWith("/orders/mark_as_completed") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id || 0); if (posFixture.ordersById[orderId]) { posFixture.ordersById[orderId].completed_at = new Date().toISOString(); if (orderContainsWashCertificate(posFixture, orderId)) { ensureWashCertificateAttachment(posFixture, orderId); } } await route.fulfill(json({ success: true, data: { id: orderId } })); return true; } if (pathname.endsWith("/modules/stripe/department/terminal/readers") && method === "GET") { if (posFixture.stripeReadersError) { const configuredError = posFixture.stripeReadersError; await route.fulfill( json( { success: false, data: { message: configuredError.message || "Unable to load Stripe readers.", code: configuredError.code || null, }, meta: {}, includes: {}, }, Number(configuredError.status || 409) ) ); return true; } await route.fulfill(json({ success: true, data: { data: posFixture.readers || [] } })); return true; } if (pathname.endsWith("/orders/module/stripe/payment_intent") && method === "GET") { const orderId = Number(parsedUrl.searchParams.get("id") || 0); const paymentIntent = posFixture.paymentIntentsByOrderId[orderId] || null; await route.fulfill( json({ success: true, data: { payment_intent: paymentIntent, has_payment_intent: !!paymentIntent, }, }) ); return true; } if (pathname.endsWith("/orders/module/stripe/payment_intent") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id || 0); const paymentIntent = posFixture.paymentIntentsByOrderId[orderId] || { id: `pi_${orderId}`, amount: 123400, amount_capturable: 123400, amount_received: 0, currency: "dkk", status: "requires_capture", metadata: { order_id: String(orderId), reader_id: String(body.reader || "reader_online_1"), tax_percentage: String(body.tax_percentage ?? 25), }, }; posFixture.paymentIntentsByOrderId[orderId] = paymentIntent; await route.fulfill( json({ success: true, data: { payment_intent: paymentIntent, has_payment_intent: true, }, }) ); return true; } if (pathname.endsWith("/orders/module/stripe/payment_intent") && method === "DELETE") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id || 0); delete posFixture.paymentIntentsByOrderId[orderId]; await route.fulfill(json({ success: true, data: { payment_intent: null, has_payment_intent: false } })); return true; } if (pathname.endsWith("/orders/module/stripe/payment_intent/capture") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.id || 0); const paymentIntent = { ...(posFixture.paymentIntentsByOrderId[orderId] || {}), id: `pi_${orderId}`, amount: 123400, amount_capturable: 0, amount_received: 123400, currency: "dkk", status: "succeeded", metadata: { order_id: String(orderId), reader_id: "reader_online_1", tax_percentage: "25", }, }; posFixture.paymentIntentsByOrderId[orderId] = paymentIntent; await route.fulfill( json({ success: true, data: { payment_intent: paymentIntent, has_payment_intent: true, }, }) ); return true; } return false; } async function handleEdgeGatewayRoute({ route, request, parsedUrl, pathname, method, edgeGatewayFixture, }) { if (!edgeGatewayFixture) { return false; } const extractMatchId = (pattern) => { const match = pathname.match(pattern); return Number(match?.[1] || 0); }; const findGateway = (gatewayId) => edgeGatewayFixture.gateways.find((entry) => Number(entry.id) === Number(gatewayId)) || null; const gatewayResponse = (gateway, includeDetail = true) => gateway ? json({ data: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, includeDetail) }) : json({ message: "Gateway not found" }, 404); const createOperation = (gateway, type, request = {}, overrides = {}) => { const operation = createFixtureOperation(type, request, { id: edgeGatewayFixture.nextOperationId++, ...overrides, }); gateway.operations = [operation, ...(gateway.operations || [])]; return operation; }; if (pathname.endsWith("/edge-gateways") && method === "GET") { processPendingEdgeGatewayClaims(edgeGatewayFixture); const departmentId = Number(parsedUrl.searchParams.get("department_id") || 0); const gateways = departmentId > 0 ? edgeGatewayFixture.gateways.filter((gateway) => Number(gateway.department_id) === departmentId) : edgeGatewayFixture.gateways; await route.fulfill( json({ data: gateways.map((gateway) => buildHttpEdgeGatewayGateway(edgeGatewayFixture, settleEdgeGatewayWork(edgeGatewayFixture, gateway.id), false) ), }) ); return true; } if (/\/edge-gateways\/\d+$/.test(pathname) && method === "GET") { const gatewayId = Number(pathname.split("/").pop()); processPendingEdgeGatewayClaims(edgeGatewayFixture); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); await route.fulfill( gateway ? json({ data: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true) }) : json({ message: "Gateway not found" }, 404) ); return true; } if (/\/edge-gateways\/\d+\/operations$/.test(pathname) && method === "GET") { const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/operations$/); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); await route.fulfill(json({ data: cloneJson(gateway?.operations || []) })); return true; } if (/\/edge-gateways\/\d+\/operations\/\d+\/events$/.test(pathname) && method === "GET") { const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/operations\/\d+\/events$/); const operationId = extractMatchId(/\/operations\/(\d+)\/events$/); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); const operation = (gateway?.operations || []).find((entry) => Number(entry.id) === Number(operationId)) || null; await route.fulfill(json({ data: cloneJson(operation?.events || []) })); return true; } if (/\/edge-gateways\/\d+\/operations$/.test(pathname) && method === "POST") { const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/operations$/); const gateway = findGateway(gatewayId); const body = request.postDataJSON?.() || {}; if (!gateway) { await route.fulfill(json({ message: "Gateway not found" }, 404)); return true; } const operationType = String(body.type || "").toUpperCase(); const operationRequest = body.request || {}; const now = toSqlDateTime(); let operation = null; if (operationType === "DISCOVERY") { gateway.discovery_status = "PENDING"; operation = createOperation(gateway, "DISCOVERY", operationRequest, { status: "IN_PROGRESS", started_at: now, updated_at: now, summary: { label: "Gateway is processing the operation", progress: 20, retryable: true }, events: [ { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_QUEUED", message: "Operation queued for gateway execution", created_at: now }, { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_STARTED", message: "Gateway started processing the operation", created_at: now }, ], }); edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId] = { operationId: operation.id, fetchCount: 0, device: { id: (gateway.inventory?.length || 0) + 1, device_id: "shelly-plus-new", local_ip: "10.1.0.33", model: "Shelly Plus 1PM", channel_count: 1, online: true, capabilities: { generation: 2 }, }, }; } else if (operationType === "UPDATE") { gateway.target_version = String(operationRequest.target_version || gateway.target_version || ""); gateway.installed_version = gateway.target_version; operation = createOperation(gateway, "UPDATE", operationRequest, { status: "COMPLETED", started_at: now, completed_at: now, updated_at: now, summary: { label: "Completed", progress: 100, retryable: true }, result: { installed_version: gateway.installed_version, target_version: gateway.target_version, restart_required: true }, events: [ { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_QUEUED", message: "Operation queued for gateway execution", created_at: now }, { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: now }, ], }); } else if (operationType === "UNINSTALL") { gateway.status = "OFFLINE"; gateway.metadata = { ...(gateway.metadata || {}), uninstalled_at: now, }; operation = createOperation(gateway, "UNINSTALL", operationRequest, { status: "COMPLETED", started_at: now, completed_at: now, updated_at: now, summary: { label: "Completed", progress: 100, retryable: false }, result: { uninstalled: true, manual_cleanup_required: true }, events: [ { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_QUEUED", message: "Operation queued for gateway execution", created_at: now }, { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: now }, ], }); } else { await route.fulfill(json({ data: { message: "Unsupported gateway operation type", error_code: "EDGE_GATEWAY_VALIDATION_FAILED" } }, 422)); return true; } gateway.audit_logs = [ { id: Date.now(), created_at: now, action: "GATEWAY_OPERATION_QUEUED", actor_type: "USER" }, ...(gateway.audit_logs || []), ]; Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); await route.fulfill(json({ data: { operation: cloneJson(operation), gateway: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true) } }, 201)); return true; } if (/\/edge-gateways\/\d+\/rotate-credentials$/.test(pathname) && method === "POST") { const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/rotate-credentials$/); const gateway = findGateway(gatewayId); if (!gateway) { await route.fulfill(json({ message: "Gateway not found" }, 404)); return true; } const rotatedAt = toSqlDateTime(); gateway.metadata = { ...(gateway.metadata || {}), credentials_rotated_at: rotatedAt, }; gateway.audit_logs = [ { id: Date.now(), created_at: rotatedAt, action: "GATEWAY_CREDENTIALS_ROTATED", actor_type: "USER" }, ...(gateway.audit_logs || []), ]; Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); await route.fulfill( json({ data: { gateway_id: gatewayId, rotated_at: rotatedAt, agent_token: "rotated-edge-agent-token", config_json: JSON.stringify( { apiUrl: "https://api.truckwash.test", gatewayId, agentToken: "rotated-edge-agent-token", installDir: "/opt/truckwash-edge-agent", serviceName: "truckwash-edge-agent.service", heartbeatIntervalSeconds: 15, operationPollTimeoutSeconds: 20, }, null, 2 ), restart_instructions: [ "sudo systemctl restart truckwash-edge-agent.service", "sudo systemctl status truckwash-edge-agent.service --no-pager", ], }, }) ); return true; } if (/\/edge-gateways\/\d+\/discovery$/.test(pathname) && method === "POST") { const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/discovery$/); const gateway = findGateway(gatewayId); if (gateway) { const now = toSqlDateTime(); const operation = createOperation(gateway, "DISCOVERY", {}, { status: "IN_PROGRESS", started_at: now, updated_at: now, summary: { label: "Gateway is processing the operation", progress: 20, retryable: true }, events: [ { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_QUEUED", message: "Operation queued for gateway execution", created_at: now }, { id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_STARTED", message: "Gateway started processing the operation", created_at: now }, ], }); gateway.discovery_status = "PENDING"; edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId] = { operationId: operation.id, fetchCount: 0, device: { id: (gateway.inventory?.length || 0) + 1, device_id: "shelly-plus-new", local_ip: "10.1.0.33", model: "Shelly Plus 1PM", channel_count: 1, online: true, capabilities: { generation: 2 }, }, }; Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); } await route.fulfill(gatewayResponse(gateway, true)); return true; } if (pathname.endsWith("/edge-gateways/install-token") && method === "POST") { const body = request.postDataJSON?.() || {}; edgeGatewayFixture.pendingClaims.push({ department_id: Number(body.department_id || 1), label: String(body.label || "").trim(), pollsRemaining: edgeGatewayFixture.claimPollsRemaining, }); await route.fulfill( json( { data: { claim_token_id: 9001, token: "edge-install-token", expires_at: "2026-04-08 08:45:00", install_command: "curl -fsSL https://api.truckwash.test/edge-agent/install.sh?token=edge-install-token | sudo bash", install_url: "https://api.truckwash.test/edge-agent/install.sh?token=edge-install-token", department_id: body.department_id ?? 1, }, }, 201 ) ); return true; } if (/\/edge-gateways\/\d+$/.test(pathname) && method === "PUT") { const gatewayId = Number(pathname.split("/").pop()); const gateway = findGateway(gatewayId); const body = request.postDataJSON?.() || {}; if (gateway) { gateway.label = String(body.label || gateway.label || "").trim() || gateway.label; if (body.is_primary === true) { edgeGatewayFixture.gateways .filter((item) => Number(item.department_id) === Number(gateway.department_id)) .forEach((item) => { item.is_primary = Number(item.id) === Number(gatewayId); }); } else if (body.is_primary === false) { gateway.is_primary = false; } gateway.audit_logs = [ { id: Date.now(), created_at: "2026-04-09 12:30:00", action: "GATEWAY_METADATA_UPDATED", actor_type: "USER", }, ...(gateway.audit_logs || []), ]; Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); edgeGatewayFixture.gateways = edgeGatewayFixture.gateways.map((item) => Number(item.id) === Number(gateway.id) ? Object.assign(item, gateway) : item ); } await route.fulfill(gatewayResponse(gateway, true)); return true; } if (/\/edge-gateways\/\d+\/bindings$/.test(pathname) && method === "PUT") { const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/bindings$/); const gateway = findGateway(gatewayId); const body = request.postDataJSON?.() || {}; if (gateway) { gateway.bindings = (body.bindings || []).map((binding, index) => ({ id: index + 1, ...binding, fallback_mode: binding.fallback_mode || "PREFER_LOCAL", })); Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); } await route.fulfill(json({ data: gateway ? buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true) : null })); return true; } if (/\/edge-gateways\/\d+\/(?:update-jobs|uninstall)(?:\/.*)?$/.test(pathname) || /\/edge-gateways\/\d+\/shell-sessions(?:\/.*)?$/.test(pathname)) { await route.fulfill(json({ message: "Route not found" }, 404)); return true; } if (/\/edge-gateways\/\d+$/.test(pathname) && method === "DELETE") { const gatewayId = Number(pathname.split("/").pop()); const gateway = findGateway(gatewayId); edgeGatewayFixture.gateways = edgeGatewayFixture.gateways.filter((item) => item.id !== gatewayId); delete edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId]; await route.fulfill( json({ data: { deleted: true, gateway_id: gatewayId, department_id: gateway?.department_id ?? null, }, }) ); return true; } if (/\/departments\/\d+\/gateway-cutover$/.test(pathname) && method === "POST") { const departmentId = extractMatchId(/\/departments\/(\d+)\/gateway-cutover$/); const body = request.postDataJSON?.() || {}; edgeGatewayFixture.gateways .filter((gateway) => gateway.department_id === departmentId) .forEach((gateway) => { gateway.department_transport_mode = body.transport_mode || "gateway"; Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); }); await route.fulfill( json({ data: { department_id: departmentId, transport_mode: body.transport_mode || "gateway", }, }) ); return true; } return false; } export async function mockApi(page, options = {}) { const shouldMockPosFixture = Boolean(options.pos || options.invoiceDistribution); const posFixture = shouldMockPosFixture ? createPosFixture(options.pos && options.pos !== true ? options.pos : {}) : null; const selfServe = options.selfServe ? createSelfServeFixture(options.selfServe === true ? {} : options.selfServe) : null; const edgeGatewayOptions = options.edgeGateways && typeof options.edgeGateways === "object" ? options.edgeGateways : {}; const edgeGatewayFixture = options.edgeGateways === false ? null : createHttpEdgeGatewayFixture(edgeGatewayOptions); await page.route(/https:\/\/cdn\.example\.test\/orders\/\d+\/attachments\/\d+$/i, async (route) => { const request = route.request(); if (request.method() !== "GET" || !posFixture) { await route.continue(); return; } const url = new URL(request.url()); const pathSegments = url.pathname.split("/").filter(Boolean); const orderId = Number(pathSegments[1] || 0); const attachmentId = Number(pathSegments[3] || 0); const attachment = (posFixture.attachmentsByOrderId?.[orderId] || []).find((entry) => Number(entry.id) === attachmentId) || null; const previewResponse = getAttachmentPreviewContentType(attachment); await route.fulfill(binary(previewResponse.body, previewResponse.contentType)); }); await page.route(API_HOST, async (route) => { const request = route.request(); const url = request.url(); const parsedUrl = new URL(url); const pathname = parsedUrl.pathname; const method = request.method(); if (url.includes("/auth/recaptcha/pre-check") && method === "GET") { await route.fulfill( json({ data: { recaptcha: { enabled: false, site_key: "", }, rate_limit: { enabled: false, limit: 0, remaining: 0, reset: 0, warning: null, }, }, }) ); return; } if (url.includes("/auth/reCAPTCHA/public") && method === "GET") { await route.fulfill( json({ data: { recaptcha: { enabled: false, site_key: "", }, rate_limit: { enabled: false, limit: 0, remaining: 0, reset: 0, warning: null, }, }, }) ); return; } if (pathname.endsWith("/ping") && method === "GET") { await route.fulfill( json({ data: { ok: true, }, }) ); return; } if (pathname.endsWith("/worker/version") && method === "GET") { await route.fulfill( json({ data: { version: options.workerVersion || "unknown", }, }) ); return; } if (url.includes("/auth/login") && method === "POST") { await route.fulfill( json({ data: { token: options.loginToken || "e2e-token", }, }) ); return; } if (url.includes("/auth/session") && method === "GET") { if (!options.authenticated) { await route.fulfill(json({ message: "Unauthenticated" }, 401)); return; } const defaultSession = { id: 1, customer_number: 12345, group_id: 1, email: "e2e@example.com", phone: { number: "12345678", country_code: 45, }, notifications: { wash_certificate_email: null, email_notifications_enabled: true, sms_notifications_enabled: false, }, created_at: "2026-01-01T00:00:00.000Z", updated_at: "2026-01-01T00:00:00.000Z", display_name: "E2E User", permissions: options.permissions || ["user"], economic_customer: [], runtime_config: { economic: { transaction_draft_customer_number: null, }, }, }; const sessionData = { ...defaultSession, ...(options.sessionData || {}), }; if (options.permissions) { sessionData.permissions = options.permissions; } await route.fulfill( json({ data: sessionData, }) ); return; } if (pathname.endsWith("/guest/validation/customer-number") && method === "POST") { const body = request.postDataJSON?.() || {}; const customerNumber = Number(body.customer_number || 0); const sessionCustomerNumber = Number(options.sessionData?.customer_number || 0); const exists = Boolean( customerNumber && (customerNumber === sessionCustomerNumber || customerNumber === Number(posFixture?.defaultCustomer?.customerNumber || 0) || posFixture?.customersByNumber?.[customerNumber]) ); await route.fulfill( json({ data: { exists, }, }) ); return; } if (pathname.endsWith("/workfeed/config")) { const workfeedConfig = [ { module: "workfeed", variable: "enabled", type: "bool", value: true }, { module: "workfeed", variable: "api_url", type: "string", value: "https://api.workfeed.test" }, { module: "workfeed", variable: "api_key", type: "string", value: "test-api-key" }, { module: "workfeed", variable: "CompanyID", type: "string", value: "123456" }, ]; if (method === "GET") { const variable = parsedUrl.searchParams.get("variable"); if (variable) { const match = workfeedConfig.find((entry) => entry.variable === variable); await route.fulfill(json({ data: match ? match.value : null })); return; } await route.fulfill(json({ data: workfeedConfig })); return; } if (method === "POST") { await route.fulfill(json({ data: { updated: true } })); return; } } if (pathname.endsWith("/modules/workfeed/departments") && method === "GET") { const departments = [ { id: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N3", name: "North Facility", timezone: "Europe/Copenhagen", active: true, }, { id: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N4", name: "South Facility", timezone: "Europe/Copenhagen", active: true, }, ]; await route.fulfill( json({ data: { items: departments, pagination: { cursor: null, nextCursor: null, limit: 20, total: departments.length }, }, }) ); return; } if (pathname.endsWith("/modules/workfeed/employees") && method === "GET") { const employees = [ { id: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P", firstName: "Anne", lastName: "Nielsen", fullName: "Anne Nielsen", email: "anne.nielsen@example.com", departmentId: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N3", active: true, }, { id: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5Q", firstName: "Mads", lastName: "Jensen", fullName: "Mads Jensen", email: "mads.jensen@example.com", departmentId: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N4", active: true, }, ]; await route.fulfill( json({ data: { items: employees, pagination: { cursor: null, nextCursor: null, limit: 20, total: employees.length }, }, }) ); return; } if (/\/modules\/workfeed\/employees\/[^/]+$/i.test(pathname) && method === "GET") { const id = pathname.split("/").pop(); await route.fulfill( json({ data: { id, firstName: "Anne", lastName: "Nielsen", fullName: "Anne Nielsen", email: "anne.nielsen@example.com", departmentId: "dep_01J5P2B0BC9Q2X7H8Y7JQ1M2N3", active: true, }, }) ); return; } if (pathname.endsWith("/modules/workfeed/shifts") && method === "GET") { const startFrom = parsedUrl.searchParams.get("startFrom"); const startTo = parsedUrl.searchParams.get("startTo"); const employeeID = parsedUrl.searchParams.get("employeeID"); const releasedRaw = parsedUrl.searchParams.get("released"); if (!startFrom) { await route.fulfill(json({ message: "Missing required query parameter: startFrom" }, 422)); return; } if (!startTo) { await route.fulfill(json({ message: "Missing required query parameter: startTo" }, 422)); return; } const shifts = [ { id: "shf_01J5P3X9D35R9C6BDZ1S0R4V6Q", title: "Morning Shift", status: "published", employeeID: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P", released: true, startAt: "2026-03-24T06:00:00Z", endAt: "2026-03-24T14:00:00Z", }, { id: "shf_01J5P3X9D35R9C6BDZ1S0R4V6R", title: "Evening Shift", status: "draft", employeeID: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5Q", released: false, startAt: "2026-03-24T14:00:00Z", endAt: "2026-03-24T22:00:00Z", }, ]; const released = releasedRaw === null ? undefined : releasedRaw.toLowerCase() === "true"; const filtered = shifts.filter((entry) => { if (employeeID && entry.employeeID !== employeeID) { return false; } if (released !== undefined && entry.released !== released) { return false; } return true; }); await route.fulfill( json({ data: { items: filtered, pagination: { cursor: null, nextCursor: null, limit: 20, total: filtered.length }, }, }) ); return; } if (/\/modules\/workfeed\/shifts\/[^/]+$/i.test(pathname) && method === "GET") { const id = pathname.split("/").pop(); await route.fulfill( json({ data: { id, title: "Morning Shift", status: "published", employeeID: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P", released: true, startAt: "2026-03-24T06:00:00Z", endAt: "2026-03-24T14:00:00Z", }, }) ); return; } if ( await handleEdgeGatewayRoute({ route, request, parsedUrl, pathname, method, edgeGatewayFixture, }) ) { return; } if (await handlePosRoute({ route, request, parsedUrl, pathname, method, posFixture })) { return; } if (selfServe) { if (pathname.endsWith("/guest/departments") && method === "GET") { await route.fulfill(json({ data: selfServe.departments })); return; } if (pathname.endsWith("/departments") && method === "GET") { await route.fulfill(json({ data: selfServe.departments.map(({ lanes, ...department }) => department) })); return; } if (pathname.endsWith("/products") && method === "GET") { await route.fulfill(json({ data: selfServe.products })); return; } if (pathname.endsWith("/vehicles") && method === "GET") { await route.fulfill(json({ data: selfServe.customerVehicles })); return; } if (pathname.endsWith("/department/lanes") && method === "GET") { const laneId = parsedUrl.searchParams.get("id"); if (laneId) { await route.fulfill(json({ data: selfServe.laneById[laneId] || null })); return; } await route.fulfill(json({ data: selfServe.departmentLanes })); return; } if (pathname.endsWith("/department/selfserve/vehicle/allowed") && method === "GET") { const laneId = parsedUrl.searchParams.get("lane_id"); const reg = (parsedUrl.searchParams.get("reg") || "").toUpperCase(); const previewData = selfServe.previewByKey[`${laneId}:${reg}`] || null; await route.fulfill( json({ data: previewData || { allowed: false, questions: [], tasks: [], conditions: [], rules: [] } }) ); return; } if (pathname.endsWith("/department/selfserve/washes/summary") && method === "GET") { const sessionId = parsedUrl.searchParams.get("session_id"); const laneId = parsedUrl.searchParams.get("lane_id"); const reg = (parsedUrl.searchParams.get("reg") || "").toUpperCase(); const summaryData = (sessionId ? selfServe.summaryBySessionId[sessionId] : null) || selfServe.summaryByKey[`${laneId}:${reg}`] || null; await route.fulfill( json({ data: summaryData || { events: [], questions: [], tasks: [], conditions: [], rules: [] } }) ); return; } if (pathname.endsWith("/department/selfserve/vehicle/conditions") && method === "POST") { const body = request.postDataJSON?.() || {}; const key = `${body.lane}:${String(body.reg || "").toUpperCase()}:${body.question}:${body.value}`; const responseSummary = selfServe.answerResponseByKey[key] || null; await route.fulfill( json({ data: { selfserve: responseSummary, }, }) ); return; } if (pathname.endsWith("/department/selfserve/tasks/attachments") && method === "GET") { const taskId = parsedUrl.searchParams.get("id"); await route.fulfill( json({ data: selfServe.attachmentsByTaskId[taskId] || [], }) ); return; } if (pathname.endsWith("/department/selfserve/tasks/attachments/download") && method === "GET") { const taskId = parsedUrl.searchParams.get("task_id"); const attachmentId = parsedUrl.searchParams.get("attachment_id"); await route.fulfill( json(selfServe.attachmentDownloadByKey[`${taskId}:${attachmentId}`] || { download_link: null }) ); return; } if (pathname.endsWith("/modules/self-serve/lane/services/allowed") && method === "POST") { await route.fulfill( json({ data: { allowed_services: selfServe.laneAllowedServices, }, }) ); return; } if (pathname.endsWith("/modules/self-serve/lane/relay/machine/enable") && method === "POST") { await route.fulfill(json(selfServe.relayResponse)); return; } if (pathname.endsWith("/modules/self-serve/lane/command") && method === "POST") { await route.fulfill(json(selfServe.commandResponse)); return; } if (pathname.endsWith("/department/lanes/dynamic-image") && method === "GET") { await route.fulfill(binary(selfServe.dynamicImage)); return; } } if (pathname.endsWith("/departments") && method === "GET") { await route.fulfill( json({ data: [ { id: 1, name: "Copenhagen" }, { id: 2, name: "Odense" }, ], }) ); return; } if (pathname.endsWith("/collected-invoices") && method === "GET") { const invoiceFixture = posFixture || { collectedInvoices: [] }; const filters = parseFilterExpressions(parsedUrl.searchParams.get("filters") || ""); const customerNumberFilter = Number(filters.customer_number || 0); const collectedInvoices = (invoiceFixture.collectedInvoices || []).filter((invoice) => { if (!customerNumberFilter) { return true; } return Number(invoice.customer_number) === customerNumberFilter; }); await route.fulfill( json({ data: collectedInvoices, }) ); return; } if (pathname.endsWith("/collected-invoices") && method === "POST") { const invoiceFixture = posFixture || { collectedInvoices: [], customersByNumber: {}, nextCollectedInvoiceId: 200, }; const body = request.postDataJSON?.() || {}; const invoiceId = Number(invoiceFixture.nextCollectedInvoiceId || 200); const customerNumber = Number(body.customer_number || 0); const customer = invoiceFixture.customersByNumber?.[customerNumber] || null; invoiceFixture.nextCollectedInvoiceId = invoiceId + 1; invoiceFixture.collectedInvoices = [ { id: invoiceId, customer_number: customerNumber, customer_name: customer?.name || `Customer ${invoiceId}`, total_net_amount: 0, created_at: body.closed_at || toSqlDateTime().slice(0, 10), closed_at: body.closed_at || null, }, ...(invoiceFixture.collectedInvoices || []), ]; await route.fulfill( json({ success: true, data: { id: invoiceId, }, }) ); return; } if (options.invoiceDistribution) { const monthFromDate = parsedUrl.searchParams.get("dateFrom"); const monthNumber = monthFromDate ? Number(monthFromDate.split("-")[1]) : 1; const monthBase = Number.isFinite(monthNumber) ? monthNumber * 10 : 10; const forceDistributionLegacy = Boolean(options.invoiceDistributionForceLegacyFallback); const forceCompareLegacy = Boolean(options.invoiceDistributionForceCompareFallback); if (pathname.endsWith("/orders") && method === "GET") { await route.fulfill( json({ data: [ { id: 1, created_at: "2026-01-01T00:00:00.000Z", }, ], }) ); return; } if (pathname.endsWith("/superuser/invoicing/period") && method === "GET") { await route.fulfill( json({ data: { types: { all: [ { transactions: [ { amount: 100 + monthBase, booked: true, excluded: false }, { amount: 50 + monthBase, booked: true, excluded: false }, { amount: 25, booked: false, excluded: false }, ], }, ], }, }, }) ); return; } if (pathname.endsWith("/superuser/invoicing/period/distribution/v2/all") && method === "GET") { if (forceDistributionLegacy) { await route.fulfill(json({ message: "v2 distribution temporarily unavailable" }, 500)); return; } await route.fulfill( json({ fixed_pricing: { customers: [ { id: 10, customer_number: 1001, customer_name: "Acme Transport", requires_action: false, transactions: [ { id: 1, date: "2026-01-10T00:00:00.000Z", amount: 80 + monthBase, booked: true, department_id: 1, excluded: false, }, ], meta: { fixed_pricing: { created_at: "2026-01-10T00:00:00.000Z", price: 80 + monthBase, original_price: 120 + monthBase, department_totals_relative: { 1: 40 + monthBase, 2: 40 }, }, }, }, ], collective_results: { total_fixed_price: 80 + monthBase, total_department_totals_relative_parsed: { Copenhagen: 40 + monthBase, Odense: 40, }, }, warnings: [], }, wash_subscriptions: { customers: [ { id: 11, customer_number: 1002, customer_name: "Nordic Haul", requires_action: false, transactions: [ { id: 2, date: "2026-01-05T00:00:00.000Z", amount: 40 + monthBase, booked: true, department_id: 1, excluded: false, }, ], meta: { wash_subscription: { created_at: "2026-01-05T00:00:00.000Z", price: 40 + monthBase, original_price: 55 + monthBase, department_totals_relative: { 1: 20 + monthBase, 2: 20 }, }, }, }, ], collective_results: { total_subscription_price: 40 + monthBase, subscription_price_department_distribution_parsed: { Copenhagen: 20 + monthBase, Odense: 20, }, }, warnings: [], }, customer_prices: { customers: [ { id: 12, customer_number: 1003, customer_name: "Discount Fleet", requires_action: false, transactions: [ { id: 3, date: "2026-01-08T00:00:00.000Z", amount: 15 + monthBase, booked: true, department_id: 2, excluded: false, }, ], meta: { customer_price: { created_at: "2026-01-08T00:00:00.000Z", price: 15 + monthBase, department_totals_relative: { 2: 15 + monthBase }, }, }, }, ], collective_results: { total_customer_price: 15 + monthBase, customer_price_department_distribution_parsed: { Odense: 15 + monthBase, }, }, warnings: [], }, }) ); return; } if (pathname.endsWith("/superuser/invoicing/period/distribution/v2/fixed-pricing") && method === "GET") { if (forceDistributionLegacy) { await route.fulfill(json({ message: "v2 fixed pricing distribution temporarily unavailable" }, 500)); return; } await route.fulfill( json({ customers: [ { id: 10, customer_number: 1001, customer_name: "Acme Transport", requires_action: false, transactions: [ { id: 1, date: "2026-01-10T00:00:00.000Z", amount: 80 + monthBase, booked: true, department_id: 1, excluded: false, }, ], meta: { fixed_pricing: { created_at: "2026-01-10T00:00:00.000Z", price: 80 + monthBase, original_price: 120 + monthBase, department_totals_relative: { 1: 40 + monthBase, 2: 40 }, }, }, }, ], collective_results: { total_fixed_price: 80 + monthBase, total_department_totals_relative_parsed: { Copenhagen: 40 + monthBase, Odense: 40, }, }, warnings: [], }) ); return; } if (pathname.endsWith("/superuser/invoicing/period/distribution/v2/wash-subscriptions") && method === "GET") { if (forceDistributionLegacy) { await route.fulfill(json({ message: "v2 wash subscriptions distribution temporarily unavailable" }, 500)); return; } await route.fulfill( json({ customers: [ { id: 11, customer_number: 1002, customer_name: "Nordic Haul", requires_action: false, transactions: [ { id: 2, date: "2026-01-05T00:00:00.000Z", amount: 40 + monthBase, booked: true, department_id: 1, excluded: false, }, ], meta: { wash_subscription: { created_at: "2026-01-05T00:00:00.000Z", price: 40 + monthBase, original_price: 55 + monthBase, department_totals_relative: { 1: 20 + monthBase, 2: 20 }, }, }, }, ], collective_results: { total_subscription_price: 40 + monthBase, subscription_price_department_distribution_parsed: { Copenhagen: 20 + monthBase, Odense: 20, }, }, warnings: [], }) ); return; } if (pathname.endsWith("/superuser/invoicing/period/distribution/v2/customer-prices") && method === "GET") { if (forceDistributionLegacy) { await route.fulfill(json({ message: "v2 customer prices distribution temporarily unavailable" }, 500)); return; } await route.fulfill( json({ customers: [ { id: 12, customer_number: 1003, customer_name: "Discount Fleet", requires_action: false, transactions: [ { id: 3, date: "2026-01-08T00:00:00.000Z", amount: 15 + monthBase, booked: true, department_id: 2, excluded: false, }, ], meta: { customer_price: { created_at: "2026-01-08T00:00:00.000Z", price: 15 + monthBase, department_totals_relative: { 2: 15 + monthBase }, }, }, }, ], collective_results: { total_customer_price: 15 + monthBase, customer_price_department_distribution_parsed: { Odense: 15 + monthBase, }, }, warnings: [], }) ); return; } if (pathname.endsWith("/superuser/invoicing/period/distribution/fixed-pricing") && method === "GET") { await route.fulfill( json({ data: [ { id: 10, customer_number: 1001, customer_name: "Acme Transport", meta: { fixed_pricing: { created_at: "2026-01-10T00:00:00.000Z", price: 80 + monthBase, original_price: 120 + monthBase, department_totals_relative: { 1: 40 + monthBase, 2: 40 }, }, }, }, ], includes: { collective_fixed_pricing_results: { total_fixed_price: 80 + monthBase, total_department_totals_relative_parsed: { Copenhagen: 40 + monthBase, Odense: 40, }, }, }, }) ); return; } if (pathname.endsWith("/superuser/invoicing/period/distribution/wash-subscriptions") && method === "GET") { await route.fulfill( json({ data: [ { id: 11, customer_number: 1002, customer_name: "Nordic Haul", meta: { wash_subscription: { created_at: "2026-01-05T00:00:00.000Z", price: 40 + monthBase, original_price: 55 + monthBase, department_totals_relative: { 1: 20 + monthBase, 2: 20 }, }, }, }, ], includes: { collective_subscription_results: { total_subscription_price: 40 + monthBase, subscription_price_department_distribution_parsed: { Copenhagen: 20 + monthBase, Odense: 20, }, }, }, }) ); return; } if (pathname.endsWith("/collected-invoices/economic/compare") && method === "GET") { const invoiceId = Number(parsedUrl.searchParams.get("collected_invoice_id") || 0); const mismatch = invoiceId === 101 || invoiceId === 103; await new Promise((resolve) => setTimeout(resolve, 600)); await route.fulfill( json({ data: { collected_invoice_id: invoiceId, warnings: mismatch ? ["Line mismatch found"] : [], internal_total: mismatch ? 150 : 120, booked_total: mismatch ? 145 : 120, draft_total: null, difference: mismatch ? 5 : 0, order_ids: mismatch ? [1, 2] : [3], }, }) ); return; } if (pathname.endsWith("/collected-invoices/economic/v2/compare/bulk") && method === "POST") { if (forceCompareLegacy) { await route.fulfill(json({ message: "v2 compare bulk temporarily unavailable" }, 500)); return; } const body = request.postDataJSON?.() || {}; const ids = Array.isArray(body.collected_invoice_ids) ? body.collected_invoice_ids : []; const results = ids.map((invoiceId) => { const mismatch = Number(invoiceId) === 101 || Number(invoiceId) === 103; const internalTotal = mismatch ? 150 : 120; const targetTotal = mismatch ? 145 : 120; return { collected_invoice_id: Number(invoiceId), warnings: mismatch ? ["Line mismatch found"] : [], details: { order_ids: mismatch ? [1, 2] : [3], customer: { internal_customer_number: 4000 + Number(invoiceId), name: `Customer ${invoiceId}`, }, internal: { normalized: { totals: { net_total: internalTotal, billable_line_count: mismatch ? 2 : 1, }, }, }, }, comparison: { totals: { internal_net_total: internalTotal, }, targets: { booked: { target: "booked", status: mismatch ? "partial_mismatch" : "exact_match", overall_match: !mismatch, totals: { target_net_total: targetTotal, }, mismatch_reasons: mismatch ? ["department_total_mismatch"] : [], warnings: mismatch ? ["Line mismatch found"] : [], lines: { summary: { internal_billable_count: mismatch ? 2 : 1, target_billable_count: mismatch ? 2 : 1, mismatch_count: mismatch ? 1 : 0, }, }, }, }, warnings: mismatch ? ["comparison warning"] : [], }, }; }); await route.fulfill( json({ requested: ids.length, compared: ids.length, failed: 0, results, errors: [], }) ); return; } } if (options.fallbackPassthrough) { await route.fallback(); return; } await route.fulfill(json({ data: [] })); }); } export async function seedAuthenticatedState(page, token = "e2e-token") { await page.addInitScript((value) => { window.localStorage.setItem("token", value); }, token); } export async function primeMockSession(page, { token = "e2e-token", bootPath = "/redirect" } = {}) { await seedAuthenticatedState(page, token); const sessionRequest = page .waitForResponse( (response) => { return response.request().method() === "GET" && response.url().includes("/auth/session"); }, { timeout: 10_000 } ) .catch(() => null); await page.goto(bootPath); await sessionRequest; }