feat: 支持证书托管和节点自动安装
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { test, expect } from "./e2e-fixtures";
|
||||
import { certificateFixture } from "./certificate-fixture";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("upload wildcard certificate, download script and complete NAT node registration", 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({ 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 = {
|
||||
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);
|
||||
expect(config.listen).toBe(":443");
|
||||
expect(config.trafficStats.listen).toBe(":9999");
|
||||
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();
|
||||
await expect(node.getByLabel("对外端口", { exact: true })).toHaveValue(
|
||||
"23456",
|
||||
);
|
||||
await expect(node.getByLabel("SNI", { exact: true })).toHaveValue(
|
||||
"nat.p.xn--876a.net",
|
||||
);
|
||||
await expect(node.getByLabel("启用节点", { exact: true })).toBeChecked();
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
Reference in New Issue
Block a user