feat: 支持自定义订阅节点并兼容现有数据库
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { test } from "node:test";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
// Start with the real deployed schema rather than the new migration output.
|
||||
test("existing v4 boots and custom nodes stay outside HY2 polling/auth while retaining credentials", async () => {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), "hy2-custom-store-"));
|
||||
process.env.DATABASE_PATH = path.join(dir, "remote-v4.db");
|
||||
process.env.ADMIN_USERNAME = "test-admin";
|
||||
process.env.ADMIN_PASSWORD = "test-admin-local-only";
|
||||
process.env.POLLER_ENABLED = "false";
|
||||
const legacy = new Database(process.env.DATABASE_PATH);
|
||||
legacy.exec(readFileSync("tests/fixtures/remote-v4-schema.sql", "utf8"));
|
||||
legacy.exec(`INSERT INTO hy2_nodes (id,slug,name,auth_token,traffic_stats_url,traffic_stats_secret,created_at,updated_at,server_host)
|
||||
VALUES (91,'existing-hy2','Existing HY2','test-auth','http://stats.example:9999','test-stats','2026-01-01','2026-01-01','hy2.example');`);
|
||||
const yaml =
|
||||
'# original comment\n- name: "Remote Custom"\n type: anytls\n password: "test-pass"\n server: custom.example\n port: 443\n';
|
||||
legacy
|
||||
.prepare(
|
||||
`INSERT INTO hy2_nodes (id,slug,name,auth_token,traffic_stats_url,traffic_stats_secret,created_at,updated_at,node_type,custom_config_yaml,client_name)
|
||||
VALUES (92,'existing-custom','Display','test-original-auth','','test-original-stats','2026-01-01','2026-01-01','custom',?,'Old alias')`,
|
||||
)
|
||||
.run(yaml);
|
||||
legacy.exec(
|
||||
`INSERT INTO app_settings VALUES ('main_proxy_group_member_slugs_json','["existing-custom","existing-hy2"]','2026-01-01')`,
|
||||
);
|
||||
const before = legacy.prepare("SELECT * FROM hy2_nodes ORDER BY id").all();
|
||||
legacy.close();
|
||||
const { ensureDb } = await import("../../lib/db");
|
||||
const store = await import("../../lib/store");
|
||||
const { verifyNodeToken } = await import("../../lib/hy2");
|
||||
const db = await ensureDb();
|
||||
try {
|
||||
assert.deepEqual(
|
||||
db.prepare("SELECT * FROM hy2_nodes ORDER BY id").all(),
|
||||
before,
|
||||
);
|
||||
assert.deepEqual(
|
||||
(await store.getSubscriptionSettings()).mainProxyGroupMemberSlugs,
|
||||
["existing-custom", "existing-hy2"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
(await store.listEnabledNodesForPolling()).map((node) => node.id),
|
||||
[91],
|
||||
);
|
||||
assert.equal((await store.listSubscriptionNodes()).length, 2);
|
||||
assert.equal(
|
||||
(await verifyNodeToken("existing-custom", "test-original-auth")).reason,
|
||||
"node_not_hy2",
|
||||
);
|
||||
assert.equal((await verifyNodeToken("existing-hy2", "test-auth")).ok, true);
|
||||
const custom = (await store.getNodeBySlug("existing-custom"))!;
|
||||
assert.equal(store.getNodeProxyName(custom), "Remote Custom");
|
||||
const input = {
|
||||
nodeType: "custom" as const,
|
||||
slug: "existing-custom",
|
||||
name: "Display",
|
||||
authToken: "",
|
||||
hy2Listen: "",
|
||||
serverHost: "",
|
||||
serverPort: 443,
|
||||
clientName: "",
|
||||
bandwidthUpMbps: 30,
|
||||
bandwidthDownMbps: 30,
|
||||
skipCertVerify: false,
|
||||
sni: null,
|
||||
customConfigYaml: yaml.replace("Remote Custom", "Updated Custom"),
|
||||
trafficStatsUrl: "",
|
||||
trafficStatsListen: ":9999",
|
||||
trafficStatsSecret: "",
|
||||
enabled: true,
|
||||
pollIntervalSeconds: 15,
|
||||
};
|
||||
await store.updateNode({ ...input, id: custom.id });
|
||||
const saved = (await store.getNodeBySlug("existing-custom"))!;
|
||||
assert.equal(saved.id, 92);
|
||||
assert.equal(saved.custom_config_yaml, input.customConfigYaml);
|
||||
assert.equal(saved.auth_token, "test-original-auth");
|
||||
assert.equal(saved.traffic_stats_secret, "test-original-stats");
|
||||
assert.equal(store.getNodeProxyName(saved), "Updated Custom");
|
||||
await assert.rejects(() =>
|
||||
store.updateNode({
|
||||
...input,
|
||||
id: 92,
|
||||
customConfigYaml: "name: [invalid",
|
||||
}),
|
||||
);
|
||||
assert.deepEqual(await store.getNodeBySlug("existing-custom"), saved);
|
||||
await store.updateNode({ ...input, id: 92, enabled: false });
|
||||
assert.deepEqual(
|
||||
(await store.listSubscriptionNodes()).map((node) => node.id),
|
||||
[91],
|
||||
);
|
||||
} finally {
|
||||
db.close();
|
||||
global.__hy2PanelDb = undefined;
|
||||
global.__hy2PanelDbInit = undefined;
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user