From 6380165a94568e82ee94d830de8cc8bff8d17d67 Mon Sep 17 00:00:00 2001 From: Bojan Kucera Date: Sun, 8 Jun 2025 00:01:27 -0400 Subject: [PATCH] well seems like no socks on bun --- .../src/services/proxy.service.ts | 75 +++++++-------- bun.lock | 4 +- libs/http/package.json | 2 +- libs/http/src/client.ts | 95 +++++++++---------- libs/http/src/proxy-manager.ts | 67 +++++-------- libs/http/test/http.test.ts | 10 +- 6 files changed, 116 insertions(+), 137 deletions(-) diff --git a/apps/data-service/src/services/proxy.service.ts b/apps/data-service/src/services/proxy.service.ts index b11a9d8..9dedc59 100644 --- a/apps/data-service/src/services/proxy.service.ts +++ b/apps/data-service/src/services/proxy.service.ts @@ -11,45 +11,46 @@ export class ProxyService { private readonly CACHE_KEY = 'proxy'; private readonly CACHE_TTL = 86400; // 24 hours private readonly CHECK_TIMEOUT = 7000; + private readonly CHECK_IP = '99.246.102.205' private readonly CHECK_URL = 'https://proxy-detection.stare.gg/?api_key=bd406bf53ddc6abe1d9de5907830a955'; private readonly PROXY_SOURCES = [ - {url: 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/refs/heads/master/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/refs/heads/master/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/casa-ls/proxy-list/refs/heads/main/http',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/databay-labs/free-proxy-list/refs/heads/master/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/http.txt', protocol: 'http' }, - {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/http',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/http.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/proxies.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/refs/heads/master/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/refs/heads/master/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/casa-ls/proxy-list/refs/heads/main/http',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/databay-labs/free-proxy-list/refs/heads/master/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/http.txt', protocol: 'http' }, + // {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/http',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/http.txt',protocol: 'http', }, + // {url: 'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/proxies.txt',protocol: 'http', }, - {url: 'https://raw.githubusercontent.com/r00tee/Proxy-List/refs/heads/main/Https.txt',protocol: 'https', }, - {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/https.txt',protocol: 'https', }, - {url: 'https://github.com/vakhov/fresh-proxy-list/blob/master/https.txt', protocol: 'https' }, - {url: 'https://raw.githubusercontent.com/databay-labs/free-proxy-list/refs/heads/master/https.txt',protocol: 'https', }, - // {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks4.txt',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks4.txt',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks4.txt',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks4',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks4.txt',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/socks4.txt',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks4.txt',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks4.txt',protocol: 'socks4', }, - // {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks5.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks5.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks5.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks5.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks5',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks5.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks5.txt',protocol: 'socks5', }, - // {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/socks5.txt',protocol: 'socks5', }, + // {url: 'https://raw.githubusercontent.com/r00tee/Proxy-List/refs/heads/main/Https.txt',protocol: 'https', }, + // {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/https.txt',protocol: 'https', }, + // {url: 'https://github.com/vakhov/fresh-proxy-list/blob/master/https.txt', protocol: 'https' }, + // {url: 'https://raw.githubusercontent.com/databay-labs/free-proxy-list/refs/heads/master/https.txt',protocol: 'https', }, + {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks4.txt',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks4.txt',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks4.txt',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks4',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks4.txt',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/socks4.txt',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks4.txt',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks4.txt',protocol: 'socks4', }, + {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks5.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks5.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks5.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks5.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks5',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks5.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks5.txt',protocol: 'socks5', }, + {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/socks5.txt',protocol: 'socks5', }, ] constructor() { @@ -157,7 +158,7 @@ export class ProxyService { responseTime: response.responseTime, }; - if (isWorking) { + if (isWorking && !response.data.includes('CHECK_IP')) { await this.cache.set(`${this.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`, result, this.CACHE_TTL); } else { await this.cache.del(`${this.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`); diff --git a/bun.lock b/bun.lock index 67f05dc..4a6cc83 100644 --- a/bun.lock +++ b/bun.lock @@ -213,7 +213,7 @@ "dependencies": { "@stock-bot/logger": "*", "@stock-bot/types": "*", - "got": "^14.4.7", + "axios": "^1.9.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "proxy-agent": "^6.5.0", @@ -952,6 +952,8 @@ "autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="], + "axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="], + "b4a": ["b4a@1.6.7", "", {}, "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], diff --git a/libs/http/package.json b/libs/http/package.json index 9fb65e6..7662a1e 100644 --- a/libs/http/package.json +++ b/libs/http/package.json @@ -18,7 +18,7 @@ "dependencies": { "@stock-bot/logger": "*", "@stock-bot/types": "*", - "got": "^14.4.7", + "axios": "^1.9.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "proxy-agent": "^6.5.0", diff --git a/libs/http/src/client.ts b/libs/http/src/client.ts index 1d5e67b..0f29f99 100644 --- a/libs/http/src/client.ts +++ b/libs/http/src/client.ts @@ -6,7 +6,7 @@ import type { } from './types.js'; import { HttpError } from './types.js'; import { ProxyManager } from './proxy-manager.js'; -import got from 'got'; +import axios, { type AxiosResponse, AxiosError } from 'axios'; import { clear } from 'node:console'; export class HttpClient { @@ -51,8 +51,7 @@ export class HttpClient { hasProxy: !!finalConfig.proxy }); - try { - // Single decision point for proxy type - only request-level proxy + try { // Single decision point for proxy type - only request-level proxy const proxy = finalConfig.proxy; const useBunFetch = !proxy || ProxyManager.shouldUseBunFetch(proxy); @@ -92,12 +91,10 @@ export class HttpClient { controller.abort(); reject(new HttpError(`Request timeout after ${timeout}ms`)); }, timeout); - }); - - // Create request promise (don't await here!) + }); // Create request promise (don't await here!) const requestPromise = useBunFetch ? this.fetchRequest(config, controller.signal) - : this.gotRequest(config, controller.signal); + : this.axiosRequest(config, controller.signal); try { // Race the promises @@ -125,12 +122,9 @@ export class HttpClient { */ private async fetchRequest(config: RequestConfig, signal: AbortSignal): Promise> { try { - - const options = this.buildFetchOptions(config, signal); - // console.log('Using fetch with proxy:', config.proxy); - // const options = config.proxy? { proxy: config.proxy.protocol + '://' + config.proxy?.host + ':' + config.proxy?.port } : {}//this.buildGotOptions(config, signal); + const options = this.buildFetchOptions(config, signal); - this.logger?.debug('Making request with fetch: ', { url: config.url, options }) + this.logger?.debug('Making request with fetch: ', { url: config.url, options }) const response = await fetch(config.url, options); @@ -141,27 +135,44 @@ export class HttpClient { : new HttpError(`Request failed: ${(error as Error).message}`); } } /** - * Got implementation (simplified for SOCKS proxies) + * Axios implementation (for SOCKS proxies) */ - private async gotRequest(config: RequestConfig, signal: AbortSignal): Promise> { + private async axiosRequest(config: RequestConfig, signal: AbortSignal): Promise> { if(config.proxy) { try { - const gotClient = await ProxyManager.createGotInstance(config.proxy); - const response = await gotClient.get(config.url); - return this.parseGotResponse(response); + const axiosProxy = await ProxyManager.createAxiosConfig(config.proxy); + axiosProxy.url = config.url; + axiosProxy.method = config.method || 'GET'; + // console.log(axiosProxy) + // const axiosConfig = { + // ...axiosProxy, + // url: config.url, + // method: config.method || 'GET', + // // headers: config.headers || {}, + // // data: config.body, + // // signal, // Axios supports AbortSignal + // }; + + // console.log('Making request with Axios: ', axiosConfig ); + + const response: AxiosResponse = await axios.request(axiosProxy); + return this.parseAxiosResponse(response); } catch (error) { - console.error('Got request error:', error); - // Handle both AbortSignal timeout and Got-specific timeout errors + console.error('Axios request error:', error); + + // Handle AbortSignal timeout if (signal.aborted) { throw new HttpError(`Request timeout`); } - if ((error as any).name === 'TimeoutError') { + + // Handle Axios timeout errors + if (error instanceof AxiosError && error.code === 'ECONNABORTED') { throw new HttpError(`Request timeout`); } throw new HttpError(`Request failed: ${(error as Error).message}`); } - }else{ + } else { throw new HttpError(`Request failed: No proxy configured, use fetch instead`); } } @@ -188,46 +199,34 @@ export class HttpClient { // Add proxy (HTTP/HTTPS only) - request level only if (config.proxy && ProxyManager.shouldUseBunFetch(config.proxy)) { - (options as any).proxy = ProxyManager.createBunProxyUrl(config.proxy); + (options as any).proxy = ProxyManager.createProxyUrl(config.proxy); } return options; } /** - * Build Got options (extracted for clarity) + * Build Axios options (for reference, though we're creating instance in ProxyManager) */ - private buildGotOptions(config: RequestConfig, signal: AbortSignal): any { + private buildAxiosOptions(config: RequestConfig, signal: AbortSignal): any { const options: any = { method: config.method || 'GET', headers: config.headers || {}, - signal, // Use AbortSignal instead of Got's timeout - retry: { - limit: 3, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] - }, - throwHttpErrors: false, - responseType: 'json' + signal, // Axios supports AbortSignal + timeout: config.timeout || 30000, + maxRedirects: 5, + validateStatus: () => true // Don't throw on HTTP errors }; // Add body if (config.body && config.method !== 'GET') { if (typeof config.body === 'object') { - options.json = config.body; + options.data = config.body; + options.headers = { 'Content-Type': 'application/json', ...options.headers }; } else { - options.body = config.body; + options.data = config.body; options.headers = { 'Content-Type': 'text/plain', ...options.headers }; } } - // Add SOCKS proxy via agent - request level only - if (config.proxy && !ProxyManager.shouldUseBunFetch(config.proxy)) { - ProxyManager.validateConfig(config.proxy); - const agent = ProxyManager.createGotAgent(config.proxy); - options.agent = { - http: agent, - https: agent - }; - } - return options; } @@ -248,15 +247,14 @@ export class HttpClient { return { data, status: response.status, headers, ok: response.ok }; } - /** - * Parse Got response (simplified) + * Parse Axios response */ - private parseGotResponse(response: any): HttpResponse { + private parseAxiosResponse(response: AxiosResponse): HttpResponse { const headers = response.headers as Record; - const status = response.statusCode; + const status = response.status; const ok = status >= 200 && status < 300; - const data = response.body as T; + const data = response.data; if (!ok) { throw new HttpError( @@ -268,7 +266,6 @@ export class HttpClient { return { data, status, headers, ok }; } - /** * Unified body parsing (works for fetch response) */ diff --git a/libs/http/src/proxy-manager.ts b/libs/http/src/proxy-manager.ts index 5133f6e..2a92d8e 100644 --- a/libs/http/src/proxy-manager.ts +++ b/libs/http/src/proxy-manager.ts @@ -1,4 +1,4 @@ -import got from 'got'; +import axios, { AxiosRequestConfig, type AxiosInstance } from 'axios'; import { SocksProxyAgent } from 'socks-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { HttpProxyAgent } from 'http-proxy-agent'; @@ -6,18 +6,19 @@ import type { ProxyInfo } from './types.js'; export class ProxyManager { /** - * Determine if we should use Bun fetch (HTTP/HTTPS) or Got (SOCKS) + * Determine if we should use Bun fetch (HTTP/HTTPS) or Axios (SOCKS) */ static shouldUseBunFetch(proxy: ProxyInfo): boolean { return proxy.protocol === 'http' || proxy.protocol === 'https'; } - /** - * Create Bun fetch proxy URL for HTTP/HTTPS proxies + * Create proxy URL for both Bun fetch and Axios proxy agents */ - static createBunProxyUrl(proxy: ProxyInfo): string { + static createProxyUrl(proxy: ProxyInfo): string { const { protocol, host, port, username, password } = proxy; - + if(protocol.includes('socks')) { + return `${protocol}://${host}:${port}`; + } if (username && password) { return `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`; } @@ -25,13 +26,13 @@ export class ProxyManager { } /** - * Create appropriate agent for Got based on proxy type + * Create appropriate agent for Axios based on proxy type */ - static createGotAgent(proxy: ProxyInfo) { + static createProxyAgent(proxy: ProxyInfo) { this.validateConfig(proxy); - const proxyUrl = this.buildProxyUrl(proxy); - + const proxyUrl = this.createProxyUrl(proxy); + console.log(`Using proxy url: ${proxyUrl}`); switch (proxy.protocol) { case 'socks4': case 'socks5': @@ -45,42 +46,22 @@ export class ProxyManager { throw new Error(`Unsupported proxy protocol: ${proxy.protocol}`); } } - /** - * Create Got instance with proxy configuration + * Create Axios instance with proxy configuration */ - static async createGotInstance(proxy: ProxyInfo): Promise { - const agent = this.createGotAgent(proxy); - await new Promise(r => setTimeout(r, 2000)); - return got.extend({ - http2: false, - agent: { - http: agent, - https: agent - }, - timeout: { - request: 30000, - connect: 10000 - }, - retry: { - limit: 3, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] - }, - throwHttpErrors: false // We'll handle errors ourselves - }); + static createAxiosConfig(proxy: ProxyInfo): AxiosRequestConfig { + const agent = this.createProxyAgent(proxy); + return { + httpAgent: agent, + httpsAgent: agent, + // timeout: 30000, + // validateStatus: () => true, // Don't throw errors on HTTP status codes + // maxRedirects: 5, + // headers: { + // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + // } + }; } - - private static buildProxyUrl(proxy: ProxyInfo): string { - const { protocol, host, port, username, password } = proxy; - if( protocol.includes('socks') ) { - return `socks5://${encodeURIComponent('me@bojancode.com')}:${encodeURIComponent('re$Pon7dipR')}@atlanta.us.socks.nordhold.net:1080`; - } - if (username && password) { - return `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`; - } - return `${protocol}://${host}:${port}`; - } - /** * Simple proxy config validation */ diff --git a/libs/http/test/http.test.ts b/libs/http/test/http.test.ts index b3db1ed..47eaf92 100644 --- a/libs/http/test/http.test.ts +++ b/libs/http/test/http.test.ts @@ -141,10 +141,9 @@ describe('ProxyManager', () => { host: 'proxy.example.com', port: 8080, username: 'user', - password: 'pass' - }; + password: 'pass' }; - const proxyUrl = ProxyManager.createBunProxyUrl(proxy); + const proxyUrl = ProxyManager.createProxyUrl(proxy); expect(proxyUrl).toBe('http://user:pass@proxy.example.com:8080'); }); @@ -152,10 +151,9 @@ describe('ProxyManager', () => { const proxy: ProxyInfo = { protocol: 'https', host: 'proxy.example.com', - port: 8080 - }; + port: 8080 }; - const proxyUrl = ProxyManager.createBunProxyUrl(proxy); + const proxyUrl = ProxyManager.createProxyUrl(proxy); expect(proxyUrl).toBe('https://proxy.example.com:8080'); }); });