import { assertEquals } from "@std/assert"; import { exportState } from "./export.ts"; import type { ActualState } from "./state/actual.ts"; import type { NbDnsNameserverGroup, NbGroup, NbNetwork, NbNetworkResource, NbNetworkRouter, NbPeer, NbPolicy, NbPostureCheck, NbRoute, NbSetupKey, NbUser, } from "./netbird/types.ts"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Builds a minimal ActualState with indexed maps from raw arrays. */ function buildActualState(data: { groups?: NbGroup[]; setupKeys?: NbSetupKey[]; peers?: NbPeer[]; policies?: NbPolicy[]; routes?: NbRoute[]; dns?: NbDnsNameserverGroup[]; postureChecks?: NbPostureCheck[]; networks?: NbNetwork[]; networkResources?: Map; networkRouters?: Map; users?: NbUser[]; }): ActualState { const groups = data.groups ?? []; const setupKeys = data.setupKeys ?? []; const peers = data.peers ?? []; const policies = data.policies ?? []; const routes = data.routes ?? []; const dns = data.dns ?? []; const postureChecks = data.postureChecks ?? []; const networks = data.networks ?? []; const users = data.users ?? []; return { groups, groupsByName: new Map(groups.map((g) => [g.name, g])), groupsById: new Map(groups.map((g) => [g.id, g])), setupKeys, setupKeysByName: new Map(setupKeys.map((k) => [k.name, k])), peers, peersByName: new Map(peers.map((p) => [p.name, p])), peersById: new Map(peers.map((p) => [p.id, p])), policies, policiesByName: new Map(policies.map((p) => [p.name, p])), routes, routesByNetworkId: new Map(routes.map((r) => [r.network_id, r])), dns, dnsByName: new Map(dns.map((d) => [d.name, d])), postureChecks, postureChecksByName: new Map(postureChecks.map((pc) => [pc.name, pc])), networks, networksByName: new Map(networks.map((n) => [n.name, n])), networkResources: data.networkResources ?? new Map(), networkRouters: data.networkRouters ?? new Map(), users, usersByEmail: new Map(users.map((u) => [u.email, u])), }; } function makeGroup( overrides: Partial & Pick, ): NbGroup { return { peers_count: 0, peers: [], issued: "api", ...overrides, }; } function makeSetupKey( overrides: Partial & Pick, ): NbSetupKey { return { id: 1, type: "one-off", key: "NBSK-masked", expires: "2027-01-01T00:00:00Z", valid: true, revoked: false, used_times: 0, state: "valid", auto_groups: [], usage_limit: 1, ...overrides, }; } // --------------------------------------------------------------------------- // Tests: Normal state with groups, keys, policy // --------------------------------------------------------------------------- Deno.test("exportState: normal state with groups, keys, and policy", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-pilots", name: "pilots", peers: [{ id: "p1", name: "Pilot-hawk-72" }], }), makeGroup({ id: "g-vehicles", name: "vehicles" }), ], setupKeys: [ makeSetupKey({ name: "Pilot-hawk-72", auto_groups: ["g-pilots"], used_times: 1, usage_limit: 1, }), ], policies: [ { id: "pol1", name: "allow-pilot-vehicle", description: "pilot to vehicle", enabled: true, source_posture_checks: [], rules: [ { name: "rule1", description: "", enabled: true, action: "accept", bidirectional: true, protocol: "all", sources: ["g-pilots"], destinations: ["g-vehicles"], }, ], }, ], }); const exported = exportState(actual); // Groups exported with correct peer mapping assertEquals(Object.keys(exported.groups), ["pilots", "vehicles"]); assertEquals(exported.groups["pilots"].peers, ["Pilot-hawk-72"]); assertEquals(exported.groups["vehicles"].peers, []); // Setup key with auto_groups resolved to names assertEquals(Object.keys(exported.setup_keys), ["Pilot-hawk-72"]); assertEquals(exported.setup_keys["Pilot-hawk-72"].auto_groups, ["pilots"]); assertEquals(exported.setup_keys["Pilot-hawk-72"].enrolled, true); assertEquals(exported.setup_keys["Pilot-hawk-72"].type, "one-off"); assertEquals(exported.setup_keys["Pilot-hawk-72"].expires_in, 604800); // Policy with source/destination resolved assertEquals(Object.keys(exported.policies), ["allow-pilot-vehicle"]); assertEquals(exported.policies["allow-pilot-vehicle"].sources, ["pilots"]); assertEquals(exported.policies["allow-pilot-vehicle"].destinations, [ "vehicles", ]); assertEquals(exported.policies["allow-pilot-vehicle"].bidirectional, true); assertEquals(exported.policies["allow-pilot-vehicle"].protocol, "all"); assertEquals(exported.policies["allow-pilot-vehicle"].action, "accept"); }); // --------------------------------------------------------------------------- // Tests: Empty state (only "All" group) // --------------------------------------------------------------------------- Deno.test("exportState: empty state with only All group produces empty export", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-all", name: "All", issued: "jwt" }), ], }); const exported = exportState(actual); assertEquals(Object.keys(exported.groups).length, 0); assertEquals(Object.keys(exported.setup_keys).length, 0); assertEquals(Object.keys(exported.policies).length, 0); assertEquals(Object.keys(exported.routes).length, 0); assertEquals(Object.keys(exported.dns.nameserver_groups).length, 0); }); // --------------------------------------------------------------------------- // Tests: auto_groups ID-to-name mapping // --------------------------------------------------------------------------- Deno.test("exportState: auto_groups IDs are resolved to group names", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-alpha", name: "alpha" }), makeGroup({ id: "g-beta", name: "beta" }), ], setupKeys: [ makeSetupKey({ name: "key-1", auto_groups: ["g-alpha", "g-beta"], }), ], }); const exported = exportState(actual); assertEquals(exported.setup_keys["key-1"].auto_groups, ["alpha", "beta"]); }); Deno.test("exportState: auto_groups with unresolvable IDs are dropped", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-alpha", name: "alpha" }), ], setupKeys: [ makeSetupKey({ name: "key-1", auto_groups: ["g-alpha", "g-nonexistent"], }), ], }); const exported = exportState(actual); assertEquals(exported.setup_keys["key-1"].auto_groups, ["alpha"]); }); // --------------------------------------------------------------------------- // Tests: Enrolled detection // --------------------------------------------------------------------------- Deno.test("exportState: enrolled detection — used key is enrolled", () => { const actual = buildActualState({ setupKeys: [ makeSetupKey({ name: "used-key", used_times: 1, usage_limit: 1 }), ], }); assertEquals(exportState(actual).setup_keys["used-key"].enrolled, true); }); Deno.test("exportState: enrolled detection — unused key is not enrolled", () => { const actual = buildActualState({ setupKeys: [ makeSetupKey({ name: "fresh-key", used_times: 0, usage_limit: 1 }), ], }); assertEquals(exportState(actual).setup_keys["fresh-key"].enrolled, false); }); Deno.test("exportState: enrolled detection — unlimited reusable is never enrolled", () => { const actual = buildActualState({ setupKeys: [ makeSetupKey({ name: "reusable-key", type: "reusable", used_times: 50, usage_limit: 0, }), ], }); assertEquals( exportState(actual).setup_keys["reusable-key"].enrolled, false, ); }); Deno.test("exportState: enrolled detection — partially used is not enrolled", () => { const actual = buildActualState({ setupKeys: [ makeSetupKey({ name: "partial-key", type: "reusable", used_times: 2, usage_limit: 5, }), ], }); assertEquals( exportState(actual).setup_keys["partial-key"].enrolled, false, ); }); // --------------------------------------------------------------------------- // Tests: System groups excluded // --------------------------------------------------------------------------- Deno.test("exportState: system groups are excluded", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-all", name: "All", issued: "jwt" }), makeGroup({ id: "g-jwt", name: "jwt-group", issued: "jwt" }), makeGroup({ id: "g-int", name: "integration-group", issued: "integration", }), makeGroup({ id: "g-api", name: "user-group", issued: "api" }), ], }); const exported = exportState(actual); const groupNames = Object.keys(exported.groups); assertEquals(groupNames, ["user-group"]); }); Deno.test("exportState: All group with api issued is still excluded", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-all", name: "All", issued: "api" }), makeGroup({ id: "g-user", name: "my-group", issued: "api" }), ], }); const exported = exportState(actual); assertEquals(Object.keys(exported.groups), ["my-group"]); }); // --------------------------------------------------------------------------- // Tests: Group peers filter by setup key name // --------------------------------------------------------------------------- Deno.test("exportState: group peers only include names matching setup keys", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g1", name: "ops", peers: [ { id: "p1", name: "Pilot-hawk-72" }, { id: "p2", name: "random-peer-no-key" }, ], }), ], setupKeys: [ makeSetupKey({ name: "Pilot-hawk-72" }), ], }); const exported = exportState(actual); assertEquals(exported.groups["ops"].peers, ["Pilot-hawk-72"]); }); // --------------------------------------------------------------------------- // Tests: Policies // --------------------------------------------------------------------------- Deno.test("exportState: policies with empty rules are skipped", () => { const actual = buildActualState({ policies: [ { id: "pol1", name: "empty-policy", description: "no rules", enabled: true, source_posture_checks: [], rules: [], }, ], }); assertEquals(Object.keys(exportState(actual).policies).length, 0); }); Deno.test("exportState: policy sources/destinations as {id,name} objects are resolved", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-src", name: "source-group" }), makeGroup({ id: "g-dst", name: "dest-group" }), ], policies: [ { id: "pol1", name: "object-refs", description: "", enabled: true, source_posture_checks: [], rules: [ { name: "r1", description: "", enabled: true, action: "accept", bidirectional: false, protocol: "tcp", ports: ["443", "8080"], sources: [{ id: "g-src", name: "source-group" }], destinations: [{ id: "g-dst", name: "dest-group" }], }, ], }, ], }); const exported = exportState(actual); assertEquals(exported.policies["object-refs"].sources, ["source-group"]); assertEquals(exported.policies["object-refs"].destinations, ["dest-group"]); assertEquals(exported.policies["object-refs"].protocol, "tcp"); assertEquals(exported.policies["object-refs"].ports, ["443", "8080"]); assertEquals(exported.policies["object-refs"].bidirectional, false); }); Deno.test("exportState: policy without ports omits the ports field", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g1", name: "g" }), ], policies: [ { id: "pol1", name: "no-ports", description: "", enabled: true, source_posture_checks: [], rules: [ { name: "r1", description: "", enabled: true, action: "accept", bidirectional: true, protocol: "all", sources: ["g1"], destinations: ["g1"], }, ], }, ], }); const exported = exportState(actual); assertEquals(exported.policies["no-ports"].ports, undefined); }); // --------------------------------------------------------------------------- // Tests: Routes // --------------------------------------------------------------------------- Deno.test("exportState: routes keyed by network_id with IDs resolved", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-pg", name: "peer-group" }), makeGroup({ id: "g-dist", name: "dist-group" }), ], routes: [ { id: "r1", description: "LAN route", network_id: "lan-net", enabled: true, peer_groups: ["g-pg"], network: "10.0.0.0/24", metric: 100, masquerade: true, groups: ["g-dist"], keep_route: false, }, ], }); const exported = exportState(actual); assertEquals(Object.keys(exported.routes), ["lan-net"]); assertEquals(exported.routes["lan-net"].peer_groups, ["peer-group"]); assertEquals(exported.routes["lan-net"].distribution_groups, ["dist-group"]); assertEquals(exported.routes["lan-net"].network, "10.0.0.0/24"); assertEquals(exported.routes["lan-net"].metric, 100); assertEquals(exported.routes["lan-net"].masquerade, true); assertEquals(exported.routes["lan-net"].enabled, true); assertEquals(exported.routes["lan-net"].keep_route, false); }); Deno.test("exportState: route with domains and no network", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g1", name: "grp" }), ], routes: [ { id: "r2", description: "DNS route", network_id: "dns-route", enabled: true, peer_groups: ["g1"], domains: ["example.com"], metric: 9999, masquerade: false, groups: ["g1"], keep_route: true, }, ], }); const exported = exportState(actual); assertEquals(exported.routes["dns-route"].domains, ["example.com"]); assertEquals(exported.routes["dns-route"].network, undefined); }); // --------------------------------------------------------------------------- // Tests: DNS // --------------------------------------------------------------------------- Deno.test("exportState: DNS nameserver groups with IDs resolved", () => { const actual = buildActualState({ groups: [ makeGroup({ id: "g-dns", name: "dns-group" }), ], dns: [ { id: "d1", name: "internal-dns", description: "internal resolver", nameservers: [{ ip: "1.1.1.1", ns_type: "udp", port: 53 }], enabled: true, groups: ["g-dns"], primary: true, domains: ["internal."], search_domains_enabled: false, }, ], }); const exported = exportState(actual); assertEquals(Object.keys(exported.dns.nameserver_groups), ["internal-dns"]); const ns = exported.dns.nameserver_groups["internal-dns"]; assertEquals(ns.groups, ["dns-group"]); assertEquals(ns.nameservers, [{ ip: "1.1.1.1", ns_type: "udp", port: 53 }]); assertEquals(ns.primary, true); assertEquals(ns.domains, ["internal."]); assertEquals(ns.search_domains_enabled, false); assertEquals(ns.enabled, true); assertEquals(ns.description, "internal resolver"); });