|
| 1 | +import { describe, it, expect } from "vitest"; |
| 2 | +import { NovadaAdapter } from "../adapters/novada.js"; |
| 3 | +import { BrightDataAdapter } from "../adapters/brightdata.js"; |
| 4 | +import { SmartproxyAdapter } from "../adapters/smartproxy.js"; |
| 5 | +import { OxylabsAdapter } from "../adapters/oxylabs.js"; |
| 6 | +import { GenericHttpAdapter } from "../adapters/generic.js"; |
| 7 | +import { resolveAdapter, listAdapters } from "../adapters/index.js"; |
| 8 | +// ─── Novada ─────────────────────────────────────────────── |
| 9 | +describe("NovadaAdapter", () => { |
| 10 | + it("returns null when credentials missing", () => { |
| 11 | + expect(NovadaAdapter.loadCredentials({})).toBeNull(); |
| 12 | + expect(NovadaAdapter.loadCredentials({ NOVADA_PROXY_USER: "u" })).toBeNull(); |
| 13 | + expect(NovadaAdapter.loadCredentials({ NOVADA_PROXY_PASS: "p" })).toBeNull(); |
| 14 | + }); |
| 15 | + it("loads credentials with defaults", () => { |
| 16 | + const creds = NovadaAdapter.loadCredentials({ |
| 17 | + NOVADA_PROXY_USER: "user1", |
| 18 | + NOVADA_PROXY_PASS: "pass1", |
| 19 | + }); |
| 20 | + expect(creds).toEqual({ |
| 21 | + user: "user1", |
| 22 | + pass: "pass1", |
| 23 | + host: "super.novada.pro", |
| 24 | + port: "7777", |
| 25 | + }); |
| 26 | + }); |
| 27 | + it("respects custom host and port", () => { |
| 28 | + const creds = NovadaAdapter.loadCredentials({ |
| 29 | + NOVADA_PROXY_USER: "u", |
| 30 | + NOVADA_PROXY_PASS: "p", |
| 31 | + NOVADA_PROXY_HOST: "custom.host", |
| 32 | + NOVADA_PROXY_PORT: "9999", |
| 33 | + }); |
| 34 | + expect(creds.host).toBe("custom.host"); |
| 35 | + expect(creds.port).toBe("9999"); |
| 36 | + }); |
| 37 | + it("falls back to default port for invalid values", () => { |
| 38 | + for (const bad of ["0", "-1", "99999", "abc", ""]) { |
| 39 | + const creds = NovadaAdapter.loadCredentials({ |
| 40 | + NOVADA_PROXY_USER: "u", |
| 41 | + NOVADA_PROXY_PASS: "p", |
| 42 | + NOVADA_PROXY_PORT: bad, |
| 43 | + }); |
| 44 | + expect(creds.port).toBe("7777"); |
| 45 | + } |
| 46 | + }); |
| 47 | + it("builds proxy URL with region/city/session", () => { |
| 48 | + const creds = { user: "user1", pass: "p@ss", host: "h.com", port: "7777" }; |
| 49 | + const url = NovadaAdapter.buildProxyUrl(creds, { |
| 50 | + country: "US", |
| 51 | + city: "NewYork", |
| 52 | + session_id: "abc123", |
| 53 | + }); |
| 54 | + expect(url).toContain("user1-zone-res-region-us-city-newyork-session-abc123"); |
| 55 | + expect(url).toContain(encodeURIComponent("p@ss")); |
| 56 | + expect(url).toContain("h.com:7777"); |
| 57 | + }); |
| 58 | + it("builds proxy URL without targeting params", () => { |
| 59 | + const creds = { user: "user1", pass: "pass1", host: "h.com", port: "7777" }; |
| 60 | + const url = NovadaAdapter.buildProxyUrl(creds, {}); |
| 61 | + expect(url).toBe("http://user1-zone-res:pass1@h.com:7777"); |
| 62 | + }); |
| 63 | +}); |
| 64 | +// ─── BrightData ─────────────────────────────────────────── |
| 65 | +describe("BrightDataAdapter", () => { |
| 66 | + it("returns null when credentials missing", () => { |
| 67 | + expect(BrightDataAdapter.loadCredentials({})).toBeNull(); |
| 68 | + }); |
| 69 | + it("loads credentials with defaults", () => { |
| 70 | + const creds = BrightDataAdapter.loadCredentials({ |
| 71 | + BRIGHTDATA_USER: "brd-customer-abc", |
| 72 | + BRIGHTDATA_PASS: "pass", |
| 73 | + }); |
| 74 | + expect(creds.host).toBe("zproxy.lum-superproxy.io"); |
| 75 | + expect(creds.port).toBe("22225"); |
| 76 | + }); |
| 77 | + it("uses -country- for country and -sid- for session", () => { |
| 78 | + const creds = { user: "brd-abc", pass: "p", host: "h", port: "22225" }; |
| 79 | + const url = BrightDataAdapter.buildProxyUrl(creds, { |
| 80 | + country: "US", |
| 81 | + session_id: "s1", |
| 82 | + }); |
| 83 | + expect(url).toContain("brd-abc-country-us"); |
| 84 | + expect(url).toContain("-sid-s1"); |
| 85 | + expect(url).not.toContain("-session-"); |
| 86 | + }); |
| 87 | +}); |
| 88 | +// ─── Smartproxy ─────────────────────────────────────────── |
| 89 | +describe("SmartproxyAdapter", () => { |
| 90 | + it("returns null when credentials missing", () => { |
| 91 | + expect(SmartproxyAdapter.loadCredentials({})).toBeNull(); |
| 92 | + }); |
| 93 | + it("loads defaults correctly", () => { |
| 94 | + const creds = SmartproxyAdapter.loadCredentials({ |
| 95 | + SMARTPROXY_USER: "u", |
| 96 | + SMARTPROXY_PASS: "p", |
| 97 | + }); |
| 98 | + expect(creds.host).toBe("gate.smartproxy.com"); |
| 99 | + expect(creds.port).toBe("10001"); |
| 100 | + }); |
| 101 | + it("uppercases country code", () => { |
| 102 | + const creds = { user: "u", pass: "p", host: "h", port: "10001" }; |
| 103 | + const url = SmartproxyAdapter.buildProxyUrl(creds, { country: "us" }); |
| 104 | + expect(url).toContain("-country-US"); |
| 105 | + }); |
| 106 | +}); |
| 107 | +// ─── Oxylabs ────────────────────────────────────────────── |
| 108 | +describe("OxylabsAdapter", () => { |
| 109 | + it("returns null when credentials missing", () => { |
| 110 | + expect(OxylabsAdapter.loadCredentials({})).toBeNull(); |
| 111 | + }); |
| 112 | + it("loads defaults correctly", () => { |
| 113 | + const creds = OxylabsAdapter.loadCredentials({ |
| 114 | + OXYLABS_USER: "u", |
| 115 | + OXYLABS_PASS: "p", |
| 116 | + }); |
| 117 | + expect(creds.host).toBe("pr.oxylabs.io"); |
| 118 | + expect(creds.port).toBe("7777"); |
| 119 | + }); |
| 120 | + it("uses -cc- for country (uppercase) and -sessid- for session", () => { |
| 121 | + const creds = { user: "u", pass: "p", host: "h", port: "7777" }; |
| 122 | + const url = OxylabsAdapter.buildProxyUrl(creds, { |
| 123 | + country: "de", |
| 124 | + session_id: "sess1", |
| 125 | + }); |
| 126 | + expect(url).toContain("-cc-DE"); |
| 127 | + expect(url).toContain("-sessid-sess1"); |
| 128 | + }); |
| 129 | +}); |
| 130 | +// ─── Generic ────────────────────────────────────────────── |
| 131 | +describe("GenericHttpAdapter", () => { |
| 132 | + it("returns null when PROXY_URL not set", () => { |
| 133 | + expect(GenericHttpAdapter.loadCredentials({})).toBeNull(); |
| 134 | + }); |
| 135 | + it("returns null for non-http schemes", () => { |
| 136 | + expect(GenericHttpAdapter.loadCredentials({ PROXY_URL: "ftp://host" })).toBeNull(); |
| 137 | + expect(GenericHttpAdapter.loadCredentials({ PROXY_URL: "socks5://host" })).toBeNull(); |
| 138 | + }); |
| 139 | + it("returns null for malformed URL", () => { |
| 140 | + expect(GenericHttpAdapter.loadCredentials({ PROXY_URL: "not-a-url" })).toBeNull(); |
| 141 | + }); |
| 142 | + it("parses a valid PROXY_URL", () => { |
| 143 | + const creds = GenericHttpAdapter.loadCredentials({ |
| 144 | + PROXY_URL: "http://user:pass@host.com:8080", |
| 145 | + }); |
| 146 | + expect(creds.user).toBe("user"); |
| 147 | + expect(creds.pass).toBe("pass"); |
| 148 | + expect(creds.proxyUrl).toBe("http://user:pass@host.com:8080"); |
| 149 | + }); |
| 150 | + it("returns the URL as-is from buildProxyUrl, ignoring params", () => { |
| 151 | + const creds = { proxyUrl: "http://u:p@h:1", user: "u", pass: "p" }; |
| 152 | + const url = GenericHttpAdapter.buildProxyUrl(creds, { country: "US", city: "LA" }); |
| 153 | + expect(url).toBe("http://u:p@h:1"); |
| 154 | + }); |
| 155 | + it("has proxyUrl in sensitiveFields", () => { |
| 156 | + expect(GenericHttpAdapter.sensitiveFields).toContain("proxyUrl"); |
| 157 | + }); |
| 158 | +}); |
| 159 | +// ─── Registry / resolveAdapter ──────────────────────────── |
| 160 | +describe("resolveAdapter", () => { |
| 161 | + it("returns Novada when both Novada and Generic are set", () => { |
| 162 | + const result = resolveAdapter({ |
| 163 | + NOVADA_PROXY_USER: "u", |
| 164 | + NOVADA_PROXY_PASS: "p", |
| 165 | + PROXY_URL: "http://u:p@h:1", |
| 166 | + }); |
| 167 | + expect(result.adapter.name).toBe("novada"); |
| 168 | + }); |
| 169 | + it("returns BrightData over Generic when both set", () => { |
| 170 | + const result = resolveAdapter({ |
| 171 | + BRIGHTDATA_USER: "u", |
| 172 | + BRIGHTDATA_PASS: "p", |
| 173 | + PROXY_URL: "http://u:p@h:1", |
| 174 | + }); |
| 175 | + expect(result.adapter.name).toBe("brightdata"); |
| 176 | + }); |
| 177 | + it("returns Generic as last resort", () => { |
| 178 | + const result = resolveAdapter({ |
| 179 | + PROXY_URL: "http://u:p@h:1", |
| 180 | + }); |
| 181 | + expect(result.adapter.name).toBe("generic"); |
| 182 | + }); |
| 183 | + it("returns null when nothing is configured", () => { |
| 184 | + expect(resolveAdapter({})).toBeNull(); |
| 185 | + }); |
| 186 | + it("Novada is always first in adapter list", () => { |
| 187 | + const adapters = listAdapters(); |
| 188 | + expect(adapters[0].name).toBe("novada"); |
| 189 | + }); |
| 190 | + it("Generic is always last in adapter list", () => { |
| 191 | + const adapters = listAdapters(); |
| 192 | + expect(adapters[adapters.length - 1].name).toBe("generic"); |
| 193 | + }); |
| 194 | +}); |
0 commit comments