feat: IPv6 outbound rotation via curl --interface
- Add IPv6SourcePool for round-robin IP rotation - fetchViaCurl: Bun.spawn + curl -6 --interface for source binding - Auto-fallback to regular fetch when IPv6 connection fails - Response decompression via --compressed flag - Docker + GHCR build/deploy workflows - 8 routable IPv6 addresses on VPS
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { test, expect, describe } from "bun:test";
|
||||
import { IPv6SourcePool } from "./ipv6-pool";
|
||||
|
||||
describe("IPv6SourcePool", () => {
|
||||
test("should load addresses from comma-separated string", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1,2001:db8::2,2001:db8::3");
|
||||
|
||||
expect(pool.size).toBe(3);
|
||||
expect(pool.configured).toBe(true);
|
||||
});
|
||||
|
||||
test("should return null when empty", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
expect(pool.size).toBe(0);
|
||||
expect(pool.configured).toBe(false);
|
||||
expect(pool.getNext()).toBeNull();
|
||||
});
|
||||
|
||||
test("should rotate through addresses round-robin", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1,2001:db8::2,2001:db8::3");
|
||||
|
||||
expect(pool.getNext()).toBe("2001:db8::1");
|
||||
expect(pool.getNext()).toBe("2001:db8::2");
|
||||
expect(pool.getNext()).toBe("2001:db8::3");
|
||||
expect(pool.getNext()).toBe("2001:db8::1"); // wraps around
|
||||
});
|
||||
|
||||
test("should skip failed sources", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1,2001:db8::2,2001:db8::3");
|
||||
pool.setFailureThreshold(1);
|
||||
|
||||
pool.markFailed("2001:db8::2");
|
||||
|
||||
const results = [pool.getNext(), pool.getNext(), pool.getNext(), pool.getNext()];
|
||||
// Should skip 2001:db8::2 (disabled after 1 failure with threshold=1)
|
||||
expect(results).not.toContain("2001:db8::2");
|
||||
});
|
||||
|
||||
test("should reset after all sources fail", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1,2001:db8::2");
|
||||
|
||||
// Mark all as failed
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.markFailed("2001:db8::2");
|
||||
pool.markFailed("2001:db8::2");
|
||||
pool.markFailed("2001:db8::2");
|
||||
|
||||
// Should auto-reset and return first
|
||||
const addr = pool.getNext();
|
||||
expect(addr).toBe("2001:db8::1");
|
||||
});
|
||||
|
||||
test("should reset failure count on success", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1,2001:db8::2");
|
||||
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.markSuccess("2001:db8::1");
|
||||
|
||||
// Should not be disabled after success
|
||||
const addr = pool.getNext();
|
||||
expect(addr).toBe("2001:db8::1");
|
||||
});
|
||||
|
||||
test("should get address by index", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1,2001:db8::2");
|
||||
|
||||
expect(pool.getAtIndex(0)).toBe("2001:db8::1");
|
||||
expect(pool.getAtIndex(1)).toBe("2001:db8::2");
|
||||
expect(pool.getAtIndex(2)).toBeNull();
|
||||
});
|
||||
|
||||
test("should handle whitespace in input", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString(" 2001:db8::1 , 2001:db8::2 ");
|
||||
|
||||
expect(pool.size).toBe(2);
|
||||
expect(pool.getNext()).toBe("2001:db8::1");
|
||||
});
|
||||
|
||||
test("should handle empty input", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("");
|
||||
expect(pool.size).toBe(0);
|
||||
});
|
||||
|
||||
test("should reset all sources", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1,2001:db8::2");
|
||||
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.markFailed("2001:db8::1");
|
||||
pool.reset();
|
||||
|
||||
expect(pool.getNext()).toBe("2001:db8::1");
|
||||
});
|
||||
|
||||
test("should change failure threshold", () => {
|
||||
const pool = new IPv6SourcePool();
|
||||
pool.loadFromString("2001:db8::1");
|
||||
pool.setFailureThreshold(1);
|
||||
|
||||
pool.markFailed("2001:db8::1");
|
||||
// With threshold 1, should be disabled after 1 failure
|
||||
// getNext should auto-reset since all failed
|
||||
const addr = pool.getNext();
|
||||
expect(addr).toBe("2001:db8::1");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user