Remove edge gateway components and templates associated with outdated workflows.
This commit is contained in:
+349
-207
@@ -635,9 +635,74 @@ function mergeEdgeGatewayFixtureRecord(baseGateway, overrides = {}) {
|
||||
bindings: Array.isArray(overrides.bindings) ? overrides.bindings : baseGateway.bindings,
|
||||
recent_commands: Array.isArray(overrides.recent_commands) ? overrides.recent_commands : baseGateway.recent_commands,
|
||||
audit_logs: Array.isArray(overrides.audit_logs) ? overrides.audit_logs : baseGateway.audit_logs,
|
||||
operations: Array.isArray(overrides.operations) ? overrides.operations : baseGateway.operations || [],
|
||||
};
|
||||
}
|
||||
|
||||
function createFixtureOperation(type, request = {}, overrides = {}) {
|
||||
const startedAt = overrides.started_at || null;
|
||||
const completedAt = overrides.completed_at || null;
|
||||
return {
|
||||
id: overrides.id || Date.now(),
|
||||
type,
|
||||
status: overrides.status || "PENDING",
|
||||
request,
|
||||
summary: overrides.summary || {
|
||||
label:
|
||||
overrides.status === "COMPLETED"
|
||||
? "Completed"
|
||||
: overrides.status === "FAILED"
|
||||
? "Failed"
|
||||
: "Queued",
|
||||
progress: overrides.status === "COMPLETED" ? 100 : overrides.status === "FAILED" ? 100 : 0,
|
||||
retryable: overrides.status !== "FAILED",
|
||||
},
|
||||
result: overrides.result || {},
|
||||
error_code: overrides.error_code || null,
|
||||
error_message: overrides.error_message || null,
|
||||
requested_at: overrides.requested_at || toSqlDateTime(),
|
||||
started_at: startedAt,
|
||||
completed_at: completedAt,
|
||||
created_at: overrides.created_at || toSqlDateTime(),
|
||||
updated_at: overrides.updated_at || completedAt || startedAt || toSqlDateTime(),
|
||||
events: Array.isArray(overrides.events) ? overrides.events : [],
|
||||
};
|
||||
}
|
||||
|
||||
function buildEdgeGatewayOperationSummary(operations = []) {
|
||||
return operations.reduce(
|
||||
(summary, operation, index) => {
|
||||
const status = String(operation.status || "PENDING");
|
||||
if (status === "PENDING") summary.pending += 1;
|
||||
if (status === "IN_PROGRESS") summary.in_progress += 1;
|
||||
if (status === "COMPLETED") {
|
||||
summary.completed += 1;
|
||||
summary.latest_completed_at = summary.latest_completed_at || operation.completed_at || null;
|
||||
}
|
||||
if (status === "FAILED") {
|
||||
summary.failed += 1;
|
||||
summary.latest_failed_at = summary.latest_failed_at || operation.completed_at || null;
|
||||
}
|
||||
if (index === 0) {
|
||||
summary.latest_type = operation.type || null;
|
||||
summary.latest_status = operation.status || null;
|
||||
}
|
||||
return summary;
|
||||
},
|
||||
{
|
||||
total: operations.length,
|
||||
pending: 0,
|
||||
in_progress: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latest_completed_at: null,
|
||||
latest_failed_at: null,
|
||||
latest_type: null,
|
||||
latest_status: null,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function buildEdgeGatewayRuntimeFixture(gateway) {
|
||||
const relayHealth = (gateway.bindings || []).map((binding) => {
|
||||
const fallbackMode = binding.fallback_mode || "PREFER_LOCAL";
|
||||
@@ -662,6 +727,27 @@ function buildEdgeGatewayRuntimeFixture(gateway) {
|
||||
});
|
||||
|
||||
const cloudRelays = relayHealth.filter((relay) => relay.execution_path === "cloud");
|
||||
const operations = Array.isArray(gateway.operations) ? gateway.operations : [];
|
||||
const activeOperation = operations.find((operation) => ["PENDING", "IN_PROGRESS"].includes(String(operation.status))) || null;
|
||||
const versionDrift =
|
||||
gateway.installed_version && gateway.target_version && gateway.installed_version !== gateway.target_version;
|
||||
const credentialFreshnessState = gateway.metadata?.credentials_rotated_at ? "FRESH" : "UNKNOWN";
|
||||
const diagnostics = [];
|
||||
|
||||
if (gateway.status === "OFFLINE") {
|
||||
diagnostics.push({
|
||||
code: "EDGE_GATEWAY_OFFLINE",
|
||||
message: "Gateway heartbeat has expired and the gateway is offline.",
|
||||
recommended_action: "restart_agent",
|
||||
});
|
||||
}
|
||||
if (versionDrift) {
|
||||
diagnostics.push({
|
||||
code: "EDGE_GATEWAY_VERSION_DRIFT",
|
||||
message: "Installed gateway version differs from the target version.",
|
||||
recommended_action: "queue_update",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...gateway,
|
||||
@@ -697,6 +783,25 @@ function buildEdgeGatewayRuntimeFixture(gateway) {
|
||||
relay_health: gateway.relay_health || relayHealth,
|
||||
last_successful_command_at: gateway.last_successful_command_at || gateway.last_heartbeat_at,
|
||||
last_successful_discovery_at: gateway.last_successful_discovery_at || gateway.last_heartbeat_at,
|
||||
active_operation: gateway.active_operation || activeOperation,
|
||||
recent_operations_summary: gateway.recent_operations_summary || buildEdgeGatewayOperationSummary(operations),
|
||||
version_drift:
|
||||
gateway.version_drift || {
|
||||
installed_version: gateway.installed_version || null,
|
||||
target_version: gateway.target_version || null,
|
||||
release_channel: gateway.release_channel || "stable",
|
||||
is_drifted: Boolean(versionDrift),
|
||||
status: versionDrift ? "UPDATE_AVAILABLE" : "IN_SYNC",
|
||||
},
|
||||
credential_freshness:
|
||||
gateway.credential_freshness || {
|
||||
rotated_at: gateway.metadata?.credentials_rotated_at || null,
|
||||
age_days: gateway.metadata?.credentials_rotated_at ? 1 : null,
|
||||
state: credentialFreshnessState,
|
||||
},
|
||||
diagnostics: gateway.diagnostics || diagnostics,
|
||||
error_state:
|
||||
gateway.error_state || (diagnostics[0] ? { ...diagnostics[0] } : activeOperation?.error_code ? { code: activeOperation.error_code, message: activeOperation.error_message } : null),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -779,6 +884,18 @@ function createEdgeGatewayFixture(options = {}) {
|
||||
},
|
||||
],
|
||||
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" }],
|
||||
},
|
||||
{
|
||||
@@ -813,6 +930,7 @@ function createEdgeGatewayFixture(options = {}) {
|
||||
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) =>
|
||||
@@ -844,10 +962,27 @@ function settleEdgeGatewayWork(edgeGatewayFixture, gatewayId) {
|
||||
if (!gateway.inventory.some((device) => device.device_id === pendingDiscovery.device.device_id)) {
|
||||
gateway.inventory = [...gateway.inventory, pendingDiscovery.device];
|
||||
}
|
||||
gateway.recent_commands = (gateway.recent_commands || []).map((job) =>
|
||||
Number(job.id) === Number(pendingDiscovery.jobId)
|
||||
? { ...job, status: "COMPLETED", completed_at: gateway.last_successful_discovery_at }
|
||||
: job
|
||||
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];
|
||||
}
|
||||
@@ -862,65 +997,13 @@ function cloneJson(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function edgeGatewayOperationsFor(edgeGatewayFixture, gatewayId) {
|
||||
if (!Array.isArray(edgeGatewayFixture.operationsByGatewayId[gatewayId])) {
|
||||
edgeGatewayFixture.operationsByGatewayId[gatewayId] = [];
|
||||
}
|
||||
|
||||
return edgeGatewayFixture.operationsByGatewayId[gatewayId];
|
||||
}
|
||||
|
||||
function edgeGatewaySessionsFor(edgeGatewayFixture, gatewayId) {
|
||||
if (!Array.isArray(edgeGatewayFixture.shellSessionsByGatewayId[gatewayId])) {
|
||||
edgeGatewayFixture.shellSessionsByGatewayId[gatewayId] = [];
|
||||
}
|
||||
|
||||
return edgeGatewayFixture.shellSessionsByGatewayId[gatewayId];
|
||||
}
|
||||
|
||||
function publicEdgeGatewayOperation(operation) {
|
||||
if (!operation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { events, progress_step, ...publicFields } = operation;
|
||||
return {
|
||||
...publicFields,
|
||||
request: cloneJson(publicFields.request || {}),
|
||||
result: cloneJson(publicFields.result || {}),
|
||||
summary: cloneJson(publicFields.summary || {}),
|
||||
latest_event: Array.isArray(events) && events.length > 0 ? cloneJson(events[events.length - 1]) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function publicEdgeGatewaySession(session) {
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...session,
|
||||
metadata: cloneJson(session.metadata || {}),
|
||||
};
|
||||
}
|
||||
|
||||
function buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, includeDetail = false) {
|
||||
const inventory = cloneJson(gateway.inventory || []);
|
||||
const bindings = cloneJson(gateway.bindings || []);
|
||||
const operations = edgeGatewayOperationsFor(edgeGatewayFixture, gateway.id)
|
||||
.slice()
|
||||
.sort((left, right) => Number(right.id) - Number(left.id));
|
||||
const activeOperation =
|
||||
operations.find((operation) => ["PENDING", "RUNNING"].includes(String(operation.status || "").toUpperCase())) ||
|
||||
null;
|
||||
const recentShellSessions = edgeGatewaySessionsFor(edgeGatewayFixture, gateway.id)
|
||||
.slice()
|
||||
.sort((left, right) => Number(right.id) - Number(left.id))
|
||||
.map((session) => publicEdgeGatewaySession(session));
|
||||
const operations = cloneJson(gateway.operations || []);
|
||||
|
||||
return {
|
||||
...cloneJson(gateway),
|
||||
control_plane: { mode: "HTTP_POLLING" },
|
||||
readiness: {
|
||||
status: gateway.status,
|
||||
last_heartbeat_at: gateway.last_heartbeat_at,
|
||||
@@ -945,137 +1028,19 @@ function buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, includeDetail
|
||||
last_heartbeat_at: gateway.last_heartbeat_at,
|
||||
system_metrics: cloneJson(gateway.metadata?.system_metrics || {}),
|
||||
},
|
||||
active_operation: activeOperation ? publicEdgeGatewayOperation(activeOperation) : null,
|
||||
recent_operations: operations.map((operation) => publicEdgeGatewayOperation(operation)),
|
||||
operations,
|
||||
active_operation: cloneJson(gateway.active_operation || null),
|
||||
recent_operations_summary: cloneJson(gateway.recent_operations_summary || buildEdgeGatewayOperationSummary(operations)),
|
||||
version_drift: cloneJson(gateway.version_drift || null),
|
||||
credential_freshness: cloneJson(gateway.credential_freshness || null),
|
||||
diagnostics: cloneJson(gateway.diagnostics || []),
|
||||
error_state: cloneJson(gateway.error_state || null),
|
||||
inventory: includeDetail ? inventory : undefined,
|
||||
bindings: includeDetail ? bindings : undefined,
|
||||
recent_shell_sessions: includeDetail ? recentShellSessions : undefined,
|
||||
audit_logs: includeDetail ? cloneJson(gateway.audit_logs || []) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, stage, message, payload = {}, level = "INFO", counts = {}) {
|
||||
const event = {
|
||||
id: edgeGatewayFixture.nextOperationEventId++,
|
||||
operation_id: operation.id,
|
||||
gateway_id: operation.gateway_id,
|
||||
stage,
|
||||
level,
|
||||
message,
|
||||
counts,
|
||||
payload,
|
||||
created_at: toSqlDateTime(),
|
||||
};
|
||||
|
||||
operation.events.push(event);
|
||||
operation.summary = {
|
||||
latest_stage: stage,
|
||||
latest_message: message,
|
||||
counts,
|
||||
};
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
function advanceEdgeGatewayOperation(edgeGatewayFixture, gateway, operation) {
|
||||
if (!gateway || !operation || ["COMPLETED", "FAILED"].includes(String(operation.status || "").toUpperCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation.operation_type === "DISCOVERY") {
|
||||
gateway.discovery_status = "RUNNING";
|
||||
|
||||
if (operation.progress_step === 0) {
|
||||
addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, "STARTED", "Discovery started on the gateway.");
|
||||
addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, "PROBE", "Probing 10.1.0.0/24 for Shelly devices.");
|
||||
operation.progress_step = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation.progress_step === 1) {
|
||||
const discoveredDevice = {
|
||||
id: gateway.inventory.length + 1,
|
||||
device_id: "shelly-plus-new",
|
||||
local_ip: "10.1.0.33",
|
||||
model: "Shelly Plus 1PM",
|
||||
channel_count: 1,
|
||||
online: true,
|
||||
capabilities: { generation: 2 },
|
||||
};
|
||||
|
||||
if (!gateway.inventory.some((device) => device.device_id === discoveredDevice.device_id)) {
|
||||
gateway.inventory.push(discoveredDevice);
|
||||
}
|
||||
|
||||
addEdgeGatewayOperationEvent(
|
||||
edgeGatewayFixture,
|
||||
operation,
|
||||
"DEVICE_FOUND",
|
||||
"Found shelly-plus-new during discovery.",
|
||||
{ device: discoveredDevice },
|
||||
"INFO",
|
||||
{ devices: gateway.inventory.length }
|
||||
);
|
||||
operation.progress_step = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
operation.status = "COMPLETED";
|
||||
operation.completed_at = toSqlDateTime();
|
||||
operation.result = { inventory: cloneJson(gateway.inventory) };
|
||||
gateway.discovery_status = "READY";
|
||||
addEdgeGatewayOperationEvent(
|
||||
edgeGatewayFixture,
|
||||
operation,
|
||||
"COMPLETED",
|
||||
`Discovery completed with ${gateway.inventory.length} device(s).`,
|
||||
{},
|
||||
"INFO",
|
||||
{ devices: gateway.inventory.length }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation.operation_type === "UPDATE") {
|
||||
if (operation.progress_step === 0) {
|
||||
addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, "STARTED", "Update started on the gateway.");
|
||||
operation.progress_step = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
gateway.installed_version = operation.request_json?.target_version || gateway.installed_version;
|
||||
gateway.target_version = operation.request_json?.target_version || gateway.target_version;
|
||||
operation.status = "COMPLETED";
|
||||
operation.completed_at = toSqlDateTime();
|
||||
operation.result = { installed_version: gateway.installed_version };
|
||||
addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, "COMPLETED", `Gateway updated to ${gateway.installed_version}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation.operation_type === "UNINSTALL") {
|
||||
if (operation.progress_step === 0) {
|
||||
addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, "STARTED", "Uninstall started on the gateway.");
|
||||
operation.progress_step = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
gateway.status = "OFFLINE";
|
||||
operation.status = "COMPLETED";
|
||||
operation.completed_at = toSqlDateTime();
|
||||
operation.result = { service_name: "truckwash-edge-agent.service" };
|
||||
addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, "COMPLETED", "Gateway uninstall completed.");
|
||||
return;
|
||||
}
|
||||
|
||||
operation.status = "COMPLETED";
|
||||
operation.completed_at = toSqlDateTime();
|
||||
operation.result = {
|
||||
online: true,
|
||||
on: Boolean(operation.request_json?.on),
|
||||
};
|
||||
addEdgeGatewayOperationEvent(edgeGatewayFixture, operation, "COMPLETED", `${operation.operation_type} completed.`);
|
||||
}
|
||||
|
||||
function processPendingEdgeGatewayClaims(edgeGatewayFixture) {
|
||||
edgeGatewayFixture.pendingClaims = (edgeGatewayFixture.pendingClaims || []).filter((claim) => {
|
||||
claim.pollsRemaining -= 1;
|
||||
@@ -1110,6 +1075,7 @@ function processPendingEdgeGatewayClaims(edgeGatewayFixture) {
|
||||
inventory: [],
|
||||
bindings: [],
|
||||
recent_commands: [],
|
||||
operations: [],
|
||||
audit_logs: [
|
||||
{
|
||||
id: Date.now(),
|
||||
@@ -1165,6 +1131,18 @@ function createHttpEdgeGatewayFixture(options = {}) {
|
||||
{ id: 2, relay_id: "M-7-LEGACY", device_id: "shelly-missing-legacy", local_ip: "10.1.0.99", channel: 1, fallback_mode: "CLOUD_ONLY" },
|
||||
],
|
||||
recent_commands: [],
|
||||
operations: [
|
||||
createFixtureOperation("DISCOVERY", {}, {
|
||||
id: 8801,
|
||||
status: "COMPLETED",
|
||||
started_at: "2026-04-08 08:14:20",
|
||||
completed_at: "2026-04-08 08:14:38",
|
||||
events: [
|
||||
{ id: 1, level: "INFO", code: "OPERATION_STARTED", message: "Gateway started processing the operation", created_at: "2026-04-08 08:14:20" },
|
||||
{ id: 2, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: "2026-04-08 08:14:38" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
audit_logs: [{ id: 501, created_at: "2026-04-08 08:16:00", action: "GATEWAY_CLAIMED", actor_type: "USER" }],
|
||||
},
|
||||
{
|
||||
@@ -1193,6 +1171,7 @@ function createHttpEdgeGatewayFixture(options = {}) {
|
||||
inventory: [],
|
||||
bindings: [],
|
||||
recent_commands: [],
|
||||
operations: [],
|
||||
audit_logs: [{ id: 502, created_at: "2026-04-07 21:05:00", action: "HEARTBEAT_TIMEOUT", actor_type: "SYSTEM" }],
|
||||
},
|
||||
]
|
||||
@@ -1201,15 +1180,11 @@ function createHttpEdgeGatewayFixture(options = {}) {
|
||||
|
||||
return {
|
||||
nextGatewayId: Math.max(703, ...baseGateways.map((gateway) => Number(gateway.id) + 1)),
|
||||
nextOperationId: 10001,
|
||||
nextOperationEventId: 20001,
|
||||
nextShellSessionId: 30001,
|
||||
nextShellEventId: 40001,
|
||||
nextOperationId: 9901,
|
||||
nextOperationEventId: 19901,
|
||||
claimPollsRemaining: Number(options.claimPollsRemaining || 2),
|
||||
pendingClaims: [],
|
||||
pendingDiscoveryByGatewayId: {},
|
||||
operationsByGatewayId: {},
|
||||
shellSessionsByGatewayId: {},
|
||||
gateways: baseGateways,
|
||||
};
|
||||
}
|
||||
@@ -2434,6 +2409,16 @@ async function handleEdgeGatewayRoute({
|
||||
|
||||
const findGateway = (gatewayId) =>
|
||||
edgeGatewayFixture.gateways.find((entry) => Number(entry.id) === Number(gatewayId)) || null;
|
||||
const gatewayResponse = (gateway, includeDetail = true) =>
|
||||
gateway ? json({ data: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, includeDetail) }) : json({ message: "Gateway not found" }, 404);
|
||||
const createOperation = (gateway, type, request = {}, overrides = {}) => {
|
||||
const operation = createFixtureOperation(type, request, {
|
||||
id: edgeGatewayFixture.nextOperationId++,
|
||||
...overrides,
|
||||
});
|
||||
gateway.operations = [operation, ...(gateway.operations || [])];
|
||||
return operation;
|
||||
};
|
||||
|
||||
if (pathname.endsWith("/edge-gateways") && method === "GET") {
|
||||
processPendingEdgeGatewayClaims(edgeGatewayFixture);
|
||||
@@ -2464,25 +2449,174 @@ async function handleEdgeGatewayRoute({
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\/edge-gateways\/\d+\/operations$/.test(pathname) && method === "GET") {
|
||||
const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/operations$/);
|
||||
const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId);
|
||||
await route.fulfill(json({ data: cloneJson(gateway?.operations || []) }));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\/edge-gateways\/\d+\/operations\/\d+\/events$/.test(pathname) && method === "GET") {
|
||||
const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/operations\/\d+\/events$/);
|
||||
const operationId = extractMatchId(/\/operations\/(\d+)\/events$/);
|
||||
const gateway = settleEdgeGatewayWork(edgeGatewayFixture, gatewayId);
|
||||
const operation = (gateway?.operations || []).find((entry) => Number(entry.id) === Number(operationId)) || null;
|
||||
await route.fulfill(json({ data: cloneJson(operation?.events || []) }));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\/edge-gateways\/\d+\/operations$/.test(pathname) && method === "POST") {
|
||||
const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/operations$/);
|
||||
const gateway = findGateway(gatewayId);
|
||||
const body = request.postDataJSON?.() || {};
|
||||
if (!gateway) {
|
||||
await route.fulfill(json({ message: "Gateway not found" }, 404));
|
||||
return true;
|
||||
}
|
||||
|
||||
const operationType = String(body.type || "").toUpperCase();
|
||||
const operationRequest = body.request || {};
|
||||
const now = toSqlDateTime();
|
||||
let operation = null;
|
||||
|
||||
if (operationType === "DISCOVERY") {
|
||||
gateway.discovery_status = "PENDING";
|
||||
operation = createOperation(gateway, "DISCOVERY", operationRequest, {
|
||||
status: "IN_PROGRESS",
|
||||
started_at: now,
|
||||
updated_at: now,
|
||||
summary: { label: "Gateway is processing the operation", progress: 20, retryable: true },
|
||||
events: [
|
||||
{ id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_QUEUED", message: "Operation queued for gateway execution", created_at: now },
|
||||
{ id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_STARTED", message: "Gateway started processing the operation", created_at: now },
|
||||
],
|
||||
});
|
||||
edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId] = {
|
||||
operationId: operation.id,
|
||||
fetchCount: 0,
|
||||
device: {
|
||||
id: (gateway.inventory?.length || 0) + 1,
|
||||
device_id: "shelly-plus-new",
|
||||
local_ip: "10.1.0.33",
|
||||
model: "Shelly Plus 1PM",
|
||||
channel_count: 1,
|
||||
online: true,
|
||||
capabilities: { generation: 2 },
|
||||
},
|
||||
};
|
||||
} else if (operationType === "UPDATE") {
|
||||
gateway.target_version = String(operationRequest.target_version || gateway.target_version || "");
|
||||
gateway.installed_version = gateway.target_version;
|
||||
operation = createOperation(gateway, "UPDATE", operationRequest, {
|
||||
status: "COMPLETED",
|
||||
started_at: now,
|
||||
completed_at: now,
|
||||
updated_at: now,
|
||||
summary: { label: "Completed", progress: 100, retryable: true },
|
||||
result: { installed_version: gateway.installed_version, target_version: gateway.target_version, restart_required: true },
|
||||
events: [
|
||||
{ id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_QUEUED", message: "Operation queued for gateway execution", created_at: now },
|
||||
{ id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: now },
|
||||
],
|
||||
});
|
||||
} else if (operationType === "UNINSTALL") {
|
||||
gateway.status = "OFFLINE";
|
||||
gateway.metadata = {
|
||||
...(gateway.metadata || {}),
|
||||
uninstalled_at: now,
|
||||
};
|
||||
operation = createOperation(gateway, "UNINSTALL", operationRequest, {
|
||||
status: "COMPLETED",
|
||||
started_at: now,
|
||||
completed_at: now,
|
||||
updated_at: now,
|
||||
summary: { label: "Completed", progress: 100, retryable: false },
|
||||
result: { uninstalled: true, manual_cleanup_required: true },
|
||||
events: [
|
||||
{ id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_QUEUED", message: "Operation queued for gateway execution", created_at: now },
|
||||
{ id: edgeGatewayFixture.nextOperationEventId++, level: "INFO", code: "OPERATION_COMPLETED", message: "Operation completed successfully", created_at: now },
|
||||
],
|
||||
});
|
||||
} else {
|
||||
await route.fulfill(json({ data: { message: "Unsupported gateway operation type", error_code: "EDGE_GATEWAY_VALIDATION_FAILED" } }, 422));
|
||||
return true;
|
||||
}
|
||||
|
||||
gateway.audit_logs = [
|
||||
{ id: Date.now(), created_at: now, action: "GATEWAY_OPERATION_QUEUED", actor_type: "USER" },
|
||||
...(gateway.audit_logs || []),
|
||||
];
|
||||
Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway));
|
||||
await route.fulfill(json({ data: { operation: cloneJson(operation), gateway: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true) } }, 201));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\/edge-gateways\/\d+\/rotate-credentials$/.test(pathname) && method === "POST") {
|
||||
const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/rotate-credentials$/);
|
||||
const gateway = findGateway(gatewayId);
|
||||
if (!gateway) {
|
||||
await route.fulfill(json({ message: "Gateway not found" }, 404));
|
||||
return true;
|
||||
}
|
||||
|
||||
const rotatedAt = toSqlDateTime();
|
||||
gateway.metadata = {
|
||||
...(gateway.metadata || {}),
|
||||
credentials_rotated_at: rotatedAt,
|
||||
};
|
||||
gateway.audit_logs = [
|
||||
{ id: Date.now(), created_at: rotatedAt, action: "GATEWAY_CREDENTIALS_ROTATED", actor_type: "USER" },
|
||||
...(gateway.audit_logs || []),
|
||||
];
|
||||
Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway));
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
gateway_id: gatewayId,
|
||||
rotated_at: rotatedAt,
|
||||
agent_token: "rotated-edge-agent-token",
|
||||
config_json: JSON.stringify(
|
||||
{
|
||||
apiUrl: "https://api.truckwash.test",
|
||||
gatewayId,
|
||||
agentToken: "rotated-edge-agent-token",
|
||||
installDir: "/opt/truckwash-edge-agent",
|
||||
serviceName: "truckwash-edge-agent.service",
|
||||
heartbeatIntervalSeconds: 15,
|
||||
operationPollTimeoutSeconds: 20,
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
restart_instructions: [
|
||||
"sudo systemctl restart truckwash-edge-agent.service",
|
||||
"sudo systemctl status truckwash-edge-agent.service --no-pager",
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\/edge-gateways\/\d+\/discovery$/.test(pathname) && method === "POST") {
|
||||
const gatewayId = extractMatchId(/\/edge-gateways\/(\d+)\/discovery$/);
|
||||
const gateway = findGateway(gatewayId);
|
||||
|
||||
if (gateway) {
|
||||
const commandId = Date.now();
|
||||
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";
|
||||
gateway.recent_commands = [
|
||||
{
|
||||
id: commandId,
|
||||
command_type: "DISCOVER_SHELLY",
|
||||
status: "PENDING",
|
||||
created_at: toSqlDateTime(),
|
||||
requested_at: toSqlDateTime(),
|
||||
},
|
||||
...(gateway.recent_commands || []),
|
||||
];
|
||||
edgeGatewayFixture.pendingDiscoveryByGatewayId[gatewayId] = {
|
||||
jobId: commandId,
|
||||
operationId: operation.id,
|
||||
fetchCount: 0,
|
||||
device: {
|
||||
id: (gateway.inventory?.length || 0) + 1,
|
||||
@@ -2497,11 +2631,7 @@ async function handleEdgeGatewayRoute({
|
||||
Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway));
|
||||
}
|
||||
|
||||
await route.fulfill(
|
||||
gateway
|
||||
? json({ data: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true) })
|
||||
: json({ message: "Gateway not found" }, 404)
|
||||
);
|
||||
await route.fulfill(gatewayResponse(gateway, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2560,10 +2690,12 @@ async function handleEdgeGatewayRoute({
|
||||
];
|
||||
|
||||
Object.assign(gateway, buildEdgeGatewayRuntimeFixture(gateway));
|
||||
edgeGatewayFixture.gateways = edgeGatewayFixture.gateways.map((item) => (Number(item.id) === Number(gateway.id) ? Object.assign(item, gateway) : item));
|
||||
edgeGatewayFixture.gateways = edgeGatewayFixture.gateways.map((item) =>
|
||||
Number(item.id) === Number(gateway.id) ? Object.assign(item, gateway) : item
|
||||
);
|
||||
}
|
||||
|
||||
await route.fulfill(gateway ? json({ data: buildHttpEdgeGatewayGateway(edgeGatewayFixture, gateway, true) }) : json({ message: "Gateway not found" }, 404));
|
||||
await route.fulfill(gatewayResponse(gateway, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2583,6 +2715,11 @@ async function handleEdgeGatewayRoute({
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\/edge-gateways\/\d+\/(?:update-jobs|uninstall)(?:\/.*)?$/.test(pathname) || /\/edge-gateways\/\d+\/shell-sessions(?:\/.*)?$/.test(pathname)) {
|
||||
await route.fulfill(json({ message: "Route not found" }, 404));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\/edge-gateways\/\d+$/.test(pathname) && method === "DELETE") {
|
||||
const gatewayId = Number(pathname.split("/").pop());
|
||||
const gateway = findGateway(gatewayId);
|
||||
@@ -2761,6 +2898,11 @@ export async function mockApi(page, options = {}) {
|
||||
display_name: "E2E User",
|
||||
permissions: options.permissions || ["user"],
|
||||
economic_customer: [],
|
||||
runtime_config: {
|
||||
economic: {
|
||||
transaction_draft_customer_number: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const sessionData = {
|
||||
|
||||
Reference in New Issue
Block a user