import { describe, expect, it } from "vitest"; import { buildDnsRecord, inspectDnsZone } from "../../src/network/dns"; import { parseVlsmRequirements, planVlsm } from "../../src/network/vlsm"; import { buildUrl, inspectUrl } from "../../src/network/url"; describe("URL inspection", () => { it("preserves duplicate query values and resolves relative URLs", () => { const inspected = inspectUrl( "../items?a=1&a=2#part", "https://Example.com/a/b/", ); expect(inspected.hostname).toBe("example.com"); expect(inspected.query).toEqual([ { key: "a", value: "1" }, { key: "a", value: "2" }, ]); expect(buildUrl(inspected)).toBe( "https://example.com/a/items?a=1&a=2#part", ); }); it("warns about credentials without disclosing the password", () => { const inspected = inspectUrl("https://alice:secret@example.test/"); expect(inspected.hasPassword).toBe(true); expect(JSON.stringify(inspected)).not.toContain("secret"); }); }); describe("DNS construction", () => { it("builds common zone-file records", () => { expect( buildDnsRecord({ owner: "www", ttl: 3600, type: "A", value: "192.0.2.4", }), ).toBe("www 3600 IN A 192.0.2.4"); expect( buildDnsRecord({ owner: "@", ttl: 300, type: "MX", value: "mail.example.", priority: 10, }), ).toBe("@ 300 IN MX 10 mail.example."); }); it("quotes TXT data and rejects oversized values", () => { expect( buildDnsRecord({ owner: "@", ttl: 60, type: "TXT", value: 'a"b' }), ).toContain('"a\\"b"'); expect(() => buildDnsRecord({ owner: "@", ttl: 60, type: "TXT", value: "x".repeat(256), }), ).toThrow(/255/u); }); it("canonicalises addresses and rejects malformed IPv6 text", () => { expect( buildDnsRecord({ owner: "@", ttl: 60, type: "AAAA", value: "2001:0db8:0:0:0:0:0:1", }), ).toBe("@ 60 IN AAAA 2001:db8::1"); expect(() => buildDnsRecord({ owner: "@", ttl: 60, type: "AAAA", value: "::::" }), ).toThrow(/IPv6/u); }); it("parses a bounded zone inertly and validates common record data", () => { const result = inspectDnsZone( "$ORIGIN example.test.\n$TTL 1h\n@ IN SOA ns hostmaster 1 1h 15m 1w 5m\n@ IN NS ns\nns IN A 192.0.2.1\n IN AAAA 2001:db8::1\n_unknown IN SVCB 1 target.example.", ); expect(result.origin).toBe("example.test."); expect(result.records).toHaveLength(5); expect(result.records[2]?.normalized).toContain( "ns.example.test. 3600 IN A 192.0.2.1", ); expect(result.records[3]?.owner).toBe("ns.example.test."); expect(result.coverage).toMatchObject({ validatedRecords: 4, syntaxOnlyRecords: 1, }); expect(result.diagnostics[0]?.message).toMatch(/syntax-only/u); }); it("rejects include directives without reading external files", () => { const result = inspectDnsZone("$INCLUDE secrets.zone"); expect(result.records).toEqual([]); expect(result.diagnostics[0]).toMatchObject({ severity: "error" }); }); it("accepts wildcard/root owners and keeps non-IN records syntax-only", () => { const result = inspectDnsZone( "$ORIGIN example.test.\n*.web IN A 192.0.2.9\n. IN NS a.root-servers.net.\nlegacy CH A not-an-ip", ); expect(result.records.map((record) => record.owner)).toEqual([ "*.web.example.test.", ".", "legacy.example.test.", ]); expect(result.records[2]).toMatchObject({ dnsClass: "CH", validation: "syntax-only", data: ["not-an-ip"], }); expect(result.diagnostics).toContainEqual( expect.objectContaining({ message: expect.stringContaining("only IN") }), ); }); it("joins names correctly beneath the DNS root origin", () => { const result = inspectDnsZone("$ORIGIN .\nwww IN A 192.0.2.10"); expect(result.records[0]).toMatchObject({ owner: "www.", normalized: "www. IN A 192.0.2.10", }); expect(result.diagnostics).toEqual([]); }); }); describe("VLSM planning", () => { it("allocates largest requirements first on subnet boundaries", () => { const plan = planVlsm( "10.0.0.0/24", parseVlsmRequirements("Printers,12\nOffice,100\nLab,50"), ); expect(plan.allocations.map((item) => item.network)).toEqual([ "10.0.0.0/25", "10.0.0.128/26", "10.0.0.192/28", ]); expect(plan.allocations[0]?.name).toBe("Office"); expect(plan.freeAddresses).toBe(48); }); it("fails atomically when requirements do not fit", () => { expect(() => planVlsm("192.0.2.0/29", [ { name: "one", hosts: 4 }, { name: "two", hosts: 4 }, ]), ).toThrow(/does not fit/u); }); it("supports a conventional full IPv4 /0 capacity without unsafe numbers", () => { const plan = planVlsm("0.0.0.0/0", [ { name: "whole range", hosts: 4_294_967_294 }, ]); expect(plan.allocations[0]).toMatchObject({ network: "0.0.0.0/0", broadcast: "255.255.255.255", usableHosts: 4_294_967_294, }); }); });