basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2026 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';8import { preconnectApi, resetPreconnectState } from './apiPreconnect.js';9 10// Mock the shared dispatcher functions from core11const { mockGetOrCreateSharedDispatcher, mockDebugLogger } = vi.hoisted(() => {12 const dispatcher = { fake: 'dispatcher' };13 const mockDebugLogger = { debug: vi.fn() };14 return {15 mockGetOrCreateSharedDispatcher: vi.fn(() => dispatcher),16 mockDebugLogger,17 };18});19 20// Mock fetch. apiPreconnect.ts now uses `import { fetch } from 'undici'`21// rather than the global fetch, so we have to intercept the module export —22// vi.stubGlobal('fetch', …) would not catch the named import.23const { mockFetch } = vi.hoisted(() => ({24 mockFetch: vi.fn().mockResolvedValue(undefined),25}));26vi.mock('undici', async (importOriginal) => {27 const actual = await importOriginal<typeof import('undici')>();28 return {29 ...actual,30 fetch: mockFetch,31 };32});33vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {34 const actual =35 await importOriginal<typeof import('@qwen-code/qwen-code-core')>();36 return {37 ...actual,38 AuthType: {39 USE_OPENAI: 'openai',40 USE_ANTHROPIC: 'anthropic',41 USE_GEMINI: 'gemini',42 },43 createDebugLogger: () => mockDebugLogger,44 detectRuntime: () => 'node',45 getOrCreateSharedDispatcher: mockGetOrCreateSharedDispatcher,46 redactProxyCredentials: actual.redactProxyCredentials,47 };48});49 50describe('apiPreconnect', () => {51 beforeEach(() => {52 resetPreconnectState();53 mockFetch.mockClear();54 mockFetch.mockResolvedValue(undefined);55 mockGetOrCreateSharedDispatcher.mockClear();56 mockDebugLogger.debug.mockClear();57 delete process.env['HTTPS_PROXY'];58 delete process.env['https_proxy'];59 delete process.env['HTTP_PROXY'];60 delete process.env['http_proxy'];61 delete process.env['QWEN_CODE_DISABLE_PRECONNECT'];62 delete process.env['NODE_EXTRA_CA_CERTS'];63 delete process.env['SANDBOX'];64 });65 66 afterEach(() => {67 vi.restoreAllMocks();68 });69 70 describe('shouldSkipPreconnect', () => {71 it('should skip when NODE_EXTRA_CA_CERTS is set', () => {72 process.env['NODE_EXTRA_CA_CERTS'] = '/path/to/ca.pem';73 preconnectApi('qwen-oauth', { proxy: 'http://proxy.example.com:8080' });74 expect(mockFetch).not.toHaveBeenCalled();75 });76 });77 78 describe('resolvedBaseUrl handling', () => {79 it('should use resolvedBaseUrl when it is a default URL', () => {80 preconnectApi('openai', {81 resolvedBaseUrl: 'https://api.openai.com/v1',82 proxy: 'http://proxy.example.com:8080',83 });84 expect(mockFetch).toHaveBeenCalledWith(85 'https://api.openai.com/v1',86 expect.objectContaining({ method: 'HEAD' }),87 );88 });89 90 it('should skip when resolvedBaseUrl is a custom (non-default) URL', () => {91 preconnectApi('openai', {92 resolvedBaseUrl: 'https://custom.api.com/v1',93 proxy: 'http://proxy.example.com:8080',94 });95 expect(mockFetch).not.toHaveBeenCalled();96 });97 98 it('should skip when resolvedBaseUrl is a subdomain-spoofed URL', () => {99 preconnectApi('openai', {100 resolvedBaseUrl: 'https://api.openai.com.malicious.com/v1',101 proxy: 'http://proxy.example.com:8080',102 });103 expect(mockFetch).not.toHaveBeenCalled();104 });105 106 it('should use resolvedBaseUrl when it is a dashscope compatible-mode URL', () => {107 preconnectApi('openai', {108 resolvedBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',109 proxy: 'http://proxy.example.com:8080',110 });111 expect(mockFetch).toHaveBeenCalledWith(112 'https://dashscope.aliyuncs.com/compatible-mode/v1',113 expect.objectContaining({ method: 'HEAD' }),114 );115 });116 117 it('should skip when resolvedBaseUrl is a dashscope subdomain-spoofed URL', () => {118 preconnectApi('openai', {119 resolvedBaseUrl: 'https://dashscope.aliyuncs.com.malicious.com/v1',120 proxy: 'http://proxy.example.com:8080',121 });122 expect(mockFetch).not.toHaveBeenCalled();123 });124 125 it('should accept DashScope regional endpoint (sg-singapore)', () => {126 preconnectApi('openai', {127 resolvedBaseUrl:128 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',129 proxy: 'http://proxy.example.com:8080',130 });131 expect(mockFetch).toHaveBeenCalledWith(132 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',133 expect.objectContaining({ method: 'HEAD' }),134 );135 });136 137 it('should accept DashScope regional endpoint (us-virginia)', () => {138 preconnectApi('openai', {139 resolvedBaseUrl: 'https://dashscope-us.aliyuncs.com/compatible-mode/v1',140 proxy: 'http://proxy.example.com:8080',141 });142 expect(mockFetch).toHaveBeenCalledWith(143 'https://dashscope-us.aliyuncs.com/compatible-mode/v1',144 expect.objectContaining({ method: 'HEAD' }),145 );146 });147 148 it('should accept DashScope regional endpoint (cn-hongkong)', () => {149 preconnectApi('openai', {150 resolvedBaseUrl:151 'https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1',152 proxy: 'http://proxy.example.com:8080',153 });154 expect(mockFetch).toHaveBeenCalledWith(155 'https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1',156 expect.objectContaining({ method: 'HEAD' }),157 );158 });159 160 it('should fall back to authType default when resolvedBaseUrl is a non-URL sentinel', () => {161 preconnectApi('qwen-oauth', {162 resolvedBaseUrl: 'DYNAMIC_QWEN_OAUTH_BASE_URL',163 proxy: 'http://proxy.example.com:8080',164 });165 expect(mockFetch).toHaveBeenCalledWith(166 'https://coding.dashscope.aliyuncs.com',167 expect.objectContaining({ method: 'HEAD' }),168 );169 });170 171 it('should fall back to default URL when resolvedBaseUrl is undefined', () => {172 preconnectApi('qwen-oauth', {173 proxy: 'http://proxy.example.com:8080',174 });175 expect(mockFetch).toHaveBeenCalledWith(176 'https://coding.dashscope.aliyuncs.com',177 expect.objectContaining({ method: 'HEAD' }),178 );179 });180 });181 182 describe('preconnect behavior', () => {183 it('should use default baseUrl for qwen-oauth', () => {184 preconnectApi('qwen-oauth', {185 proxy: 'http://proxy.example.com:8080',186 });187 expect(mockFetch).toHaveBeenCalledWith(188 'https://coding.dashscope.aliyuncs.com',189 expect.objectContaining({ method: 'HEAD' }),190 );191 });192 193 it('should use default baseUrl for openai', () => {194 preconnectApi('openai', {195 proxy: 'http://proxy.example.com:8080',196 });197 expect(mockFetch).toHaveBeenCalledWith(198 'https://api.openai.com',199 expect.objectContaining({ method: 'HEAD' }),200 );201 });202 203 it('should use default baseUrl for anthropic', () => {204 preconnectApi('anthropic', {205 proxy: 'http://proxy.example.com:8080',206 });207 expect(mockFetch).toHaveBeenCalledWith(208 'https://api.anthropic.com',209 expect.objectContaining({ method: 'HEAD' }),210 );211 });212 213 it('should pass shared dispatcher on Node.js runtime', () => {214 preconnectApi('qwen-oauth', {215 proxy: 'http://proxy.example.com:8080',216 });217 expect(mockFetch).toHaveBeenCalledWith(218 expect.any(String),219 expect.objectContaining({220 dispatcher: { fake: 'dispatcher' },221 }),222 );223 });224 225 it('should pass configured proxy to shared dispatcher', () => {226 preconnectApi('qwen-oauth', {227 proxy: 'http://proxy.example.com:8080',228 });229 expect(mockGetOrCreateSharedDispatcher).toHaveBeenCalledWith(230 'http://proxy.example.com:8080',231 );232 });233 234 it('should not fire twice', () => {235 preconnectApi('qwen-oauth', {236 proxy: 'http://proxy.example.com:8080',237 });238 preconnectApi('openai', { proxy: 'http://proxy.example.com:8080' });239 expect(mockFetch).toHaveBeenCalledTimes(1);240 });241 242 it('should retry when targetUrl was unavailable on first call', () => {243 // First call: unknown authType, no resolvedBaseUrl → no targetUrl244 preconnectApi('unknown-auth', {245 proxy: 'http://proxy.example.com:8080',246 });247 expect(mockFetch).not.toHaveBeenCalled();248 249 // Second call: valid authType → should fire250 preconnectApi('qwen-oauth', {251 proxy: 'http://proxy.example.com:8080',252 });253 expect(mockFetch).toHaveBeenCalledTimes(1);254 expect(mockFetch).toHaveBeenCalledWith(255 'https://coding.dashscope.aliyuncs.com',256 expect.objectContaining({ method: 'HEAD' }),257 );258 });259 260 it('should skip dispatcher creation when no proxy configured', () => {261 preconnectApi('qwen-oauth');262 expect(mockFetch).not.toHaveBeenCalled();263 expect(mockGetOrCreateSharedDispatcher).not.toHaveBeenCalled();264 expect(mockDebugLogger.debug).toHaveBeenCalledWith(265 'Skipping preconnect dispatcher: no proxy configured',266 );267 });268 269 it('should allow a later proxy preconnect after a no-proxy skip', () => {270 // First call: no proxy, no useful undici pool to warm.271 preconnectApi('qwen-oauth');272 expect(mockFetch).not.toHaveBeenCalled();273 274 // Second call: proxy is now available, so preconnect should still fire.275 preconnectApi('qwen-oauth', { proxy: 'http://proxy.example.com:8080' });276 expect(mockFetch).toHaveBeenCalledTimes(1);277 expect(mockGetOrCreateSharedDispatcher).toHaveBeenCalledWith(278 'http://proxy.example.com:8080',279 );280 });281 282 it('should handle fetch errors gracefully', async () => {283 mockFetch.mockRejectedValue(new Error('Network error'));284 // Should not throw285 expect(() =>286 preconnectApi('qwen-oauth', { proxy: 'http://proxy.example.com:8080' }),287 ).not.toThrow();288 });289 290 it('should redact proxy credentials from async fetch errors', async () => {291 mockFetch.mockRejectedValue(292 new Error('connect ECONNREFUSED token@proxy.local:8080'),293 );294 preconnectApi('qwen-oauth', {295 proxy: 'http://token@proxy.local:8080',296 });297 298 await Promise.resolve();299 await Promise.resolve();300 301 expect(mockDebugLogger.debug).toHaveBeenCalledWith(302 'Preconnect failed (ignored): Error: connect ECONNREFUSED <redacted>@proxy.local:8080',303 );304 expect(mockDebugLogger.debug).not.toHaveBeenCalledWith(305 expect.stringContaining('token@'),306 );307 });308 309 it('should handle synchronous dispatcher errors gracefully', () => {310 mockGetOrCreateSharedDispatcher.mockImplementation(() => {311 throw new Error('Failed to create dispatcher');312 });313 expect(() =>314 preconnectApi('qwen-oauth', { proxy: 'http://proxy.example.com:8080' }),315 ).not.toThrow();316 });317 318 it('should redact proxy credentials from synchronous dispatcher errors', () => {319 mockGetOrCreateSharedDispatcher.mockImplementation(() => {320 throw new Error('connect ECONNREFUSED user:pass@proxy.local:8080');321 });322 323 preconnectApi('qwen-oauth', {324 proxy: 'http://user:pass@proxy.local:8080',325 });326 327 expect(mockDebugLogger.debug).toHaveBeenCalledWith(328 'Preconnect failed (ignored): Error: connect ECONNREFUSED <redacted>@proxy.local:8080',329 );330 expect(mockDebugLogger.debug).not.toHaveBeenCalledWith(331 expect.stringContaining('user:pass@'),332 );333 });334 335 it('should skip when QWEN_CODE_DISABLE_PRECONNECT is set', () => {336 process.env['QWEN_CODE_DISABLE_PRECONNECT'] = '1';337 preconnectApi('qwen-oauth', { proxy: 'http://proxy.example.com:8080' });338 expect(mockFetch).not.toHaveBeenCalled();339 });340 341 it('should skip in sandbox mode', () => {342 process.env['SANDBOX'] = '1';343 preconnectApi('qwen-oauth', { proxy: 'http://proxy.example.com:8080' });344 expect(mockFetch).not.toHaveBeenCalled();345 });346 });347});348 