Files
pikasTech-HWLAB/internal/dev-entrypoint/cloud-web-runtime.test.mjs
T
2026-06-20 14:26:44 +08:00

429 lines
14 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdtemp, 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 { createCloudWebServer, isClientDisconnectError } 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("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 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);
assert.equal(upstreamRequests.length, 2);
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: "{}"
});
} 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 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);
assert.equal(await response.text(), "<div id=\"root\"></div>\n", 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 {
await close(cloudWeb);
await rm(root, { recursive: true, force: true });
}
});
function listen(server) {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
}
function close(server) {
return new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
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}`;
}