165 lines
5.0 KiB
TypeScript
165 lines
5.0 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
|
|
const emptyRuntimeSnapshot = {
|
|
availability: "not_configured",
|
|
source: "none",
|
|
scenario_availability: "not_configured",
|
|
artifact_availability: "not_configured",
|
|
schedule_availability: "not_configured",
|
|
work_order_availability: "not_configured",
|
|
alert_availability: "not_configured",
|
|
generated_at: "2026-09-17T02:30:00Z",
|
|
conditions: [],
|
|
alerts: [],
|
|
scenarios: [],
|
|
latest_analysis: null,
|
|
work_orders: []
|
|
};
|
|
|
|
export const TEST_PROJECT_ID = "324ff7ee-c2d4-5184-a84b-48fb14a2933e";
|
|
|
|
export async function mockScadaApi(
|
|
page: Page,
|
|
devices: unknown[] = [],
|
|
runtimeSnapshot: unknown = emptyRuntimeSnapshot,
|
|
readings: unknown[] = [],
|
|
projectPresentation?: { metadata: unknown; accessContext: unknown },
|
|
analysisRuns: Record<string, unknown> = {},
|
|
analysisHistory: unknown[] = []
|
|
) {
|
|
const corsHeaders = { "Access-Control-Allow-Origin": "*" };
|
|
await page.route("https://tjwater-api.invalid/**", async (route) => {
|
|
if (route.request().method() === "OPTIONS") {
|
|
await route.fulfill({
|
|
status: 204,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Access-Control-Allow-Headers": "Authorization, Content-Type, Last-Event-ID, X-Project-Id",
|
|
"Access-Control-Allow-Methods": "DELETE, GET, PATCH, POST, OPTIONS"
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
const url = new URL(route.request().url());
|
|
if (url.pathname === "/api/v1/projects") {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify({
|
|
items: [{ project_id: TEST_PROJECT_ID, gs_workspace: "tjwater_next", status: "active" }],
|
|
total: 1,
|
|
limit: 1000,
|
|
offset: 0
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/scada-devices") {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify({ items: devices, total: devices.length, limit: 1000, offset: 0 })
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/projects/current/metadata" && projectPresentation) {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify(projectPresentation.metadata)
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/access-context" && projectPresentation) {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify(projectPresentation.accessContext)
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/timeseries/scada-readings") {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify(readings)
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/workbench/runtime") {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify(runtimeSnapshot)
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/workbench/sync-snapshot") {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify({
|
|
schema: "workbench-sync-snapshot@1",
|
|
event_cursor: 0,
|
|
runtime: runtimeSnapshot,
|
|
tasks: [],
|
|
notifications: [],
|
|
generated_at: "2026-09-17T02:30:00Z"
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/workbench/events") {
|
|
await route.fulfill({
|
|
contentType: "text/event-stream",
|
|
headers: { ...corsHeaders, "Cache-Control": "no-cache" },
|
|
body: ": test keepalive\n\n"
|
|
});
|
|
return;
|
|
}
|
|
if (url.pathname === "/api/v1/workbench/analysis-runs") {
|
|
const limit = Number(url.searchParams.get("limit") ?? 10);
|
|
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
const items = analysisHistory.slice(offset, offset + limit);
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify({
|
|
items,
|
|
limit,
|
|
offset,
|
|
next_offset: offset + limit < analysisHistory.length ? offset + limit : null
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
const analysisRunMatch = url.pathname.match(/^\/api\/v1\/workbench\/analysis-runs\/([^/]+)$/);
|
|
if (analysisRunMatch) {
|
|
const runId = decodeURIComponent(analysisRunMatch[1]);
|
|
const analysisRun = analysisRuns[runId];
|
|
if (analysisRun) {
|
|
await route.fulfill({
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify(analysisRun)
|
|
});
|
|
} else {
|
|
await route.fulfill({
|
|
status: 404,
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: JSON.stringify({ detail: "Analysis run not found" })
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
await route.fulfill({
|
|
status: 404,
|
|
contentType: "application/json",
|
|
headers: corsHeaders,
|
|
body: "{}"
|
|
});
|
|
});
|
|
}
|