diff --git a/server/lib/ip.test.ts b/server/lib/ip.test.ts new file mode 100644 index 00000000..f3925cf1 --- /dev/null +++ b/server/lib/ip.test.ts @@ -0,0 +1,183 @@ +import { cidrToRange, findNextAvailableCidr } from "./ip"; + +/** + * Compares two objects for deep equality + * @param actual The actual value to test + * @param expected The expected value to compare against + * @param message The message to display if assertion fails + * @throws Error if objects are not equal + */ +export function assertEqualsObj(actual: T, expected: T, message: string): void { + const actualStr = JSON.stringify(actual); + const expectedStr = JSON.stringify(expected); + if (actualStr !== expectedStr) { + throw new Error(`${message}\nExpected: ${expectedStr}\nActual: ${actualStr}`); + } +} + +/** + * Compares two primitive values for equality + * @param actual The actual value to test + * @param expected The expected value to compare against + * @param message The message to display if assertion fails + * @throws Error if values are not equal + */ +export function assertEquals(actual: T, expected: T, message: string): void { + if (actual !== expected) { + throw new Error(`${message}\nExpected: ${expected}\nActual: ${actual}`); + } +} + +/** + * Tests if a function throws an expected error + * @param fn The function to test + * @param expectedError The expected error message or part of it + * @param message The message to display if assertion fails + * @throws Error if function doesn't throw or throws unexpected error + */ +export function assertThrows( + fn: () => void, + expectedError: string, + message: string +): void { + try { + fn(); + throw new Error(`${message}: Expected to throw "${expectedError}"`); + } catch (error) { + if (!(error instanceof Error)) { + throw new Error(`${message}\nUnexpected error type: ${typeof error}`); + } + + if (!error.message.includes(expectedError)) { + throw new Error( + `${message}\nExpected error: ${expectedError}\nActual error: ${error.message}` + ); + } + } +} + + +// Test cases +function testFindNextAvailableCidr() { + console.log("Running findNextAvailableCidr tests..."); + + // Test 1: Basic IPv4 allocation + { + const existing = ["10.0.0.0/16", "10.1.0.0/16"]; + const result = findNextAvailableCidr(existing, 16, "10.0.0.0/8"); + assertEquals(result, "10.2.0.0/16", "Basic IPv4 allocation failed"); + } + + // Test 2: Finding gap between allocations + { + const existing = ["10.0.0.0/16", "10.2.0.0/16"]; + const result = findNextAvailableCidr(existing, 16, "10.0.0.0/8"); + assertEquals(result, "10.1.0.0/16", "Finding gap between allocations failed"); + } + + // Test 3: No available space + { + const existing = ["10.0.0.0/8"]; + const result = findNextAvailableCidr(existing, 8, "10.0.0.0/8"); + assertEquals(result, null, "No available space test failed"); + } + + // // Test 4: IPv6 allocation + // { + // const existing = ["2001:db8::/32", "2001:db8:1::/32"]; + // const result = findNextAvailableCidr(existing, 32, "2001:db8::/16"); + // assertEquals(result, "2001:db8:2::/32", "Basic IPv6 allocation failed"); + // } + + // // Test 5: Mixed IP versions + // { + // const existing = ["10.0.0.0/16", "2001:db8::/32"]; + // assertThrows( + // () => findNextAvailableCidr(existing, 16), + // "All CIDRs must be of the same IP version", + // "Mixed IP versions test failed" + // ); + // } + + // Test 6: Empty input + { + const existing: string[] = []; + const result = findNextAvailableCidr(existing, 16); + assertEquals(result, null, "Empty input test failed"); + } + + // Test 7: Block size alignment + { + const existing = ["10.0.0.0/24"]; + const result = findNextAvailableCidr(existing, 24, "10.0.0.0/16"); + assertEquals(result, "10.0.1.0/24", "Block size alignment test failed"); + } + + // Test 8: Block size alignment + { + const existing: string[] = []; + const result = findNextAvailableCidr(existing, 24, "10.0.0.0/16"); + assertEquals(result, "10.0.0.0/24", "Block size alignment test failed"); + } + + // Test 9: Large block size request + { + const existing = ["10.0.0.0/24", "10.0.1.0/24"]; + const result = findNextAvailableCidr(existing, 16, "10.0.0.0/16"); + assertEquals(result, null, "Large block size request test failed"); + } + + console.log("All findNextAvailableCidr tests passed!"); +} + +// function testCidrToRange() { +// console.log("Running cidrToRange tests..."); + +// // Test 1: Basic IPv4 conversion +// { +// const result = cidrToRange("192.168.0.0/24"); +// assertEqualsObj(result, { +// start: BigInt("3232235520"), +// end: BigInt("3232235775") +// }, "Basic IPv4 conversion failed"); +// } + +// // Test 2: IPv6 conversion +// { +// const result = cidrToRange("2001:db8::/32"); +// assertEqualsObj(result, { +// start: BigInt("42540766411282592856903984951653826560"), +// end: BigInt("42540766411282592875350729025363378175") +// }, "IPv6 conversion failed"); +// } + +// // Test 3: Invalid prefix length +// { +// assertThrows( +// () => cidrToRange("192.168.0.0/33"), +// "Invalid prefix length for IPv4", +// "Invalid IPv4 prefix test failed" +// ); +// } + +// // Test 4: Invalid IPv6 prefix +// { +// assertThrows( +// () => cidrToRange("2001:db8::/129"), +// "Invalid prefix length for IPv6", +// "Invalid IPv6 prefix test failed" +// ); +// } + +// console.log("All cidrToRange tests passed!"); +// } + +// Run all tests +try { + // testCidrToRange(); + testFindNextAvailableCidr(); + console.log("All tests passed successfully!"); +} catch (error) { + console.error("Test failed:", error); + process.exit(1); +} \ No newline at end of file diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 62cf1d2d..86fe1169 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -100,7 +100,7 @@ function bigIntToIp(num: bigint, version: IPVersion): string { /** * Converts CIDR to IP range */ -function cidrToRange(cidr: string): IPRange { +export function cidrToRange(cidr: string): IPRange { const [ip, prefix] = cidr.split('/'); const version = detectIpVersion(ip); const prefixBits = parseInt(prefix); @@ -132,15 +132,22 @@ export function findNextAvailableCidr( blockSize: number, startCidr?: string ): string | null { - if (existingCidrs.length === 0) return null; + + if (!startCidr && existingCidrs.length === 0) { + return null; + } + + // If no existing CIDRs, use the IP version from startCidr + const version = startCidr + ? detectIpVersion(startCidr.split('/')[0]) + : 4; // Default to IPv4 if no startCidr provided - // Determine IP version from first CIDR - const version = detectIpVersion(existingCidrs[0].split('/')[0]); // Use appropriate default startCidr if none provided startCidr = startCidr || (version === 4 ? "0.0.0.0/0" : "::/0"); - // Ensure all CIDRs are same version - if (existingCidrs.some(cidr => detectIpVersion(cidr.split('/')[0]) !== version)) { + // If there are existing CIDRs, ensure all are same version + if (existingCidrs.length > 0 && + existingCidrs.some(cidr => detectIpVersion(cidr.split('/')[0]) !== version)) { throw new Error('All CIDRs must be of the same IP version'); }