import assert from "node:assert/strict";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { createServer } from "node:http";
import { connect } from "node:net";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { gzipSync } from "node:zlib";

import { createCloudWebServer, isClientDisconnectError, serveCloudWeb } from "./cloud-web-runtime.mjs";

test("cloud web classifies Bun client disconnect ECONNRESET as non-fatal", () => {
  const error = new Error("aborted");
  error.code = "ECONNRESET";
  assert.equal(isClientDisconnectError(error), true);
  assert.equal(isClientDisconnectError(new Error("database migration failed")), false);
});

test("repo feature schema owns one unique key per product feature", async () => {
  const schema = JSON.parse(await readFile(new URL("../../config/feature-config.schema.json", import.meta.url), "utf8"));
  assert.equal(schema.$schema, "https://json-schema.org/draft/2020-12/schema");
  assert.deepEqual(Object.keys(schema.properties), ["tracePanel", "rawEvents", "debugReplay", "views"]);
  const features = Object.values(schema.properties).map((property) => property["x-unidesk-feature"]);
  assert.equal(features.every((feature) => typeof feature === "string" && feature.length > 0), true);
  assert.equal(new Set(features).size, features.length);
  const encoded = JSON.stringify(schema);
  for (const architectureEnv of [
    "HWLAB_KAFKA_DIRECT_PUBLISH_ENABLED",
    "HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED",
    "HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED",
    "HWLAB_KAFKA_TRANSACTIONAL_PROJECTOR_ENABLED",
    "HWLAB_KAFKA_PROJECTION_OUTBOX_RELAY_ENABLED",
    "HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED"
  ]) assert.equal(encoded.includes(architectureEnv), false);
});

test("serveCloudWeb starts without obsolete architecture capabilities", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间",
    HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true",
    HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: undefined,
    HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false",
    HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576"
  });
  const port = await unusedPort();
  let server = null;
  try {
    server = await serveCloudWeb({
      port,
      serviceId: "hwlab-cloud-web",
      healthPayload: () => ({ status: "ok" }),
      sendJson(response, statusCode, body) {
        response.writeHead(statusCode, { "content-type": "application/json" });
        response.end(JSON.stringify(body));
      }
    });
    assert.equal(await canConnect(port), true);
  } finally {
    if (server) await close(server);
    restoreEnv();
  }
});

test("cloud web auth requests are proxied once", async () => {
  let upstreamRequests = 0;
  const upstream = createServer((request, response) => {
    upstreamRequests += 1;
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: true, path: request.url }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/auth/session`, {
      headers: { cookie: "hwlab_session=token" }
    });
    assert.equal(response.status, 200);
    assert.deepEqual(await response.json(), { authenticated: true, path: "/auth/session" });
    assert.equal(upstreamRequests, 1);
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web proxies Workbench writes with trace context", async () => {
  const upstreamRequests = [];
  const upstream = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    upstreamRequests.push({
      method: request.method,
      url: request.url,
      cookie: request.headers.cookie,
      traceparent: request.headers.traceparent,
      otelTraceId: request.headers["x-hwlab-otel-trace-id"],
      body
    });
    response.writeHead(404, {
      "content-type": "application/json",
      "x-hwlab-otel-trace-id": "11111111111111111111111111111111"
    });
    response.end(JSON.stringify({ error: "not_found" }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const body = JSON.stringify({ projectId: "project_d601", taskRef: "task_2136" });
    for (const route of ["/v1/workbench/launches", "/v1/workbench/commands"]) {
      const response = await fetch(`${serverUrl(cloudWeb)}${route}`, {
        method: "POST",
        headers: {
          "content-type": "application/json",
          cookie: "hwlab_session=session-launch",
          traceparent: "00-11111111111111111111111111111111-2222222222222222-01",
          "x-hwlab-otel-trace-id": "11111111111111111111111111111111"
        },
        body
      });
      assert.equal(response.status, 404);
      assert.equal(response.headers.get("x-hwlab-otel-trace-id"), "11111111111111111111111111111111");
      assert.deepEqual(await response.json(), { error: "not_found" });
    }
    assert.deepEqual(upstreamRequests, ["/v1/workbench/launches", "/v1/workbench/commands"].map((url) => ({
      method: "POST",
      url,
      cookie: "hwlab_session=session-launch",
      traceparent: "00-11111111111111111111111111111111-2222222222222222-01",
      otelTraceId: "11111111111111111111111111111111",
      body
    })));
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web proxy reports a unified upstream unavailable error", async () => {
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: "http://127.0.0.1:1",
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/v1/web-performance/summary?window=15m`, {
      headers: { accept: "application/json", "x-trace-id": "trc_proxy_upstream_unavailable" }
    });
    assert.equal(response.status, 502);
    const payload = await response.json();
    assert.equal(payload.status, "failed");
    assert.equal(payload.error.code, "upstream_unavailable");
    assert.equal(payload.error.layer, "proxy");
    assert.equal(payload.error.retryable, true);
    assert.equal(payload.error.userMessage, "暂时无法连接上游。");
    assert.equal(payload.error.message, "暂时无法连接上游。");
    assert.equal(payload.error.blocker.code, "upstream_unavailable");
    assert.equal(payload.error.blocker.summary, "暂时无法连接上游。");
    assert.equal(payload.reason, "暂时无法连接上游。");
    assert.doesNotMatch(JSON.stringify(payload), /Code Agent|输入已保留|稍后重试/u);
  } finally {
    await close(cloudWeb);
  }
});

test("cloud web proxies Admin Access write routes", async () => {
  const upstreamRequests = [];
  const upstream = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    upstreamRequests.push({
      method: request.method,
      url: request.url,
      authorization: request.headers.authorization,
      cookie: request.headers.cookie,
      body
    });
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: true, path: request.url }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const checkBody = JSON.stringify({ user: "usr_alice", relation: "can_use", object: "tool:hwpod" });
    const checkResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/access/check`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin",
        cookie: "hwlab_session=session-a"
      },
      body: checkBody
    });
    assert.equal(checkResponse.status, 200);
    assert.deepEqual(await checkResponse.json(), { ok: true, path: "/v1/admin/access/check" });

    const grantResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/access/users/usr_alice/tools/hwpod/can-use`, {
      method: "PUT",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin"
      },
      body: "{}"
    });
    assert.equal(grantResponse.status, 200);

    assert.equal(upstreamRequests.length, 2);
    assert.deepEqual(upstreamRequests[0], {
      method: "POST",
      url: "/v1/admin/access/check",
      authorization: "Bearer hwl_live_admin",
      cookie: "hwlab_session=session-a",
      body: checkBody
    });
    assert.deepEqual(upstreamRequests[1], {
      method: "PUT",
      url: "/v1/admin/access/users/usr_alice/tools/hwpod/can-use",
      authorization: "Bearer hwl_live_admin",
      cookie: undefined,
      body: "{}"
    });
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web proxies provider profile management write routes", async () => {
  const upstreamRequests = [];
  const upstream = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    upstreamRequests.push({
      method: request.method,
      url: request.url,
      authorization: request.headers.authorization,
      body
    });
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: true, path: request.url }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const credentialBody = JSON.stringify({ apiKey: "sk-test-redacted" });
    const credentialResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/credential`, {
      method: "PUT",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin"
      },
      body: credentialBody
    });
    assert.equal(credentialResponse.status, 200);

    const validateResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/validate`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin"
      },
      body: "{}"
    });
    assert.equal(validateResponse.status, 200);

    const testBody = JSON.stringify({ model: "gpt-5.4-mini", prompt: "只回复 OK" });
    const testResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/test`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin"
      },
      body: testBody
    });
    assert.equal(testResponse.status, 200);

    assert.equal(upstreamRequests.length, 3);
    assert.deepEqual(upstreamRequests[0], {
      method: "PUT",
      url: "/v1/admin/provider-profiles/deepseek/credential",
      authorization: "Bearer hwl_live_admin",
      body: credentialBody
    });
    assert.deepEqual(upstreamRequests[1], {
      method: "POST",
      url: "/v1/admin/provider-profiles/deepseek/validate",
      authorization: "Bearer hwl_live_admin",
      body: "{}"
    });
    assert.deepEqual(upstreamRequests[2], {
      method: "POST",
      url: "/v1/admin/provider-profiles/deepseek/test",
      authorization: "Bearer hwl_live_admin",
      body: testBody
    });
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web forwards hwpod node-ops plans to cloud-api", async () => {
  const upstreamRequests = [];
  const upstream = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    upstreamRequests.push({
      method: request.method,
      url: request.url,
      authorization: request.headers.authorization,
      body
    });
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: false, status: "blocked", path: request.url, blocker: { code: "hwpod_node_unavailable" } }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const plan = {
      contractVersion: "hwpod-node-ops-v1",
      planId: "hwpod_plan_proxy_test",
      hwpodId: "hwpod-test",
      nodeId: "node-test",
      ops: [{ opId: "op_01", op: "node.health", args: {} }]
    };
    const response = await fetch(`${serverUrl(cloudWeb)}/v1/hwpod-node-ops`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_operator"
      },
      body: JSON.stringify(plan)
    });
    assert.equal(response.status, 200);
    assert.deepEqual(await response.json(), { ok: false, status: "blocked", path: "/v1/hwpod-node-ops", blocker: { code: "hwpod_node_unavailable" } });
    assert.equal(upstreamRequests.length, 1);
    assert.deepEqual(upstreamRequests[0], {
      method: "POST",
      url: "/v1/hwpod-node-ops",
      authorization: "Bearer hwl_live_operator",
      body: JSON.stringify(plan)
    });
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web remains healthy after a client disconnects during static response", async () => {
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-"));
  await writeFile(path.join(root, "large.txt"), "x".repeat(512 * 1024), "utf8");
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [root],
    cloudApiBaseUrl: "http://127.0.0.1:1",
    healthPayload: () => ({ status: "ok", process: { uptimeSeconds: 1 } }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    await disconnectDuringResponse(serverUrl(cloudWeb), "/large.txt");
    const response = await fetch(`${serverUrl(cloudWeb)}/health/live`);
    assert.equal(response.status, 200);
    assert.equal((await response.json()).status, "ok");
  } finally {
    await close(cloudWeb);
    await rm(root, { recursive: true, force: true });
  }
});

test("cloud web serves client deep links through the Vue shell", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间",
    HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true",
    HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "true",
    HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false",
    HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576"
  });
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-"));
  await writeFile(path.join(root, "index.html"), "<div id=\"root\"></div>\n", "utf8");
  await writeFile(path.join(root, "asset.txt"), "asset body\n", "utf8");
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [root],
    cloudApiBaseUrl: "http://127.0.0.1:1",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    for (const route of ["/access", "/register", "/api-keys", "/admin/users"]) {
      const response = await fetch(`${serverUrl(cloudWeb)}${route}`, {
        headers: { accept: "text/html" }
      });
      assert.equal(response.status, 200, route);
      assert.equal(response.headers.get("content-type"), "text/html; charset=utf-8", route);
      const html = await response.text();
      assert.match(html, /<div id="root"><\/div>/u, route);
      assert.match(html, /HWLAB_CLOUD_WEB_CONFIG/u, route);
      assert.doesNotMatch(html, /realtimeFeatures|liveKafkaSse|kafkaRefreshReplay|projectionRealtime/u, route);
      assert.match(html, /"debugCapabilities":\{"isolatedKafka":true,"rawHwlabEventWindow":\{"enabled":true,"maxEntries":200,"maxRetainedBytes":1048576\}\}/u, route);
    }

    const assetResponse = await fetch(`${serverUrl(cloudWeb)}/asset.txt`, {
      headers: { accept: "text/plain" }
    });
    assert.equal(assetResponse.status, 200);
    assert.equal(await assetResponse.text(), "asset body\n");

    const missingAssetResponse = await fetch(`${serverUrl(cloudWeb)}/missing.css`, {
      headers: { accept: "text/css,*/*" }
    });
    assert.equal(missingAssetResponse.status, 404);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await rm(root, { recursive: true, force: true });
  }
});

test("cloud web injects trace explorer runtime config from env", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间",
    HWLAB_WORKBENCH_TRACE_EXPLORER_URL_TEMPLATE: "/v1/workbench/traces/{trace_id}/events",
    HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true",
    HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "false",
    HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false",
    HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576"
  });
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-"));
  await writeFile(path.join(root, "index.html"), "<html><head></head><body><div id=\"root\"></div></body></html>\n", "utf8");
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [root],
    cloudApiBaseUrl: "http://127.0.0.1:1",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/workbench`, { headers: { accept: "text/html" } });
    assert.equal(response.status, 200);
    const html = await response.text();
    assert.match(html, /traceExplorerUrlTemplate":"\/v1\/workbench\/traces\/\{trace_id\}\/events/u);
    assert.doesNotMatch(html, /api key|secret|password/iu);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await rm(root, { recursive: true, force: true });
  }
});

test("cloud web OpenCode proxy requires an authenticated HWLAB session", async () => {
  const authRequests = [];
  let opencodeRequests = 0;
  const cloudApi = createServer((request, response) => {
    authRequests.push({
      traceparent: request.headers.traceparent,
      otelTraceId: request.headers["x-hwlab-otel-trace-id"],
      requestId: request.headers["x-request-id"],
      sourceServiceId: request.headers["x-source-service-id"]
    });
    request.resume();
    response.writeHead(401, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: false }));
  });
  const opencode = createServer((request, response) => {
    opencodeRequests += 1;
    request.resume();
    response.writeHead(200, { "content-type": "text/plain" });
    response.end("opencode should not be reached");
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "127.0.0.1",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/`, { headers: { accept: "text/html" } });
    assert.equal(response.status, 401);
    const body = await response.json();
    assert.equal(body.error, "opencode_auth_required");
    assert.match(response.headers.get("traceparent") ?? "", /^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/u);
    assert.match(response.headers.get("x-hwlab-otel-trace-id") ?? "", /^[0-9a-f]{32}$/u);
    assert.match(response.headers.get("x-request-id") ?? "", /^req_[0-9a-f]{32}$/u);
    assert.equal(body.diagnostic.traceId, response.headers.get("x-hwlab-otel-trace-id"));
    assert.equal(body.diagnostic.requestId, response.headers.get("x-request-id"));
    assert.deepEqual(authRequests, [{
      traceparent: response.headers.get("traceparent"),
      otelTraceId: response.headers.get("x-hwlab-otel-trace-id"),
      requestId: response.headers.get("x-request-id"),
      sourceServiceId: "hwlab-cloud-web"
    }]);
    assert.equal(opencodeRequests, 0);
  } finally {
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
  }
});

test("cloud web OpenCode frame-url endpoint mints traced tickets", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间",
    HWLAB_CLOUD_WEB_OPENCODE_URL: "https://opencode.example.test",
    HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true",
    HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "false",
    HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false",
    HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576"
  });
  const cloudApiRequests = [];
  const opencodeRequests = [];
  const cloudApi = createServer((request, response) => {
    cloudApiRequests.push({
      cookie: request.headers.cookie,
      traceparent: request.headers.traceparent,
      otelTraceId: request.headers["x-hwlab-otel-trace-id"],
      requestId: request.headers["x-request-id"],
      sourceServiceId: request.headers["x-source-service-id"]
    });
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
  });
  const opencode = createServer((request, response) => {
    opencodeRequests.push({ url: request.url, cookie: request.headers.cookie });
    request.resume();
    response.writeHead(200, { "content-type": "text/plain" });
    response.end("opencode ok\n");
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/opencode/frame-url`, {
      headers: { accept: "application/json", cookie: "hwlab_session=session-a", "x-request-id": "req_opencode_frame_url" }
    });
    assert.equal(response.status, 200);
    assert.equal(response.headers.get("x-request-id"), "req_opencode_frame_url");
    assert.match(response.headers.get("traceparent") ?? "", /^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/u);
    const body = await response.json();
    const frameUrl = new URL(body.url);
    const ticket = frameUrl.searchParams.get("hwlab_opencode_ticket");
    assert.ok(ticket, "dynamic endpoint should append an OpenCode ticket");
    assert.equal(body.diagnostic.traceId, response.headers.get("x-hwlab-otel-trace-id"));
    assert.deepEqual(cloudApiRequests, [{
      cookie: "hwlab_session=session-a",
      traceparent: response.headers.get("traceparent"),
      otelTraceId: response.headers.get("x-hwlab-otel-trace-id"),
      requestId: "req_opencode_frame_url",
      sourceServiceId: "hwlab-cloud-web"
    }]);

    const proxied = await fetch(`${serverUrl(cloudWeb)}/_opencode/?hwlab_opencode_ticket=${encodeURIComponent(ticket)}`, {
      headers: { accept: "text/plain" }
    });
    assert.equal(proxied.status, 200);
    assert.equal(await proxied.text(), "opencode ok\n");
    assert.deepEqual(opencodeRequests, [{ url: "/", cookie: undefined }]);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
  }
});

test("cloud web OpenCode proxy bootstraps the default project for iframe HTML", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_OPENCODE_URL: "https://opencode.example.test"
  });
  const cloudApi = createServer((request, response) => {
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
  });
  const opencodeRequests = [];
  const opencodeHtml = "<html><head></head><body><div id=\"root\"></div></body></html>\n";
  const opencodeHtmlGzip = gzipSync(Buffer.from(opencodeHtml, "utf8"));
  const opencode = createServer((request, response) => {
    opencodeRequests.push({
      url: request.url,
      authorization: request.headers.authorization,
      cookie: request.headers.cookie
    });
    request.resume();
    response.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-encoding": "gzip", etag: "upstream-html-etag" });
    response.end(opencodeHtmlGzip);
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    opencodeDefaultProject: "/workspace",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const frame = await fetch(`${serverUrl(cloudWeb)}/opencode/frame-url`, {
      headers: { accept: "application/json", cookie: "hwlab_session=session-a" }
    });
    const ticket = new URL((await frame.json()).url).searchParams.get("hwlab_opencode_ticket");
    const proxied = await fetch(`${serverUrl(cloudWeb)}/_opencode/?hwlab_opencode_ticket=${encodeURIComponent(ticket)}`, {
      headers: { accept: "text/html" }
    });
    assert.equal(proxied.status, 200);
    assert.equal(proxied.headers.get("content-encoding"), null);
    assert.equal(proxied.headers.get("etag"), null);
    const html = await proxied.text();
    assert.match(html, /data-hwlab-opencode-default-project/u);
    assert.match(html, /src="\/__hwlab\/opencode-default-project\.js"/u);
    assert.doesNotMatch(html, /localStorage/u);

    const ticketCookie = proxied.headers.get("set-cookie")?.match(/hwlab_opencode_ticket=([^;]+)/u)?.[1] || "";
    assert.ok(ticketCookie, "expected iframe HTML response to set OpenCode ticket cookie");
    const bootstrap = await fetch(`${serverUrl(cloudWeb)}/_opencode/__hwlab/opencode-default-project.js`, {
      headers: { accept: "text/javascript", cookie: `hwlab_opencode_ticket=${ticketCookie}` }
    });
    assert.equal(bootstrap.status, 200);
    assert.equal(bootstrap.headers.get("content-type"), "text/javascript; charset=utf-8");
    const bootstrapScript = await bootstrap.text();
    assert.match(bootstrapScript, /opencode\.global\.dat:server/u);
    assert.match(bootstrapScript, /opencode\.global\.dat:layout\.page/u);
    assert.match(bootstrapScript, /\/workspace/u);
    assert.deepEqual(opencodeRequests, [{
      url: "/",
      authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`,
      cookie: undefined
    }]);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
  }
});

test("cloud web OpenCode proxy injects upstream Basic Auth without forwarding HWLAB cookie", async () => {
  const opencodeRequests = [];
  const cloudApi = createServer((request, response) => {
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
  });
  const opencode = createServer((request, response) => {
    opencodeRequests.push({
      url: request.url,
      authorization: request.headers.authorization,
      cookie: request.headers.cookie
    });
    request.resume();
    response.writeHead(200, { "content-type": "text/javascript" });
    response.end("export const ok = true;\n");
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "127.0.0.1",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/assets/index.js`, {
      headers: { accept: "text/javascript", cookie: "hwlab_session=session-a", "x-trace-id": "trc_opencode_proxy" }
    });
    assert.equal(response.status, 200);
    assert.equal(await response.text(), "export const ok = true;\n");
    assert.deepEqual(opencodeRequests, [{
      url: "/assets/index.js",
      authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`,
      cookie: undefined
    }]);
  } finally {
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
  }
});

test("cloud web OpenCode proxy rewrites configured event stream directory", async () => {
  const cloudApi = createServer((request, response) => {
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
  });
  const opencode = createServer((request, response) => {
    request.resume();
    response.writeHead(200, { "content-type": "text/event-stream; charset=utf-8" });
    response.write('data: {"directory":"/workspace","payload":{"type":"session.status","properties":{"sessionID":"ses_1","status":{"type":"idle"}}}}\n\n');
    response.write('data: {"directory":"/other","payload":{"type":"server.heartbeat","properties":{}}}\n\n');
    response.end();
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "127.0.0.1",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    opencodeEventDirectoryRewrite: { from: "/workspace", to: "/" },
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/global/event`, {
      headers: { accept: "text/event-stream", cookie: "hwlab_session=session-a" }
    });
    assert.equal(response.status, 200);
    const text = await response.text();
    assert.match(text, /"directory":"\/"/u);
    assert.doesNotMatch(text, /"directory":"\/workspace"/u);
    assert.match(text, /"directory":"\/other"/u);
  } finally {
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
  }
});

test("cloud web OpenCode proxy emits bounded OTLP span", async () => {
  const otelBodies = [];
  const otel = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    otelBodies.push(JSON.parse(body));
    response.writeHead(200, { "content-type": "application/json" });
    response.end("{}\n");
  });
  await listen(otel);
  const restoreEnv = withEnv({ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: `${serverUrl(otel)}/v1/traces` });

  const cloudApi = createServer((request, response) => {
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
  });
  const opencode = createServer((request, response) => {
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: true }));
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "127.0.0.1",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/session/ses_otel/prompt_async`, {
      method: "POST",
      headers: { accept: "application/json", "content-type": "application/json", cookie: "hwlab_session=session-a" },
      body: JSON.stringify({ parts: [{ type: "text", text: "hi" }] })
    });
    assert.equal(response.status, 200);
    await waitFor(() => otelBodies.length > 0, "expected OpenCode proxy OTel span");
    const spans = otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []);
    const span = spans.find((item) => item.name === "opencode.proxy.request");
    assert.ok(span, "expected opencode.proxy.request span");
    const attrs = new Map((span.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue]));
    assert.equal(attrs.get("http.route"), "/session/:sessionID/prompt_async");
    assert.equal(attrs.get("http.response.status_class"), "2xx");
    assert.equal(attrs.get("valuesPrinted"), false);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
    await close(otel);
  }
});

test("cloud web OpenCode event stream emits rewrite OTLP stats when the client closes", async () => {
  const otelBodies = [];
  const otel = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    otelBodies.push(JSON.parse(body));
    response.writeHead(200, { "content-type": "application/json" });
    response.end("{}\n");
  });
  await listen(otel);
  const restoreEnv = withEnv({ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: `${serverUrl(otel)}/v1/traces` });

  const cloudApi = createServer((request, response) => {
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
  });
  const opencodeResponses = [];
  const opencode = createServer((request, response) => {
    request.resume();
    opencodeResponses.push(response);
    response.writeHead(200, { "content-type": "text/event-stream; charset=utf-8" });
    response.write('data: {"directory":"/workspace","payload":{"type":"session.status","properties":{"sessionID":"ses_1"}}}\n\n');
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "127.0.0.1",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    opencodeEventDirectoryRewrite: { from: "/workspace", to: "/" },
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/global/event`, {
      headers: { accept: "text/event-stream", cookie: "hwlab_session=session-a" }
    });
    assert.equal(response.status, 200);
    const reader = response.body.getReader();
    const first = await reader.read();
    assert.equal(first.done, false);
    const text = new TextDecoder().decode(first.value);
    assert.match(text, /"directory":"\/"/u);

    const collectSpans = () => otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []);
    await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.stream.start"), "expected OpenCode event stream start OTel span");
    const startSpan = collectSpans().find((item) => item.name === "opencode.proxy.stream.start");
    assert.ok(startSpan, "expected opencode.proxy.stream.start span");
    const startAttrs = new Map((startSpan.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue]));
    assert.equal(startAttrs.get("http.route"), "/global/event");
    assert.equal(startAttrs.get("opencode.proxy.phase"), "stream_start");
    assert.equal(startAttrs.get("opencode.proxy.streaming"), true);
    assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_enabled"), true);
    assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_from"), "/workspace");
    assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_to"), "/");

    await reader.cancel();

    await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.request"), "expected OpenCode event stream completion OTel span");
    const spans = collectSpans();
    const span = spans.find((item) => item.name === "opencode.proxy.request");
    assert.ok(span, "expected opencode.proxy.request span");
    const attrs = new Map((span.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue]));
    assert.equal(attrs.get("http.route"), "/global/event");
    assert.equal(attrs.get("opencode.proxy.streaming"), true);
    assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_enabled"), true);
    assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_from"), "/workspace");
    assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_to"), "/");
    assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_data_lines"), "1");
    assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_events"), "1");
    assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_json_errors"), "0");
  } finally {
    for (const response of opencodeResponses) response.destroy();
    restoreEnv();
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
    await close(otel);
  }
});

test("cloud web OpenCode proxy accepts short-lived tickets minted by the shell", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间",
    HWLAB_CLOUD_WEB_OPENCODE_URL: "https://opencode.example.test",
    HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true",
    HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "false",
    HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false",
    HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200",
    HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576"
  });
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-"));
  await writeFile(path.join(root, "index.html"), "<html><head></head><body><div id=\"root\"></div></body></html>\n", "utf8");
  const cloudApiCookies = [];
  const opencodeRequests = [];
  const cloudApi = createServer((request, response) => {
    cloudApiCookies.push(request.headers.cookie);
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
  });
  const opencode = createServer((request, response) => {
    opencodeRequests.push({
      url: request.url,
      authorization: request.headers.authorization,
      cookie: request.headers.cookie
    });
    request.resume();
    response.writeHead(200, { "content-type": "text/plain" });
    response.end("opencode ok\n");
  });
  await listen(cloudApi);
  await listen(opencode);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [root],
    cloudApiBaseUrl: serverUrl(cloudApi),
    cloudApiProxyTimeoutMs: 1000,
    opencodeUpstreamUrl: serverUrl(opencode),
    opencodeProxyHost: "",
    opencodeProxyTimeoutMs: 1000,
    opencodeUsername: "oc_user",
    opencodePassword: "oc_password",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, { "content-type": "application/json", "content-length": Buffer.byteLength(payload) });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const shell = await fetch(`${serverUrl(cloudWeb)}/opencode`, {
      headers: { accept: "text/html", cookie: "hwlab_session=session-a" }
    });
    assert.equal(shell.status, 200);
    const html = await shell.text();
    const config = JSON.parse(html.match(/window\.HWLAB_CLOUD_WEB_CONFIG=([^<]+);<\/script>/u)?.[1] ?? "null");
    const frameUrl = new URL(config.opencode.url);
    const ticket = frameUrl.searchParams.get("hwlab_opencode_ticket");
    assert.ok(ticket, "ticket should be appended to OpenCode iframe URL");

    const first = await fetch(`${serverUrl(cloudWeb)}/_opencode/?hwlab_opencode_ticket=${encodeURIComponent(ticket)}`, {
      headers: { accept: "text/plain" }
    });
    assert.equal(first.status, 200);
    assert.equal(await first.text(), "opencode ok\n");
    const ticketCookie = first.headers.get("set-cookie") ?? "";
    assert.match(ticketCookie, /hwlab_opencode_ticket=/u);
    assert.match(ticketCookie, /HttpOnly/u);
    assert.match(ticketCookie, /SameSite=None/u);

    const second = await fetch(`${serverUrl(cloudWeb)}/_opencode/assets/index.js`, {
      headers: { accept: "text/plain", cookie: ticketCookie.split(";")[0] }
    });
    assert.equal(second.status, 200);
    assert.equal(await second.text(), "opencode ok\n");
    assert.deepEqual(cloudApiCookies, ["hwlab_session=session-a", "hwlab_session=session-a", "hwlab_session=session-a"]);
    assert.deepEqual(opencodeRequests, [
      {
        url: "/",
        authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`,
        cookie: undefined
      },
      {
        url: "/assets/index.js",
        authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`,
        cookie: undefined
      }
    ]);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await close(opencode);
    await close(cloudApi);
    await rm(root, { recursive: true, force: true });
  }
});

function withEnv(values) {
  const previous = new Map();
  for (const [key, value] of Object.entries(values)) {
    previous.set(key, process.env[key]);
    process.env[key] = value;
  }
  return () => {
    for (const [key, value] of previous.entries()) {
      if (value === undefined) delete process.env[key];
      else process.env[key] = value;
    }
  };
}

function listen(server) {
  return new Promise((resolve, reject) => {
    server.once("error", reject);
    server.listen(0, "127.0.0.1", resolve);
  });
}

async function unusedPort() {
  const server = createServer();
  await listen(server);
  const port = server.address().port;
  await close(server);
  return port;
}

function canConnect(port) {
  return new Promise((resolve) => {
    const socket = connect(port, "127.0.0.1");
    socket.once("connect", () => {
      socket.destroy();
      resolve(true);
    });
    socket.once("error", () => resolve(false));
  });
}

function close(server) {
  return new Promise((resolve, reject) => {
    server.close((error) => (error ? reject(error) : resolve()));
  });
}

async function waitFor(predicate, message, timeoutMs = 1000) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    if (predicate()) return;
    await new Promise((resolve) => setTimeout(resolve, 20));
  }
  assert.fail(message);
}

function disconnectDuringResponse(baseUrl, pathname) {
  const url = new URL(pathname, baseUrl);
  return new Promise((resolve, reject) => {
    const socket = connect(Number(url.port), url.hostname);
    const timeout = setTimeout(() => {
      socket.destroy();
      reject(new Error("client disconnect probe timed out"));
    }, 2000);
    socket.once("connect", () => {
      socket.write(`GET ${url.pathname} HTTP/1.1\r\nHost: ${url.host}\r\nConnection: close\r\n\r\n`);
    });
    socket.once("data", () => {
      clearTimeout(timeout);
      socket.destroy();
      setTimeout(resolve, 50);
    });
    socket.once("error", (error) => {
      clearTimeout(timeout);
      reject(error);
    });
  });
}

function serverUrl(server) {
  const address = server.address();
  return `http://127.0.0.1:${address.port}`;
}
