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); syncOrderAttachments(posFixture, orderId); return attachment; } function syncOrderAttachments(posFixture, orderId) { if (!posFixture.ordersById?.[orderId]) { return; } posFixture.ordersById[orderId].attachments = [...(posFixture.attachmentsByOrderId[orderId] || [])]; } function replaceWashCertificateAttachment(posFixture, orderId) { if (!Array.isArray(posFixture.attachmentsByOrderId[orderId])) { posFixture.attachmentsByOrderId[orderId] = []; } const existingAttachmentIndex = (posFixture.attachmentsByOrderId[orderId] || []).findIndex((attachment) => { return String(attachment?.content?.other || "").toUpperCase() === "WASH_CERTIFICATE"; }); if (existingAttachmentIndex === -1) { return null; } const existingAttachment = posFixture.attachmentsByOrderId[orderId][existingAttachmentIndex]; const attachmentId = posFixture.nextAttachmentId++; const replacementAttachment = { ...existingAttachment, id: attachmentId, content: { ...existingAttachment.content, document: `wash_certificate_${orderId}_${attachmentId}.pdf`, other: "WASH_CERTIFICATE", src: null, }, created_at: toSqlDateTime(), updated_at: toSqlDateTime(), deleted_at: null, }; posFixture.attachmentsByOrderId[orderId].splice(existingAttachmentIndex, 1, replacementAttachment); syncOrderAttachments(posFixture, orderId); return replacementAttachment; } function normalizeRelevantOrderUpdateValue(field, value) { if (field === "customer_id") { return normalizePositiveIntegerValue(value); } if (field === "reg_1" || field === "reg_2" || field === "reg_3") { return normalizeRegistrationValue(value); } if (field === "safety_seal") { return normalizeSafetySealValue(value); } return value; } function shouldRegenerateWashCertificateForOrderUpdate(order, body) { if (!order || !body || typeof body !== "object") { return false; } const relevantFields = ["customer_id", "reg_1", "reg_2", "reg_3", "safety_seal"]; return relevantFields.some((field) => { const hasDirectField = Object.prototype.hasOwnProperty.call(body, field); const hasLegacyField = body.field === field; if (!hasDirectField && !hasLegacyField) { return false; } const nextValue = normalizeRelevantOrderUpdateValue(field, hasDirectField ? body[field] : body.value); const currentValue = normalizeRelevantOrderUpdateValue(field, order[field]); return nextValue !== currentValue; }); } 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; const status = overrides.status || "PENDING"; return { id: overrides.id || Date.now(), type, status, request, summary: overrides.summary || { label: status === "COMPLETED" ? "Completed" : status === "FAILED" ? "Failed" : status === "CANCELLED" ? "Cancelled" : status === "CANCEL_REQUESTED" ? "Cancellation requested" : "Queued", progress: ["COMPLETED", "FAILED"].includes(status) ? 100 : 0, retryable: 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 === "CANCEL_REQUESTED") summary.cancel_requested += 1; if (status === "CANCELLED") { summary.cancelled += 1; summary.latest_cancelled_at = summary.latest_cancelled_at || operation.completed_at || null; } 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, cancel_requested: 0, cancelled: 0, completed: 0, failed: 0, latest_cancelled_at: null, 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", "CANCEL_REQUESTED"].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", }); } const containerHealth = gateway.container_health || gateway.metadata?.container_health || { state: gateway.status === "OFFLINE" ? "OFFLINE" : "ONLINE", summary: gateway.status === "OFFLINE" ? "0/6 containers healthy" : "6/6 containers healthy", services: [ { name: "edge-agent", status: gateway.status === "OFFLINE" ? "offline" : "healthy" }, { name: "lan-worker", status: gateway.status === "OFFLINE" ? "offline" : "healthy" }, { name: "redis", status: gateway.status === "OFFLINE" ? "offline" : "healthy" }, { name: "mariadb", status: gateway.status === "OFFLINE" ? "offline" : "healthy" }, { name: "minio", status: gateway.status === "OFFLINE" ? "offline" : "healthy" }, { name: "auto-updater", status: gateway.status === "OFFLINE" ? "offline" : "healthy" }, ], }; const outboxStatus = gateway.outbox_status || gateway.metadata?.outbox_status || { state: "IN_SYNC", queued: 0, summary: "Outbox is empty", oldest_queued_at: null, last_replayed_at: gateway.last_heartbeat_at, }; const updateWindow = gateway.update_window || { window: gateway.metadata?.update_window || "02:00-04:00", strategy: "nightly", timezone: gateway.metadata?.timezone || null, }; const stagedVersion = gateway.staged_version || gateway.metadata?.staged_version || null; const rollbackStatus = gateway.rollback_status || gateway.metadata?.rollback_status || { state: "IDLE", reason: null, rolled_back_to: null, at: null, }; if (Number(outboxStatus.queued || 0) > 0) { diagnostics.push({ code: "EDGE_GATEWAY_OUTBOX_BACKLOG", message: "The gateway has queued outbound control-plane items waiting for replay.", recommended_action: "inspect_connectivity", }); } if (String(rollbackStatus.state || "").toUpperCase() === "ROLLED_BACK") { diagnostics.push({ code: "EDGE_GATEWAY_UPDATE_ROLLED_BACK", message: "The last container rollout was rolled back automatically.", recommended_action: "review_diagnostics", }); } 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, }, container_health: containerHealth, outbox_status: outboxStatus, last_sync_at: gateway.last_sync_at || gateway.metadata?.last_sync_at || outboxStatus.last_replayed_at || null, update_window: updateWindow, staged_version: stagedVersion, rollback_status: rollbackStatus, diagnostics: gateway.diagnostics || diagnostics, error_state: gateway.error_state || (diagnostics[0] ? { ...diagnostics[0] } : activeOperation?.error_code && !["CANCEL_REQUESTED", "CANCELLED"].includes(String(activeOperation?.status || "")) ? { 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), container_health: cloneJson(gateway.container_health || null), outbox_status: cloneJson(gateway.outbox_status || null), last_sync_at: gateway.last_sync_at || null, update_window: cloneJson(gateway.update_window || null), staged_version: cloneJson(gateway.staged_version || null), rollback_status: cloneJson(gateway.rollback_status || 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 buildHttpEdgeGatewayTasksPage(edgeGatewayFixture, gateway) { const gatewayPayload = buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true); return { gateway: gatewayPayload, active_operation: cloneJson(gateway.active_operation || null), operations: cloneJson(gateway.operations || []), recent_commands: cloneJson(gateway.recent_commands || []), recent_operations_summary: cloneJson( gateway.recent_operations_summary || buildEdgeGatewayOperationSummary(gateway.operations || []) ), }; } function buildHttpEdgeGatewayLogsPage(edgeGatewayFixture, gateway) { const gatewayPayload = buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true); const auditLogs = cloneJson(gateway.audit_logs || []); const logEntries = cloneJson(gateway.log_entries || []); const shellSessions = cloneJson(edgeGatewayFixture.shellSessionsByGatewayId?.[gateway.id] || []); const timeline = [ ...auditLogs.map((entry) => ({ type: "audit", level: entry.severity || "INFO", message: entry.action || "AUDIT_EVENT", created_at: entry.created_at || null, entry, })), ...logEntries.map((entry) => ({ type: "log", level: entry.level || "INFO", message: entry.message || "", created_at: entry.created_at || null, entry, })), ...(gateway.operations || []).flatMap((operation) => (operation.events || []).map((entry) => ({ type: "operation_event", level: entry.level || "INFO", message: entry.message || entry.code || "", created_at: entry.created_at || null, entry: { ...entry, operation_id: operation.id, operation_type: operation.type, }, })) ), ].sort((left, right) => String(right.created_at || "").localeCompare(String(left.created_at || ""))); return { gateway: gatewayPayload, timeline, audit_logs: auditLogs, log_entries: logEntries, shell_sessions: shellSessions, }; } function buildHttpEdgeGatewayStatisticsPage(edgeGatewayFixture, gateway) { const gatewayPayload = buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true); return { gateway: gatewayPayload, fleet_usage: buildHttpEdgeGatewayFleetUsage([gatewayPayload]), channel_status: cloneJson(gatewayPayload.channel_status || {}), transport_health: cloneJson(gatewayPayload.transport_health || {}), backlog_depth: cloneJson(gatewayPayload.backlog_depth || {}), container_health: cloneJson(gatewayPayload.container_health || {}), system_metrics: cloneJson( gatewayPayload.metadata?.system_metrics || gatewayPayload.agent_runtime?.system_metrics || {} ), version_drift: cloneJson(gatewayPayload.version_drift || {}), }; } function averageEdgeGatewayMetric(rows = [], metricKey) { const values = rows .map((gateway) => gateway?.agent_runtime?.system_metrics?.[metricKey]) .filter((value) => Number.isFinite(Number(value))) .map((value) => Number(value)); if (!values.length) { return null; } return Math.round(values.reduce((sum, value) => sum + value, 0) / values.length); } function buildHttpEdgeGatewayFleetUsage(rows = []) { const departments = new Set(); for (const gateway of rows) { if (Number(gateway?.department_id || 0) > 0) { departments.add(Number(gateway.department_id)); } } return { gateways: { total: rows.length, departments: departments.size, online: rows.filter((gateway) => gateway?.status === "ONLINE").length, offline: rows.filter((gateway) => gateway?.status === "OFFLINE").length, degraded: rows.filter((gateway) => gateway?.status === "DEGRADED").length, drifted: rows.filter((gateway) => Boolean(gateway?.version_drift?.is_drifted)).length, broker_connected: rows.filter((gateway) => Boolean(gateway?.channel_status?.broker?.connected)).length, }, inventory: { total: rows.reduce((sum, gateway) => sum + Number(gateway?.inventory_summary?.total || 0), 0), online: rows.reduce((sum, gateway) => sum + Number(gateway?.inventory_summary?.online || 0), 0), offline: rows.reduce((sum, gateway) => sum + Number(gateway?.inventory_summary?.offline || 0), 0), }, bindings: { total: rows.reduce((sum, gateway) => sum + Number(gateway?.binding_summary?.total || 0), 0), fallback_overrides: rows.reduce( (sum, gateway) => sum + Number(gateway?.binding_summary?.fallback_overrides || 0), 0 ), cloud_only: rows.reduce((sum, gateway) => sum + Number(gateway?.fallback_summary?.cloud_only_relays || 0), 0), local_only: rows.reduce((sum, gateway) => sum + Number(gateway?.fallback_summary?.local_only_relays || 0), 0), }, operations: { active: rows.filter((gateway) => Boolean(gateway?.active_operation)).length, pending: rows.reduce((sum, gateway) => sum + Number(gateway?.recent_operations_summary?.pending || 0), 0), in_progress: rows.reduce((sum, gateway) => sum + Number(gateway?.recent_operations_summary?.in_progress || 0), 0), backlog: rows.reduce((sum, gateway) => sum + Number(gateway?.backlog_depth?.operations || 0), 0), }, commands: { backlog: rows.reduce((sum, gateway) => sum + Number(gateway?.backlog_depth?.commands || 0), 0), }, system: { latency_ms_avg: averageEdgeGatewayMetric(rows, "latency_ms"), cpu_usage_pct_avg: averageEdgeGatewayMetric(rows, "cpu_usage_pct"), memory_usage_pct_avg: averageEdgeGatewayMetric(rows, "memory_usage_pct"), disk_usage_pct_avg: averageEdgeGatewayMetric(rows, "disk_usage_pct"), }, }; } function processPendingEdgeGatewayClaims(edgeGatewayFixture) { edgeGatewayFixture.pendingClaims = (edgeGatewayFixture.pendingClaims || []).filter((claim) => { claim.pollsRemaining -= 1; if (claim.pollsRemaining > 0) { return true; } const gatewayId = Number(claim.reuse_gateway_id || 0); const claimedAt = toSqlDateTime(); const auditLog = { id: Date.now(), created_at: claimedAt, action: "GATEWAY_CLAIMED", actor_type: "USER", }; const metadata = { broker_connected: true, system_metrics: { latency_ms: 73, cpu_usage_pct: 19, memory_usage_pct: 44, disk_usage_pct: 52, }, update_window: "02:00-04:00", last_sync_at: claimedAt, }; const existingGateway = gatewayId > 0 ? edgeGatewayFixture.gateways.find((gateway) => Number(gateway.id) === gatewayId) || null : null; if (existingGateway) { Object.assign(existingGateway, { department_id: Number(claim.department_id || existingGateway.department_id || 1), label: claim.label || existingGateway.label || `Gateway ${gatewayId}`, hostname: existingGateway.hostname || `edge-${gatewayId}`, is_primary: true, status: "ONLINE", transport_mode: "gateway", department_transport_mode: "gateway", installed_version: existingGateway.installed_version || "php-agent-v1", target_version: existingGateway.target_version || existingGateway.installed_version || "php-agent-v1", last_heartbeat_at: claimedAt, last_seen_ip: "10.9.0.14", discovery_status: existingGateway.discovery_status || "READY", metadata: { ...(existingGateway.metadata || {}), ...metadata, }, audit_logs: [auditLog, ...(existingGateway.audit_logs || [])], }); return false; } const newGatewayId = edgeGatewayFixture.nextGatewayId++; edgeGatewayFixture.gateways.push({ id: newGatewayId, department_id: Number(claim.department_id || 1), label: claim.label || `Gateway ${newGatewayId}`, hostname: `edge-${newGatewayId}`, 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: claimedAt, last_seen_ip: "10.9.0.14", discovery_status: "READY", metadata, inventory: [], bindings: [], recent_commands: [], operations: [], audit_logs: [auditLog], }); return false; }); } function edgeGatewayInstallStepMessage(step) { return ( { VERIFY_TOKEN: "Installer verified the claim token.", INSTALL_PACKAGES: "Installer is preparing the host runtime.", DOWNLOAD_ARTIFACTS: "Installer is downloading gateway artifacts.", WRITE_CONFIG: "Installer is writing gateway configuration.", START_STACK: "Installer is starting the compose stack.", WAIT_FOR_CLAIM: "Installer is waiting for the gateway heartbeat and claim.", CLAIMED: "Gateway claimed successfully.", FAILED: "Installer failed before the gateway could claim.", }[String(step || "").toUpperCase()] || "Installer is running." ); } function pushEdgeGatewayInstallSessionEvent(session, status, step, message) { const event = { status, step, message, at: toSqlDateTime(), }; if (!session.started_at && String(status || "").toUpperCase() !== "PENDING") { session.started_at = event.at; } session.events = [...(session.events || []).slice(-7), event]; session.updated_at = event.at; } function finalizeEdgeGatewayInstallSession(edgeGatewayFixture, session) { edgeGatewayFixture.pendingClaims.push({ department_id: Number(session.department_id || 1), label: String(session.label || "").trim(), pollsRemaining: 0, reuse_gateway_id: Number(session.reuse_gateway_id || 0), }); processPendingEdgeGatewayClaims(edgeGatewayFixture); const claimedGateway = (Number(session.reuse_gateway_id || 0) > 0 && edgeGatewayFixture.gateways.find((gateway) => Number(gateway.id) === Number(session.reuse_gateway_id))) || edgeGatewayFixture.gateways.find( (gateway) => Number(gateway.department_id) === Number(session.department_id || 1) && String(gateway.label || "") .trim() .toLowerCase() === String(session.label || "") .trim() .toLowerCase() ) || edgeGatewayFixture.gateways[edgeGatewayFixture.gateways.length - 1] || null; session.status = "CLAIMED"; session.step = "CLAIMED"; session.message = Number(session.reuse_gateway_id || 0) > 0 ? "Gateway reconnected." : "Gateway connected."; session.terminal = true; session.gateway_id = claimedGateway?.id ?? null; session.last_error = null; session.diagnostics = []; pushEdgeGatewayInstallSessionEvent(session, session.status, session.step, session.message); return session; } function advanceEdgeGatewayInstallSession(edgeGatewayFixture, claimTokenId) { const session = edgeGatewayFixture.installSessionsById?.[claimTokenId] || null; if (!session || session.terminal) { return session; } session.poll_count = Number(session.poll_count || 0) + 1; const steps = [ "VERIFY_TOKEN", "INSTALL_PACKAGES", "DOWNLOAD_ARTIFACTS", "WRITE_CONFIG", "START_STACK", "WAIT_FOR_CLAIM", ]; const failureConfig = session.failure && typeof session.failure === "object" ? session.failure : edgeGatewayFixture.installSessionFailure && typeof edgeGatewayFixture.installSessionFailure === "object" ? edgeGatewayFixture.installSessionFailure : null; const phaseSize = Math.max(1, Math.ceil(Math.max(1, Number(session.claim_polls_remaining || 1)) / steps.length)); const stepIndex = Math.min(steps.length - 1, Math.floor((session.poll_count - 1) / phaseSize)); const step = steps[stepIndex]; const message = edgeGatewayInstallStepMessage(step); if (session.status !== "RUNNING" || session.step !== step || session.message !== message) { session.status = "RUNNING"; session.step = step; session.message = message; pushEdgeGatewayInstallSessionEvent(session, session.status, session.step, session.message); } else { session.updated_at = toSqlDateTime(); } if (failureConfig) { const failureStep = String(failureConfig.step || "START_STACK").toUpperCase(); const failureIndex = Math.max(0, steps.indexOf(failureStep)); const failurePoll = Number(failureConfig.failurePoll || (failureIndex + 1) * phaseSize); if (session.poll_count >= failurePoll) { session.status = "FAILED"; session.step = failureStep; session.message = String(failureConfig.message || "truckwash-edge-gateway-stack.service failed during startup."); session.terminal = true; session.last_error = session.message; session.diagnostics = Array.isArray(failureConfig.diagnostics) ? cloneJson(failureConfig.diagnostics) : [ { name: "systemctl status", output: "Job for truckwash-edge-gateway-stack.service failed because the control process exited with error code.", }, ]; pushEdgeGatewayInstallSessionEvent(session, session.status, session.step, session.message); return session; } } if (session.poll_count >= Number(session.claim_polls_remaining || 1)) { return finalizeEdgeGatewayInstallSession(edgeGatewayFixture, session); } return session; } function buildEdgeGatewayInstallSessionResponse(session) { if (!session) { return null; } return { claim_token_id: session.claim_token_id, department_id: session.department_id, label: session.label || null, expires_at: session.expires_at || null, status: session.status, step: session.step, message: session.message, started_at: session.started_at, updated_at: session.updated_at, terminal: Boolean(session.terminal), gateway_id: session.gateway_id ?? null, last_error: session.last_error ?? null, diagnostics: cloneJson(session.diagnostics || []), events: cloneJson(session.events || []), }; } 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 installSessionsById = Object.fromEntries( Object.entries(options.installSessionsById || {}).map(([claimTokenId, session]) => [ Number(claimTokenId), cloneJson(session), ]) ); 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, }, update_window: "02:00-04:00", last_sync_at: "2026-04-08 08:14:56", container_health: { state: "ONLINE", summary: "6/6 containers healthy", services: [ { name: "edge-agent", status: "healthy" }, { name: "lan-worker", status: "healthy" }, { name: "redis", status: "healthy" }, { name: "mariadb", status: "healthy" }, { name: "minio", status: "healthy" }, { name: "auto-updater", status: "healthy" }, ], }, outbox_status: { state: "IN_SYNC", queued: 0, summary: "Outbox is empty", last_replayed_at: "2026-04-08 08:14:56", }, rollback_status: { state: "IDLE", reason: null, rolled_back_to: null, at: null, }, }, 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: [], log_entries: [ { id: 801, created_at: "2026-04-08 08:14:58", level: "INFO", message: "Broker session connected and telemetry streaming.", }, ], 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: [], log_entries: [ { id: 802, created_at: "2026-04-07 21:05:00", level: "ERROR", message: "Gateway lost broker connectivity and fell back to HTTP.", }, ], 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)), nextClaimTokenId: Number(options.nextClaimTokenId || 9001), nextOperationId: 9901, nextOperationEventId: 19901, claimPollsRemaining: Number(options.claimPollsRemaining || 2), reuseClaimGatewayId: Number(options.reuseClaimGatewayId || 0), installSessionFailure: options.installSessionFailure && typeof options.installSessionFailure === "object" ? cloneJson(options.installSessionFailure) : null, config: { enabled: options.config?.enabled ?? true, default_release_channel: options.config?.default_release_channel || "stable", default_update_window: options.config?.default_update_window || "02:00-04:00", }, installSessionsById, pendingClaims: [], pendingDiscoveryByGatewayId: {}, nextShellSessionId: 3001, shellSessionsByGatewayId: cloneJson(options.shellSessionsByGatewayId || {}), 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 }], departmentCategoriesDelayMs: 0, productsDelayMs: 0, productsDelayMsByCategory: {}, nextOrderId: 54519, nextOrderItemId: 9200, nextAttachmentId: 400, }; return { __isPosFixture: true, ...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] || {}, }, }; } function getPosOrderStripeInvoiceTotal(posFixture, orderId) { return (posFixture.orderItemsByOrderId[orderId] || []).reduce((sum, item) => { return sum + Number(item?.price || 0) * Number(item?.quantity || 1); }, 0); } function isTerminalStripeInvoiceStatus(status) { return ["paid", "void", "uncollectible", "deleted"].includes(String(status || "")); } function createPosStripeModuleOrder(posFixture, orderId, overrides = {}) { const total = getPosOrderStripeInvoiceTotal(posFixture, orderId); const amountDue = Number(overrides.amount_due ?? total); const paid = Boolean(overrides.paid ?? false); const invoiceId = overrides.invoice_id || `in_${orderId}_${Date.now()}`; return { id: Number(overrides.id ?? orderId), invoice_id: invoiceId, customer_id: Number(overrides.customer_id ?? posFixture.ordersById?.[orderId]?.customer_id ?? 0) || null, url: overrides.url || `https://stripe.example.test/invoices/${invoiceId}`, created_at: overrides.created_at || toSqlDateTime(), paid, status: overrides.status || (paid ? "paid" : "open"), amount_due: amountDue, amount_paid: Number(overrides.amount_paid ?? (paid ? amountDue : 0)), }; } function getFixtureDelayMs(value = 0) { const parsed = Number(value || 0); return Number.isFinite(parsed) && parsed > 0 ? parsed : 0; } async function maybeDelayFixtureResponse(delayMs = 0) { const normalizedDelayMs = getFixtureDelayMs(delayMs); if (!normalizedDelayMs) { return; } await new Promise((resolve) => setTimeout(resolve, normalizedDelayMs)); } 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 maybeDelayFixtureResponse(posFixture.departmentCategoriesDelayMs); 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); const categoryDelayMap = posFixture.productsDelayMsByCategory || {}; const categoryDelayMs = category > 0 ? categoryDelayMap[category] ?? categoryDelayMap[String(category)] ?? posFixture.productsDelayMs : posFixture.productsDelayMs; await maybeDelayFixtureResponse(categoryDelayMs); 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); const shouldRegenerateWashCertificate = shouldRegenerateWashCertificateForOrderUpdate( posFixture.ordersById[orderId], body ); 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) } : {}), }; } if (shouldRegenerateWashCertificate) { replaceWashCertificateAttachment(posFixture, orderId); } 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); const shouldRegenerateWashCertificate = shouldRegenerateWashCertificateForOrderUpdate( posFixture.ordersById[orderId], body ); 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) } : {}), }; } } if (shouldRegenerateWashCertificate) { replaceWashCertificateAttachment(posFixture, orderId); } 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); syncOrderAttachments(posFixture, orderId); await route.fulfill(json({ success: true, data: attachment })); return true; } if (pathname.endsWith("/orders/attachments") && method === "DELETE") { const body = request.postDataJSON?.() || {}; const orderId = Number(parsedUrl.searchParams.get("order_id") || body.order_id || 0); const attachmentId = Number(parsedUrl.searchParams.get("attachment_id") || body.attachment_id || 0); posFixture.attachmentsByOrderId[orderId] = (posFixture.attachmentsByOrderId[orderId] || []).filter( (attachment) => attachment.id !== attachmentId ); syncOrderAttachments(posFixture, orderId); 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) { syncOrderAttachments(posFixture, orderId); 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); syncOrderAttachments(posFixture, orderId); 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; } if (pathname.endsWith("/modules/stripe/invoice") && method === "POST") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.order_id || 0); const existingInvoice = posFixture.stripeModuleOrdersByOrderId[orderId] || {}; if (existingInvoice?.invoice_id && !isTerminalStripeInvoiceStatus(existingInvoice.status)) { await route.fulfill( json( { success: false, data: { message: "A Stripe payment link is already active for this order.", code: "stripe_invoice_exists", stripeModuleOrders: existingInvoice, }, }, 409 ) ); return true; } const nextInvoice = createPosStripeModuleOrder(posFixture, orderId, { customer_id: posFixture.ordersById?.[orderId]?.customer_id ?? null, }); posFixture.stripeModuleOrdersByOrderId[orderId] = nextInvoice; await route.fulfill( json({ success: true, data: { id: nextInvoice.invoice_id, customer: nextInvoice.customer_id, hosted_invoice_url: nextInvoice.url, paid: nextInvoice.paid, status: nextInvoice.status, amount_due: nextInvoice.amount_due, amount_paid: nextInvoice.amount_paid, }, }) ); return true; } if (pathname.endsWith("/modules/stripe/invoice") && method === "DELETE") { const body = request.postDataJSON?.() || {}; const orderId = Number(body.order_id || 0); const existingInvoice = posFixture.stripeModuleOrdersByOrderId[orderId] || {}; if (!existingInvoice?.invoice_id) { posFixture.stripeModuleOrdersByOrderId[orderId] = {}; await route.fulfill( json({ success: true, data: { stripeModuleOrders: [], }, }) ); return true; } if (Boolean(existingInvoice.paid) || String(existingInvoice.status || "") === "paid") { await route.fulfill( json( { success: false, data: { message: "A paid Stripe payment link cannot be cancelled.", code: "stripe_invoice_paid", stripeModuleOrders: existingInvoice, }, }, 409 ) ); return true; } posFixture.stripeModuleOrdersByOrderId[orderId] = {}; await route.fulfill( json({ success: true, data: { stripeModuleOrders: [], }, }) ); return true; } return false; } function ensureEdgeGatewayHardwareFixture(edgeGatewayFixture) { if (!edgeGatewayFixture) { return null; } if (!edgeGatewayFixture.hardwareWorkspace) { edgeGatewayFixture.hardwareWorkspace = { nextScannerId: 3, departments: [ { id: 1, name: "Copenhagen", description: "Primary launch department", order_priority: 1, self_serve_enabled: true, lanes: [ { id: 7, department: 1, name: "Lane 7", status: "AVAILABLE", machine_type_id: 1, relay_in_id: null, relay_out_id: null, relay_machine_id: "M-7", relay_machine_program_picker_id: null, relay_machine_cleaner_id: null, dynamic_image_id: 77, self_serve_products: ["Truck", "Van"], }, { id: 8, department: 1, name: "Lane 8", status: "FAULT", machine_type_id: 1, relay_in_id: null, relay_out_id: null, relay_machine_id: "M-8", relay_machine_program_picker_id: null, relay_machine_cleaner_id: null, dynamic_image_id: 78, self_serve_products: ["Truck"], }, ], }, { id: 2, name: "Odense", description: "Fallback transport department", order_priority: 2, self_serve_enabled: true, lanes: [ { id: 9, department: 2, name: "Lane 9", status: "AVAILABLE", machine_type_id: 1, relay_in_id: null, relay_out_id: null, relay_machine_id: "M-9", relay_machine_program_picker_id: null, relay_machine_cleaner_id: null, dynamic_image_id: 79, self_serve_products: ["Truck", "Car"], }, ], }, ], gates: [ { id: 41, department: 1, name: "North Entrance", is_entrance: true, is_exit: false, config: { type: "RELAY", relay_id: "M-7", pulse_seconds: 1, }, }, { id: 42, department: 1, name: "Service Exit", is_entrance: false, is_exit: true, config: { type: "PHONE_CALL", phone_number: "+4512345678", call_duration_threshold: 3, }, }, { id: 43, department: 2, name: "Odense Main Gate", is_entrance: true, is_exit: false, config: { type: "PHONE_CALL", phone_number: "+4598765432", call_duration_threshold: 3, }, }, ], scanners: [ { id: 1, department_id: 1, name: "North scanner", notes: "Mounted at the primary entry lane", lane_id: 7, api_key: "scanner-key-1", }, { id: 2, department_id: 1, name: "South scanner", notes: "Waiting for lane assignment", lane_id: null, api_key: "scanner-key-2", }, ], scans: [ { id: 801, department_id: 1, plate_scanner_id: 1, plate: "AB12345", bay_id: "7", created_at: "2026-04-08 08:44:07", }, { id: 802, department_id: 1, plate_scanner_id: 2, plate: "CD67890", bay_id: "8", created_at: "2026-04-08 08:31:05", }, ], }; } return edgeGatewayFixture.hardwareWorkspace; } function buildEdgeGatewayHardwareBindingsIndex(edgeGatewayFixture, departmentId) { const gateways = edgeGatewayFixture.gateways.filter((gateway) => Number(gateway.department_id) === Number(departmentId)); const bindingsByRelayId = {}; gateways.forEach((gateway) => { (gateway.bindings || []).forEach((binding) => { const relayId = String(binding?.relay_id || "").trim(); if (!relayId) { return; } if (!bindingsByRelayId[relayId]) { bindingsByRelayId[relayId] = []; } bindingsByRelayId[relayId].push({ ...cloneJson(binding), gateway_id: gateway.id, gateway_label: gateway.label || `Gateway ${gateway.id}`, gateway_status: gateway.status || "OFFLINE", is_primary_gateway: Boolean(gateway.is_primary), }); }); }); Object.values(bindingsByRelayId).forEach((bindings) => { bindings.sort((left, right) => { if (Number(Boolean(right.is_primary_gateway)) !== Number(Boolean(left.is_primary_gateway))) { return Number(Boolean(right.is_primary_gateway)) - Number(Boolean(left.is_primary_gateway)); } return Number(left.gateway_id || 0) - Number(right.gateway_id || 0); }); }); return { gateways, bindingsByRelayId, }; } function buildEdgeGatewayHardwareRelayCoverage(bindingsByRelayId, relayId) { const bindings = bindingsByRelayId[String(relayId || "").trim()] || []; return { relay_id: relayId, covered: bindings.length > 0, status: bindings.length > 0 ? "BOUND" : "MISSING", binding_count: bindings.length, primary_binding: bindings[0] || null, bindings, }; } function buildEdgeGatewayHardwareDepartmentWorkspace(edgeGatewayFixture, departmentId, includeGateways = true) { const hardware = ensureEdgeGatewayHardwareFixture(edgeGatewayFixture); const department = (hardware?.departments || []).find((entry) => Number(entry.id) === Number(departmentId)) || null; if (!department) { return null; } const { gateways, bindingsByRelayId } = buildEdgeGatewayHardwareBindingsIndex(edgeGatewayFixture, departmentId); const gatewayPayloads = gateways.map((gateway) => buildHttpEdgeGatewayGateway(edgeGatewayFixture, settleEdgeGatewayWork(edgeGatewayFixture, gateway.id), true) ); const lanes = (department.lanes || []).map((lane) => { const relaySlots = [ ["ENTRY", lane.relay_in_id], ["EXIT", lane.relay_out_id], ["MACHINE", lane.relay_machine_id], ["PROGRAM_PICKER", lane.relay_machine_program_picker_id], ["CLEANER", lane.relay_machine_cleaner_id], ] .filter(([, relayId]) => Boolean(relayId)) .map(([slot, relayId]) => ({ slot, relay_id: relayId, catalog: relayId ? { relay_id: relayId } : null, coverage: buildEdgeGatewayHardwareRelayCoverage(bindingsByRelayId, relayId), })); const requiredRelayCount = relaySlots.length; const boundRelayCount = relaySlots.filter((slot) => slot.coverage.covered).length; return { id: lane.id, department: lane.department, name: lane.name, relay_in_id: lane.relay_in_id, relay_out_id: lane.relay_out_id, relay_machine_id: lane.relay_machine_id, relay_machine_program_picker_id: lane.relay_machine_program_picker_id, relay_machine_cleaner_id: lane.relay_machine_cleaner_id, dynamic_image_id: lane.dynamic_image_id, machine_type_id: lane.machine_type_id, status: lane.status, self_serve_products: cloneJson(lane.self_serve_products || []), relay_slots: relaySlots, binding_coverage: { required: requiredRelayCount, bound: boundRelayCount, missing: Math.max(0, requiredRelayCount - boundRelayCount), state: requiredRelayCount === 0 ? "NOT_REQUIRED" : boundRelayCount === requiredRelayCount ? "READY" : "MISSING", }, links: { legacy: `/superuser/department/lanes/${lane.id}`, self_serve_studio: `/admin/${department.id}/modules/self-serve/studio`, }, }; }); const selfServe = { enabled: Boolean(department.self_serve_enabled), lane_count: lanes.length, ready_lanes: lanes.filter((lane) => String(lane.binding_coverage?.state || "") === "READY").length, configured_task_count: lanes.reduce((sum, lane) => sum + (lane.self_serve_products?.length || 0), 0), configured_product_count: new Set(lanes.flatMap((lane) => lane.self_serve_products || [])).size, readiness_state: !department.self_serve_enabled ? "DISABLED" : lanes.length === 0 ? "UNCONFIGURED" : lanes.every((lane) => String(lane.binding_coverage?.state || "") === "READY") ? "READY" : "PARTIAL", links: { studio: `/admin/${department.id}/modules/self-serve/studio`, legacy: "/superuser/selfserve", }, }; const gates = (hardware.gates || []) .filter((gate) => Number(gate.department) === Number(departmentId)) .map((gate) => { const transportType = String(gate?.config?.type || "PHONE_CALL").toUpperCase(); const relayId = String(gate?.config?.relay_id || "").trim(); const coverage = transportType === "RELAY" && relayId ? buildEdgeGatewayHardwareRelayCoverage(bindingsByRelayId, relayId) : null; return { id: gate.id, department: gate.department, name: gate.name, is_entrance: Boolean(gate.is_entrance), is_exit: Boolean(gate.is_exit), config: cloneJson(gate.config || {}), transport_type: transportType, config_complete: transportType === "PHONE_CALL" ? Boolean(gate?.config?.phone_number) && Number(gate?.config?.call_duration_threshold || 0) > 0 : Boolean(relayId), relay: relayId ? { relay_id: relayId } : null, coverage, }; }); const laneIndex = Object.fromEntries(lanes.map((lane) => [Number(lane.id), lane])); const scansByScannerId = {}; (hardware.scans || []) .filter((scan) => Number(scan.department_id) === Number(departmentId)) .sort((left, right) => new Date(String(right.created_at || 0)).getTime() - new Date(String(left.created_at || 0)).getTime()) .forEach((scan) => { const scannerId = Number(scan.plate_scanner_id || 0); if (!scansByScannerId[scannerId]) { scansByScannerId[scannerId] = []; } if (scansByScannerId[scannerId].length >= 5) { return; } scansByScannerId[scannerId].push(cloneJson(scan)); }); const scanners = (hardware.scanners || []) .filter((scanner) => Number(scanner.department_id) === Number(departmentId)) .map((scanner) => { const assignedLane = scanner.lane_id ? laneIndex[Number(scanner.lane_id)] || null : null; const assignmentState = !scanner.lane_id ? "UNASSIGNED" : !assignedLane ? "INVALID" : Number(assignedLane?.binding_coverage?.missing || 0) === 0 ? "READY" : "PARTIAL"; const recentScans = scansByScannerId[Number(scanner.id)] || []; return { ...cloneJson(scanner), assigned_lane: assignedLane, assignment_state: assignmentState, recent_scan_at: recentScans[0]?.created_at || null, recent_scans: recentScans, recent_scan_count: recentScans.length, }; }); const issues = []; const onlineGatewayCount = gatewayPayloads.filter((gateway) => String(gateway?.status || "").toUpperCase() === "ONLINE").length; const transportMode = String(gatewayPayloads[0]?.department_transport_mode || gateways[0]?.department_transport_mode || "cloud"); if (gatewayPayloads.length === 0) { issues.push({ severity: "danger", code: "NO_GATEWAY", message: "No edge gateway has been claimed for this department." }); } else if (transportMode === "gateway" && onlineGatewayCount === 0) { issues.push({ severity: "danger", code: "NO_ONLINE_GATEWAY", message: "Gateway transport mode is enabled, but no department gateway is currently online.", }); } lanes.forEach((lane) => { if (Number(lane.binding_coverage?.missing || 0) > 0) { issues.push({ severity: "warning", code: "LANE_BINDING_GAP", message: `Lane ${lane.name} is missing relay bindings.`, target_type: "lane", target_id: lane.id, }); } }); gates.forEach((gate) => { if (!gate.config_complete) { issues.push({ severity: "warning", code: "GATE_CONFIG_INCOMPLETE", message: `Gate ${gate.name} has incomplete transport configuration.`, target_type: "gate", target_id: gate.id, }); return; } if (gate.transport_type === "RELAY" && !gate.coverage?.covered) { issues.push({ severity: "warning", code: "GATE_BINDING_MISSING", message: `Gate ${gate.name} is assigned to an unbound relay.`, target_type: "gate", target_id: gate.id, }); } }); scanners.forEach((scanner) => { if (scanner.assignment_state === "UNASSIGNED") { issues.push({ severity: "warning", code: "SCANNER_UNASSIGNED", message: `Scanner ${scanner.name} is not assigned to a default lane.`, target_type: "scanner", target_id: scanner.id, }); } else if (scanner.assignment_state === "PARTIAL") { issues.push({ severity: "info", code: "SCANNER_LANE_PARTIAL", message: `Scanner ${scanner.name} is assigned to a lane with missing relay coverage.`, target_type: "scanner", target_id: scanner.id, }); } }); if (selfServe.enabled && Number(selfServe.ready_lanes || 0) < Number(selfServe.lane_count || 0)) { issues.push({ severity: "warning", code: "SELFSERVE_PARTIAL_READY", message: "Self-serve is enabled, but one or more lanes are missing required relay coverage.", }); } const actions = [ { code: "OPEN_GATEWAY_TAB", label: "Open gateway controls", path: `/superuser/departments/${departmentId}/gateways?tab=gateways`, }, ]; if (gatewayPayloads.length === 0) { actions.push({ code: "INSTALL_GATEWAY", label: "Install first edge gateway", path: "/superuser/configuration/edgegateway", }); } if (lanes.some((lane) => Number(lane.binding_coverage?.missing || 0) > 0)) { actions.push({ code: "REVIEW_LANE_BINDINGS", label: "Resolve lane bindings", path: `/superuser/departments/${departmentId}/gateways?tab=lanes`, }); } if (gates.some((gate) => gate.transport_type === "RELAY" && !gate.coverage?.covered)) { actions.push({ code: "REVIEW_GATE_BINDINGS", label: "Resolve gate relay bindings", path: `/superuser/departments/${departmentId}/gateways?tab=gates`, }); } if (scanners.some((scanner) => scanner.assignment_state === "UNASSIGNED")) { actions.push({ code: "ASSIGN_SCANNERS", label: "Assign scanners to lanes", path: `/superuser/departments/${departmentId}/gateways?tab=scanners`, }); } if (selfServe.enabled && lanes.length > 0) { actions.push({ code: "OPEN_SELFSERVE_STUDIO", label: "Open self-serve studio", path: `/admin/${departmentId}/modules/self-serve/studio`, }); } const requiredRelayIds = new Set(); const coveredRelayIds = new Set(); lanes.forEach((lane) => { (lane.relay_slots || []).forEach((slot) => { if (!slot?.relay_id) { return; } requiredRelayIds.add(slot.relay_id); if (slot.coverage?.covered) { coveredRelayIds.add(slot.relay_id); } }); }); gates.forEach((gate) => { const relayId = String(gate?.relay?.relay_id || gate?.config?.relay_id || "").trim(); if (!relayId) { return; } requiredRelayIds.add(relayId); if (gate.coverage?.covered) { coveredRelayIds.add(relayId); } }); const primaryGateway = gatewayPayloads.find((gateway) => gateway?.is_primary) || gatewayPayloads[0] || null; const recentScanAt = scanners .map((scanner) => scanner?.recent_scan_at) .filter(Boolean) .sort((left, right) => new Date(String(right || 0)).getTime() - new Date(String(left || 0)).getTime())[0] || null; const health = issues.some((issue) => issue.severity === "danger") ? "AT_RISK" : issues.length > 0 ? "PARTIAL" : "READY"; const summary = { department_id: department.id, department_name: department.name, order_priority: department.order_priority, transport_mode: transportMode, gateway_count: gatewayPayloads.length, online_gateway_count: onlineGatewayCount, primary_gateway: primaryGateway ? { id: primaryGateway.id, label: primaryGateway.label || `Gateway ${primaryGateway.id}`, status: primaryGateway.status || "OFFLINE", } : null, lane_count: lanes.length, self_serve_enabled: selfServe.enabled, self_serve_ready_lanes: selfServe.ready_lanes, required_relay_count: requiredRelayIds.size, bound_relay_count: coveredRelayIds.size, missing_binding_count: Math.max(0, requiredRelayIds.size - coveredRelayIds.size), gate_count: gates.length, gate_transport_mix: { relay: gates.filter((gate) => gate.transport_type === "RELAY").length, phone_call: gates.filter((gate) => gate.transport_type === "PHONE_CALL").length, }, scanner_count: scanners.length, assigned_scanner_count: scanners.filter((scanner) => Number(scanner.lane_id || 0) > 0).length, recent_scan_at: recentScanAt, issue_count: issues.length, health, }; return { department: { id: department.id, name: department.name, description: department.description, order_priority: department.order_priority, }, summary, gateways: includeGateways ? gatewayPayloads : [], lanes, self_serve: selfServe, gates, scanners, issues, actions, }; } 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; }; const edgeGatewayCollectionPattern = /\/(?:modules\/)?edge-gateways$/; const edgeGatewayWorkspaceDepartmentsPattern = /\/modules\/edge-gateways\/workspace\/departments$/; const edgeGatewayWorkspaceDepartmentPattern = /\/modules\/edge-gateways\/workspace\/departments\/(\d+)$/; const edgeGatewayDetailPattern = /\/(?:modules\/)?edge-gateways\/(\d+)$/; const edgeGatewayTasksPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/tasks$/; const edgeGatewayLogsPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/logs$/; const edgeGatewayStatisticsPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/statistics$/; const edgeGatewayStreamSessionPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/stream-session$/; const edgeGatewayOperationsPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/operations$/; const edgeGatewayOperationEventsPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/operations\/(\d+)\/events$/; const edgeGatewayOperationCancelPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/operations\/(\d+)\/cancel$/; const edgeGatewayRotateCredentialsPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/rotate-credentials$/; const edgeGatewayDiscoveryPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/discovery$/; const edgeGatewayInstallTokenPattern = /\/(?:modules\/)?edge-gateways\/install-token$/; const edgeGatewayInstallTokenStatusPattern = /\/(?:modules\/)?edge-gateways\/install-token\/(\d+)\/status$/; const edgeGatewayBindingsPattern = /\/(?:modules\/)?edge-gateways\/(\d+)\/bindings$/; const numberPlateScannersPattern = /\/numberplatescanners$/; const numberPlateScannerRotatePattern = /\/numberplatescanners\/(\d+)\/rotate-key$/; const edgeGatewayDeletePattern = /\/(?:modules\/)?edge-gateways\/(\d+)$/; const edgeGatewayCutoverPattern = /\/(?:modules\/edge-gateways\/departments\/(\d+)\/cutover|departments\/(\d+)\/gateway-cutover)$/; const edgeGatewayUnsupportedPattern = /\/(?:modules\/)?edge-gateways\/\d+\/(?:update-jobs|uninstall)(?:\/.*)?$/; const edgeGatewayShellPattern = /\/(?:modules\/)?edge-gateways\/\d+\/shell-sessions(?:\/.*)?$/; const buildConfigEntries = () => [ { variable: "enabled", value: edgeGatewayFixture.config.enabled }, { variable: "default_release_channel", value: edgeGatewayFixture.config.default_release_channel }, { variable: "default_update_window", value: edgeGatewayFixture.config.default_update_window }, ]; if (pathname.endsWith("/edgegateway/config") && method === "GET") { await route.fulfill(json({ data: buildConfigEntries() })); return true; } if (pathname.endsWith("/edgegateway/config") && method === "POST") { const body = request.postDataJSON?.() || {}; const variable = String(body.variable || ""); if (variable === "enabled") { edgeGatewayFixture.config.enabled = !( body.value === false || body.value === "false" || body.value === 0 || body.value === "0" ); } else if (variable === "default_release_channel") { edgeGatewayFixture.config.default_release_channel = String(body.value || "stable"); } else if (variable === "default_update_window") { edgeGatewayFixture.config.default_update_window = String(body.value || "02:00-04:00"); } else { await route.fulfill( json( { success: false, data: { message: "Variable and value not set", }, }, 400 ) ); return true; } await route.fulfill(json({ data: buildConfigEntries() })); return true; } if (edgeGatewayWorkspaceDepartmentsPattern.test(pathname) && method === "GET") { const hardware = ensureEdgeGatewayHardwareFixture(edgeGatewayFixture); const summaries = (hardware?.departments || []) .map((department) => buildEdgeGatewayHardwareDepartmentWorkspace(edgeGatewayFixture, department.id, false)?.summary) .filter(Boolean) .sort((left, right) => Number(left?.order_priority || 0) - Number(right?.order_priority || 0)); await route.fulfill(json({ data: summaries })); return true; } if (edgeGatewayWorkspaceDepartmentPattern.test(pathname) && method === "GET") { const departmentId = extractMatchId(edgeGatewayWorkspaceDepartmentPattern); const payload = buildEdgeGatewayHardwareDepartmentWorkspace(edgeGatewayFixture, departmentId, true); await route.fulfill(payload ? json({ data: payload }) : json({ message: "Department not found" }, 404)); return true; } if (numberPlateScannersPattern.test(pathname) && method === "GET") { const hardware = ensureEdgeGatewayHardwareFixture(edgeGatewayFixture); const rows = cloneJson(hardware?.scanners || []); const paginated = paginateRows(rows, 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 (numberPlateScannersPattern.test(pathname) && method === "POST") { const hardware = ensureEdgeGatewayHardwareFixture(edgeGatewayFixture); const body = request.postDataJSON?.() || {}; const scanner = { id: hardware.nextScannerId++, department_id: Number(body.department_id || 0), name: String(body.name || ""), notes: String(body.notes || ""), lane_id: body.lane_id === null || body.lane_id === undefined || body.lane_id === "" ? null : Number(body.lane_id), api_key: `scanner-key-${Date.now()}`, }; hardware.scanners.push(scanner); await route.fulfill(json({ success: true, data: cloneJson(scanner) }, 201)); return true; } if (numberPlateScannersPattern.test(pathname) && method === "PUT") { const hardware = ensureEdgeGatewayHardwareFixture(edgeGatewayFixture); const body = request.postDataJSON?.() || {}; const scannerId = Number(body.id || 0); const scannerIndex = (hardware.scanners || []).findIndex((scanner) => Number(scanner.id) === scannerId); if (scannerIndex === -1) { await route.fulfill(json({ message: "Scanner not found" }, 404)); return true; } hardware.scanners[scannerIndex] = { ...hardware.scanners[scannerIndex], department_id: Number(body.department_id || hardware.scanners[scannerIndex].department_id || 0), name: body.name === undefined ? hardware.scanners[scannerIndex].name : String(body.name || ""), notes: body.notes === undefined ? hardware.scanners[scannerIndex].notes : String(body.notes || ""), lane_id: body.lane_id === undefined ? hardware.scanners[scannerIndex].lane_id : body.lane_id === null || body.lane_id === "" ? null : Number(body.lane_id), }; await route.fulfill(json({ success: true, data: cloneJson(hardware.scanners[scannerIndex]) })); return true; } if (numberPlateScannerRotatePattern.test(pathname) && method === "POST") { const hardware = ensureEdgeGatewayHardwareFixture(edgeGatewayFixture); const scannerId = extractMatchId(numberPlateScannerRotatePattern); const scanner = (hardware.scanners || []).find((entry) => Number(entry.id) === scannerId) || null; if (!scanner) { await route.fulfill(json({ message: "Scanner not found" }, 404)); return true; } scanner.api_key = `rotated-scanner-key-${scanner.id}`; await route.fulfill( json({ success: true, data: { scanner: cloneJson(scanner), api_key: scanner.api_key, }, }) ); return true; } if (edgeGatewayCollectionPattern.test(pathname) && 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; const gatewayRows = gateways.map((gateway) => buildHttpEdgeGatewayGateway(edgeGatewayFixture, settleEdgeGatewayWork(edgeGatewayFixture, gateway.id), false) ); await route.fulfill( json({ data: gatewayRows, meta: { fleet_usage: buildHttpEdgeGatewayFleetUsage(gatewayRows), }, }) ); return true; } if (edgeGatewayDetailPattern.test(pathname) && method === "GET") { const gatewayId = extractMatchId(edgeGatewayDetailPattern); 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 (edgeGatewayTasksPattern.test(pathname) && method === "GET") { const gatewayId = extractMatchId(edgeGatewayTasksPattern); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); await route.fulfill( gateway ? json({ data: buildHttpEdgeGatewayTasksPage(edgeGatewayFixture, gateway) }) : json({ message: "Gateway not found" }, 404) ); return true; } if (edgeGatewayLogsPattern.test(pathname) && method === "GET") { const gatewayId = extractMatchId(edgeGatewayLogsPattern); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); await route.fulfill( gateway ? json({ data: buildHttpEdgeGatewayLogsPage(edgeGatewayFixture, gateway) }) : json({ message: "Gateway not found" }, 404) ); return true; } if (edgeGatewayStatisticsPattern.test(pathname) && method === "GET") { const gatewayId = extractMatchId(edgeGatewayStatisticsPattern); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); await route.fulfill( gateway ? json({ data: buildHttpEdgeGatewayStatisticsPage(edgeGatewayFixture, gateway) }) : json({ message: "Gateway not found" }, 404) ); return true; } if (edgeGatewayStreamSessionPattern.test(pathname) && method === "POST") { const gatewayId = extractMatchId(edgeGatewayStreamSessionPattern); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); const body = request.postDataJSON?.() || {}; const scopes = Array.isArray(body.scopes) && body.scopes.length ? body.scopes : ["overview", "tasks", "logs", "statistics"]; await route.fulfill( gateway ? json( { data: { token: `mock-stream-token-${gatewayId}`, gateway_id: gatewayId, expires_at: "2026-04-09 10:00:00", scopes, broker_url: "https://broker.example.test", ws_url: "mock-ws://edge-broker/browser-gateway-stream", mock_events: [ { delay_ms: 40, data: { type: "presence.changed", gatewayId: String(gatewayId), status: "connected" }, }, { delay_ms: 120, data: { type: "gateway.telemetry", gatewayId: String(gatewayId), telemetry: cloneJson(gateway.metadata?.system_metrics || {}), gateway: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true), }, }, { delay_ms: 200, data: { type: "stats.updated", gatewayId: String(gatewayId), statistics: buildHttpEdgeGatewayStatisticsPage(edgeGatewayFixture, gateway), }, }, ], }, }, 201 ) : json({ message: "Gateway not found" }, 404) ); return true; } if (edgeGatewayOperationsPattern.test(pathname) && method === "GET") { const gatewayId = extractMatchId(edgeGatewayOperationsPattern); const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId); await route.fulfill(json({ data: cloneJson(gateway?.operations || []) })); return true; } if (edgeGatewayOperationEventsPattern.test(pathname) && method === "GET") { const gatewayId = extractMatchId(edgeGatewayOperationEventsPattern); const operationId = Number(pathname.match(edgeGatewayOperationEventsPattern)?.[2] || 0); 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 (edgeGatewayOperationCancelPattern.test(pathname) && method === "POST") { const gatewayId = extractMatchId(edgeGatewayOperationCancelPattern); const operationId = Number(pathname.match(edgeGatewayOperationCancelPattern)?.[2] || 0); const gateway = findGateway(gatewayId); const now = toSqlDateTime(); if (!gateway) { await route.fulfill(json({ message: "Gateway not found" }, 404)); return true; } const operation = (gateway.operations || []).find((entry) => Number(entry.id) === Number(operationId)) || null; if (!operation) { await route.fulfill(json({ message: "Operation not found" }, 404)); return true; } if (operation.status === "IN_PROGRESS" || operation.status === "CANCEL_REQUESTED") { operation.status = "CANCELLED"; operation.completed_at = now; operation.updated_at = now; operation.error_code = "EDGE_GATEWAY_CANCELLED"; operation.error_message = "Operation cancelled by operator"; operation.summary = { ...(operation.summary || {}), label: "Cancelled", retryable: true, }; operation.events = [ ...(operation.events || []), { id: edgeGatewayFixture.nextOperationEventId++, level: "WARNING", code: "OPERATION_CANCELLED", message: "Operation cancelled by operator", created_at: now, }, ]; delete edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId]; } else if (operation.status === "PENDING") { operation.status = "CANCELLED"; operation.completed_at = now; operation.updated_at = now; operation.error_code = "EDGE_GATEWAY_CANCELLED"; operation.error_message = "Operation cancelled by operator"; operation.summary = { ...(operation.summary || {}), label: "Cancelled", retryable: true, }; } gateway.audit_logs = [ { id: Date.now(), created_at: now, action: "GATEWAY_OPERATION_CANCELLED", actor_type: "USER" }, ...(gateway.audit_logs || []), ]; Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway)); await route.fulfill( json({ data: { operation: cloneJson(operation), gateway: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true), }, }) ); return true; } if (edgeGatewayOperationsPattern.test(pathname) && method === "POST") { const gatewayId = extractMatchId(edgeGatewayOperationsPattern); const gateway = findGateway(gatewayId); const body = request.postDataJSON?.() || {}; if (!gateway) { await route.fulfill(json({ message: "Gateway not found" }, 404)); return true; } const activeOperation = (gateway.operations || []).find((entry) => ["PENDING", "IN_PROGRESS", "CANCEL_REQUESTED"].includes(String(entry.status || "")) ); if (activeOperation) { await route.fulfill( json( { data: { message: "Another gateway operation is already active", error_code: "EDGE_GATEWAY_CONFLICT", }, }, 409 ) ); 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.staged_version = { target_version: gateway.target_version, staged_at: now, apply_after: "2026-04-09T02:00:00+02:00", status: "STAGED", }; operation = createOperation(gateway, "UPDATE", operationRequest, { status: "COMPLETED", started_at: now, completed_at: now, updated_at: now, summary: { label: "Completed", progress: 100, retryable: true }, result: { applied: false, installed_version: gateway.installed_version, staged_version: gateway.target_version, target_version: gateway.target_version, apply_after: "2026-04-09T02:00:00+02:00", update_window: "02:00-04:00", restart_required: true, rollback_status: gateway.rollback_status || { state: "IDLE" }, }, 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 (edgeGatewayRotateCredentialsPattern.test(pathname) && method === "POST") { const gatewayId = extractMatchId(edgeGatewayRotateCredentialsPattern); 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", stackServiceName: "truckwash-edge-gateway-stack.service", composeFileName: "docker-compose.gateway.yml", composeProjectName: "truckwash-edge-gateway", launcherScriptName: "gateway-launcher.sh", runtimeDir: "/opt/truckwash-edge-agent/runtime", stateDatabasePath: "/opt/truckwash-edge-agent/runtime/gateway-state.sqlite", workerBaseUrl: "http://lan-worker:8090", updateWindow: "02:00-04:00", heartbeatIntervalSeconds: 15, operationPollTimeoutSeconds: 20, }, null, 2 ), restart_instructions: [ "sudo systemctl restart truckwash-edge-gateway-stack.service", "sudo systemctl status truckwash-edge-gateway-stack.service --no-pager", "cd /opt/truckwash-edge-agent && sudo ./gateway-launcher.sh reconcile", ], }, }) ); return true; } if (edgeGatewayDiscoveryPattern.test(pathname) && method === "POST") { const gatewayId = extractMatchId(edgeGatewayDiscoveryPattern); 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 (edgeGatewayInstallTokenPattern.test(pathname) && method === "POST") { const body = request.postDataJSON?.() || {}; const claimTokenId = edgeGatewayFixture.nextClaimTokenId++; const session = { claim_token_id: claimTokenId, department_id: Number(body.department_id || 1), label: String(body.label || "").trim(), expires_at: "2026-04-08 08:45:00", status: "PENDING", step: "PENDING", message: "Installer command generated. Run it on the gateway host.", started_at: null, updated_at: null, terminal: false, gateway_id: null, last_error: null, diagnostics: [], events: [], claim_polls_remaining: Math.max(1, Number(edgeGatewayFixture.claimPollsRemaining || 1)), reuse_gateway_id: Number(edgeGatewayFixture.reuseClaimGatewayId || 0), }; pushEdgeGatewayInstallSessionEvent(session, session.status, session.step, session.message); edgeGatewayFixture.installSessionsById[claimTokenId] = session; await route.fulfill( json( { data: { claim_token_id: claimTokenId, token: "edge-install-token", expires_at: session.expires_at, 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 (edgeGatewayInstallTokenStatusPattern.test(pathname) && method === "GET") { const claimTokenId = extractMatchId(edgeGatewayInstallTokenStatusPattern); const session = advanceEdgeGatewayInstallSession(edgeGatewayFixture, claimTokenId); await route.fulfill( session ? json({ data: buildEdgeGatewayInstallSessionResponse(session) }) : json({ message: "Install token not found" }, 404) ); return true; } if (edgeGatewayDetailPattern.test(pathname) && method === "PUT") { const gatewayId = extractMatchId(edgeGatewayDetailPattern); 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 (edgeGatewayBindingsPattern.test(pathname) && method === "PUT") { const gatewayId = extractMatchId(edgeGatewayBindingsPattern); 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 (edgeGatewayShellPattern.test(pathname) && method === "POST") { const gatewayId = extractMatchId(edgeGatewayShellPattern); const gateway = findGateway(gatewayId); if (!gateway) { await route.fulfill(json({ message: "Gateway not found" }, 404)); return true; } const sessionId = edgeGatewayFixture.nextShellSessionId++; const body = request.postDataJSON?.() || {}; const session = { id: sessionId, gateway_id: gatewayId, department_id: gateway.department_id, actor_user_id: 1, status: "OPEN", reason: body.reason || "Interactive diagnostic terminal", cwd: body.cwd || "/opt/truckwash-edge-agent", cols: Number(body.cols || 120), rows: Number(body.rows || 28), approved_at: "2026-04-09 09:15:00", opened_at: "2026-04-09 09:15:01", closed_at: null, }; if (!Array.isArray(edgeGatewayFixture.shellSessionsByGatewayId[gatewayId])) { edgeGatewayFixture.shellSessionsByGatewayId[gatewayId] = []; } edgeGatewayFixture.shellSessionsByGatewayId[gatewayId] = [ session, ...edgeGatewayFixture.shellSessionsByGatewayId[gatewayId], ]; gateway.audit_logs = [ { id: Date.now(), created_at: "2026-04-09 09:15:01", action: "GATEWAY_SHELL_SESSION_CREATED", actor_type: "USER", }, ...(gateway.audit_logs || []), ]; await route.fulfill( json( { data: { session, token: `mock-shell-token-${gatewayId}-${sessionId}`, gateway_id: gatewayId, expires_at: "2026-04-09 10:15:00", broker_url: "https://broker.example.test", ws_url: "mock-ws://edge-broker/browser-shell", mock_banner: `Connected to ${gateway.label || gateway.hostname}.\n`, mock_prompt: "edge@truckwash:/opt/truckwash-edge-agent$ ", }, }, 201 ) ); return true; } if (edgeGatewayUnsupportedPattern.test(pathname) || edgeGatewayShellPattern.test(pathname)) { await route.fulfill(json({ message: "Route not found" }, 404)); return true; } if (edgeGatewayDeletePattern.test(pathname) && method === "DELETE") { const gatewayId = extractMatchId(edgeGatewayDeletePattern); 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 (edgeGatewayCutoverPattern.test(pathname) && method === "POST") { const cutoverMatch = pathname.match(edgeGatewayCutoverPattern); const departmentId = Number(cutoverMatch?.[1] || cutoverMatch?.[2] || 0); 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 ? options.pos && options.pos !== true && options.pos.__isPosFixture ? options.pos : 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; }