diff --git a/tests/unit/utils/hooks/image-analysis-runtime-status-circular-dependency.test.ts b/tests/unit/utils/hooks/image-analysis-runtime-status-circular-dependency.test.ts new file mode 100644 index 00000000..d0b669fd --- /dev/null +++ b/tests/unit/utils/hooks/image-analysis-runtime-status-circular-dependency.test.ts @@ -0,0 +1,144 @@ +import { describe, expect, it } from 'bun:test'; +import { readFileSync } from 'fs'; +import { dirname } from 'path'; +import type { ImageAnalysisStatus } from '../../../../src/utils/hooks/image-analysis-backend-resolver'; +import type { AuthStatus } from '../../../../src/cliproxy/auth-handler'; +import { + ModuleKind, + ScriptTarget, + transpileModule, + type CompilerOptions, +} from 'typescript'; + +function createStatus(overrides: Partial = {}): ImageAnalysisStatus { + return { + enabled: true, + supported: true, + status: 'active', + backendId: 'ghcp', + backendDisplayName: 'GitHub Copilot (OAuth)', + model: 'claude-haiku-4.5', + resolutionSource: 'profile-backend', + reason: null, + shouldPersistHook: true, + persistencePath: '/tmp/orq.settings.json', + runtimePath: '/api/provider/ghcp', + usesCurrentTarget: true, + usesCurrentAuthToken: true, + hookInstalled: true, + sharedHookInstalled: true, + authReadiness: 'unknown', + authProvider: 'ghcp', + authDisplayName: 'GitHub Copilot (OAuth)', + authReason: 'Auth readiness has not been verified yet.', + proxyReadiness: 'unknown', + proxyReason: 'CLIProxy runtime readiness has not been verified yet.', + effectiveRuntimeMode: 'native-read', + effectiveRuntimeReason: null, + profileModel: 'claude-haiku-4.5', + nativeReadPreference: false, + nativeImageCapable: true, + nativeImageReason: 'claude-haiku-4.5 can read images natively.', + ...overrides, + }; +} + +describe('image-analysis-runtime-status circular dependency regression', () => { + it('reads auth deps from the CommonJS module object at call time', async () => { + const authHandlerModule: { + initializeAccounts: undefined | (() => void); + getAuthStatus: undefined | ((provider: 'ghcp') => AuthStatus); + } = { + initializeAccounts: undefined, + getAuthStatus: undefined, + }; + + const sourcePath = new URL( + '../../../../src/utils/hooks/image-analysis-runtime-status.ts', + import.meta.url + ); + const source = readFileSync(sourcePath, 'utf8'); + const compilerOptions: CompilerOptions = { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES2020, + esModuleInterop: true, + }; + const transpiled = transpileModule(source, { compilerOptions }).outputText; + + const module = { exports: {} as Record }; + const requireMap: Record = { + '../../cliproxy/auth-handler': authHandlerModule, + '../../cliproxy/remote-proxy-client': { + checkRemoteProxy: async () => ({ reachable: false, error: 'not-used' }), + }, + '../../cliproxy/remote-auth-fetcher': { + fetchRemoteAuthStatus: async () => [], + }, + '../../cliproxy/proxy-target-resolver': { + getProxyTarget: () => ({ + host: '127.0.0.1', + port: 8317, + protocol: 'http', + isRemote: false, + }), + }, + '../../cliproxy/provider-capabilities': { + getProviderDisplayName: () => 'GitHub Copilot (OAuth)', + isCLIProxyProvider: () => true, + }, + '../../cliproxy/stats-fetcher': { + isCliproxyRunning: async () => true, + }, + '../../config/unified-config-types': { + DEFAULT_IMAGE_ANALYSIS_CONFIG: {}, + }, + './image-analysis-backend-resolver': { + resolveImageAnalysisStatus: () => createStatus(), + }, + }; + + const compiledModule = new Function( + 'exports', + 'require', + 'module', + '__filename', + '__dirname', + transpiled + ); + compiledModule( + module.exports, + (specifier: string) => { + const dependency = requireMap[specifier]; + if (!dependency) { + throw new Error(`Unexpected dependency: ${specifier}`); + } + return dependency; + }, + module, + sourcePath.pathname, + dirname(sourcePath.pathname) + ); + + authHandlerModule.initializeAccounts = () => {}; + authHandlerModule.getAuthStatus = () => ({ + provider: 'ghcp', + authenticated: true, + tokenDir: '/tmp/auth', + tokenFiles: ['github-copilot-test.json'], + accounts: [], + defaultAccount: undefined, + }); + + const { hydrateImageAnalysisRuntimeStatus } = module.exports as { + hydrateImageAnalysisRuntimeStatus: ( + baseStatus: ImageAnalysisStatus, + deps?: Record + ) => Promise; + }; + const status = await hydrateImageAnalysisRuntimeStatus(createStatus(), {}); + + expect(status.authReadiness).toBe('ready'); + expect(status.proxyReadiness).toBe('ready'); + expect(status.effectiveRuntimeMode).toBe('cliproxy-image-analysis'); + }); +});