fix(image-analysis): harden runtime and provisioning

This commit is contained in:
Tam Nhu Tran committed 2026-04-02 18:36:19 -04:00
1 parent 6d1c895721
commit 77b488f8b1
21 files changed
+811 -169

No files matched your search

@@ -0,0 +1,61 @@
import { describe, expect, it } from 'bun:test';
import { resolveImageAnalysisRuntimeConnection } from '../../../../src/utils/hooks';
describe('resolveImageAnalysisRuntimeConnection', () => {
it('returns a direct local runtime connection for local CLIProxy targets', () => {
const connection = resolveImageAnalysisRuntimeConnection({
proxyTarget: {
host: '127.0.0.1',
port: 8317,
protocol: 'http',
isRemote: false,
},
});
expect(connection.baseUrl).toBe('http://127.0.0.1:8317');
expect(connection.allowSelfSigned).toBe(false);
expect(connection.proxyTarget.isRemote).toBe(false);
});
it('returns the remote runtime base URL and TLS flag for self-signed targets', () => {
const connection = resolveImageAnalysisRuntimeConnection({
proxyTarget: {
host: 'remote.example.com',
port: 9443,
protocol: 'https',
authToken: 'remote-token',
allowSelfSigned: true,
isRemote: true,
},
});
expect(connection.baseUrl).toBe('https://remote.example.com:9443');
expect(connection.apiKey).toBe('remote-token');
expect(connection.allowSelfSigned).toBe(true);
expect(connection.proxyTarget.isRemote).toBe(true);
});
it('prefers the local tunnel when one is active for a remote target', () => {
const connection = resolveImageAnalysisRuntimeConnection({
proxyTarget: {
host: 'remote.example.com',
port: 9443,
protocol: 'https',
authToken: 'remote-token',
allowSelfSigned: true,
isRemote: true,
},
tunnelPort: 9911,
});
expect(connection.baseUrl).toBe('http://127.0.0.1:9911');
expect(connection.apiKey).toBe('remote-token');
expect(connection.allowSelfSigned).toBe(false);
expect(connection.proxyTarget).toMatchObject({
host: '127.0.0.1',
port: 9911,
protocol: 'http',
isRemote: false,
});
});
});
@@ -38,6 +38,7 @@ function createStatus(overrides: Partial<ImageAnalysisStatus> = {}): ImageAnalys
describe('image-analysis-runtime-status', () => {
it('falls back to native read when provider auth is missing', async () => {
const status = await hydrateImageAnalysisRuntimeStatus(createStatus(), {
checkRemoteProxy: async () => ({ reachable: false, error: 'not-used' }),
getProxyTarget: () => ({
host: '127.0.0.1',
port: 8317,
@@ -63,6 +64,7 @@ describe('image-analysis-runtime-status', () => {
it('marks an idle local proxy as launchable when auth is ready', async () => {
const status = await hydrateImageAnalysisRuntimeStatus(createStatus(), {
checkRemoteProxy: async () => ({ reachable: false, error: 'not-used' }),
getProxyTarget: () => ({
host: '127.0.0.1',
port: 8317,
@@ -107,7 +109,10 @@ describe('image-analysis-runtime-status', () => {
source: 'remote',
},
],
isCliproxyRunning: async () => false,
checkRemoteProxy: async () => ({
reachable: false,
error: 'Remote CLIProxy target remote.example:443 is unreachable.',
}),
});
expect(status.authReadiness).toBe('ready');
@@ -123,6 +128,7 @@ describe('image-analysis-runtime-status', () => {
reason: 'Profile hook is missing from the persisted settings file.',
}),
{
checkRemoteProxy: async () => ({ reachable: false, error: 'not-used' }),
getProxyTarget: () => ({
host: '127.0.0.1',
port: 8317,
@@ -147,4 +153,38 @@ describe('image-analysis-runtime-status', () => {
expect(status.effectiveRuntimeMode).toBe('cliproxy-image-analysis');
expect(status.effectiveRuntimeReason).toContain('Profile hook is missing');
});
it('marks a reachable remote proxy as remote instead of inspecting local 8317', async () => {
const status = await hydrateImageAnalysisRuntimeStatus(createStatus(), {
getProxyTarget: () => ({
host: 'remote.example',
port: 443,
protocol: 'https',
authToken: 'token',
managementKey: 'secret',
allowSelfSigned: true,
isRemote: true,
}),
fetchRemoteAuthStatus: async () => [
{
provider: 'ghcp',
displayName: 'GitHub Copilot (OAuth)',
authenticated: true,
tokenFiles: 1,
accounts: [],
defaultAccount: null,
source: 'remote',
},
],
checkRemoteProxy: async () => ({
reachable: true,
latencyMs: 42,
}),
isCliproxyRunning: async () => false,
});
expect(status.proxyReadiness).toBe('remote');
expect(status.proxyReason).toContain('remote.example:443');
expect(status.effectiveRuntimeMode).toBe('cliproxy-image-analysis');
});
});
@@ -1,12 +1,14 @@
import { afterEach, describe, expect, it, mock } from 'bun:test';
import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as lockfile from 'proper-lockfile';
import {
ensureImageAnalysisMcp,
getImageAnalysisMcpRuntimePath,
getImageAnalysisMcpServerName,
getImageAnalysisMcpServerPath,
hasImageAnalysisMcpReady,
uninstallImageAnalysisMcp,
} from '../../../../src/utils/image-analysis';
@@ -102,6 +104,7 @@ describe('ensureImageAnalysisMcp', () => {
expect(config.mcpServers.existing).toEqual({ command: 'uvx', args: ['some-server'] });
expect(config.mcpServers[getImageAnalysisMcpServerName()]).toEqual(getManagedConfig());
expect(hasImageAnalysisMcpReady(claudeUserConfigPath)).toBe(true);
});
it('removes the managed MCP runtime while preserving unrelated server entries', () => {
@@ -177,4 +180,14 @@ describe('ensureImageAnalysisMcp', () => {
expect(fs.existsSync(getImageAnalysisMcpServerPath())).toBe(true);
expect(fs.existsSync(getImageAnalysisMcpRuntimePath())).toBe(true);
});
it('serializes ~/.claude.json updates with a file lock', () => {
setupTempHome();
writeEnabledConfig();
const lockSpy = spyOn(lockfile, 'lockSync');
expect(ensureImageAnalysisMcp()).toBe(true);
expect(lockSpy).toHaveBeenCalled();
});
});