Files
hy2-panel/tests/provisioning.spec.ts
T

186 lines
6.9 KiB
TypeScript

import { test, expect } from "./e2e-fixtures";
import { certificateFixture } from "./certificate-fixture";
import { readFileSync } from "node:fs";
import { parse } from "yaml";
for (const protocol of ["hy2", "anytls"] as const)
test(`upload wildcard certificate and register ${protocol} NAT node`, async ({
page,
request,
}) => {
const errors: string[] = [];
page.on("pageerror", (e) => errors.push(e.message));
const fixture = certificateFixture();
await page.goto("/login");
await page.getByLabel("用户名", { exact: true }).fill("ui-review");
await page.getByLabel("密码", { exact: true }).fill("ui-review-local-2026");
await page.getByRole("button", { name: "登录", exact: true }).click();
await expect(page).toHaveURL(/dashboard/);
await page.getByRole("link", { name: "证书与安装", exact: true }).click();
const upload = page.locator("details").filter({
has: page.getByRole("heading", { name: "上传证书", exact: true }),
});
await upload.getByLabel("证书名称").fill("测试通配符证书");
await upload.getByLabel("CRT / 完整证书链").setInputFiles({
name: "test.crt",
mimeType: "text/plain",
buffer: Buffer.from(fixture.certificate),
});
await upload.getByLabel("KEY / 私钥").setInputFiles({
name: "test.key",
mimeType: "text/plain",
buffer: Buffer.from("invalid"),
});
await upload.getByRole("button", { name: "上传证书", exact: true }).click();
await expect(upload.getByRole("alert")).toContainText("无法解析");
await upload.getByLabel("KEY / 私钥").setInputFiles({
name: "test.key",
mimeType: "text/plain",
buffer: Buffer.from(fixture.privateKey),
});
await upload.getByRole("button", { name: "上传证书", exact: true }).click();
await expect(
page.getByText("测试通配符证书", { exact: true }).first(),
).toBeVisible();
await page.reload();
const create = page.locator("details").filter({
has: page.getByRole("heading", {
name: "生成服务器安装脚本",
exact: true,
}),
});
if ((await create.getAttribute("open")) === null)
await create.locator("summary").click();
await create.getByLabel("节点名称", { exact: true }).fill("NAT 浏览器测试");
await create.getByLabel("节点 Slug", { exact: true }).fill("nat-browser");
await create.getByLabel("代理协议", { exact: true }).selectOption(protocol);
await create
.getByLabel("托管证书", { exact: true })
.selectOption({ label: "测试通配符证书" });
await create
.getByRole("button", { name: "生成安装脚本", exact: true })
.click();
const row = page
.locator(".provision-row")
.filter({ hasText: "nat-browser" });
await expect(row).toContainText("等待安装");
const downloadPath = await row
.getByRole("link", { name: "下载 SH" })
.getAttribute("href");
expect(
(await request.get(downloadPath!, { maxRedirects: 0 })).status(),
).toBe(307);
const [download] = await Promise.all([
page.waitForEvent("download"),
row.getByRole("link", { name: "下载 SH" }).click(),
]);
const script = readFileSync((await download.path())!, "utf8");
expect(script).not.toContain("PRIVATE KEY");
expect(script).toContain("--domain");
const token = script.match(/^TOKEN='([a-f0-9]+)'$/m)![1];
const ports =
protocol === "anytls"
? {
domain: "nat.p.xn--876a.net",
port: 23456,
listenPort: 23456,
statsPort: 19090,
controlPort: 19091,
}
: {
domain: "nat.p.xn--876a.net",
hy2Port: 23456,
statsPort: 23457,
hy2ListenPort: 443,
statsListenPort: 9999,
};
expect(
(
await request.post("/api/install", {
data: { action: "prepare", ports },
})
).status(),
).toBe(400);
expect(
(await request.post("/api/install", { data: "x".repeat(5000) })).status(),
).toBe(413);
const prepare = await request.post("/api/install", {
headers: { Authorization: `Bearer ${token}` },
data: { action: "prepare", ports },
});
expect(prepare.status()).toBe(200);
expect(prepare.headers()["cache-control"]).toBe("no-store");
const bundle = await prepare.json();
expect(bundle.privateKey).toBe(fixture.privateKey);
const config = parse(bundle.config);
if (protocol === "hy2") {
expect(config.listen).toBe(":443");
expect(config.trafficStats.listen).toBe(":9999");
} else {
expect(config.inbounds[0].type).toBe("anytls");
expect(config.experimental.v2ray_api.listen).toBe("127.0.0.1:19090");
}
expect(
(
await request.post("/api/install", {
headers: { Authorization: `Bearer ${token}` },
data: { action: "complete" },
})
).status(),
).toBe(200);
await page.reload();
await expect(row).toContainText("已安装");
await expect(row.getByRole("link", { name: "下载 SH" })).toHaveCount(0);
expect(
(
await request.post("/api/install", {
headers: { Authorization: `Bearer ${token}` },
data: { action: "prepare", ports },
})
).status(),
).toBe(400);
await page.screenshot({
path: "test-results/certificates-desktop.png",
fullPage: true,
});
await page.setViewportSize({ width: 390, height: 844 });
await page.screenshot({
path: "test-results/certificates-mobile.png",
fullPage: true,
});
expect(
await page.evaluate(
() => document.documentElement.scrollWidth <= innerWidth + 1,
),
).toBe(true);
await page.getByRole("link", { name: "节点管理", exact: true }).click();
const node = page
.locator(".entity-table__item")
.filter({ hasText: "nat-browser" });
await expect(node).toContainText("nat.p.xn--876a.net");
await node.locator(":scope > summary").click();
if (protocol === "hy2") {
await expect(node.getByLabel("对外端口", { exact: true })).toHaveValue(
"23456",
);
await expect(node.getByLabel("SNI", { exact: true })).toHaveValue(
"nat.p.xn--876a.net",
);
} else {
await expect(node).toContainText("节点主动上报");
await expect(node.getByLabel("节点名称 / 订阅名")).toHaveValue(
"NAT 浏览器测试",
);
const sync = await request.post("/api/agent/nat-browser/sync", {
headers: { Authorization: `Bearer ${bundle.agent.token}` },
data: { epoch: "test:1", traffic: {}, streamCount: 1 },
});
expect(sync.status()).toBe(200);
expect(
(await sync.json()).config.inbounds[0].users.length,
).toBeGreaterThan(0);
}
await expect(node.getByLabel("启用节点", { exact: true })).toBeChecked();
expect(errors).toEqual([]);
});