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.deepEqual((await store.getSubscriptionSettings()).mainProxyGroupHiddenSlugs, []); await store.saveMainProxyGroupMemberSlugs(["existing-hy2"], ["existing-hy2"]); assert.deepEqual((await store.getSubscriptionSettings()).mainProxyGroupHiddenSlugs, ["existing-hy2"]); await store.saveMainProxyGroupMemberSlugs(["existing-custom"]); assert.deepEqual((await store.getSubscriptionSettings()).mainProxyGroupHiddenSlugs, ["existing-hy2"]); await store.saveMainProxyGroupMemberSlugs([], []); assert.deepEqual((await store.getSubscriptionSettings()).mainProxyGroupHiddenSlugs, []); 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 }); } });