Files
claude-mem/tests/services/sync/chroma-mcp-manager-ssl.test.ts
T
Vincent Leraitre 237a4c37f8 fix: always pass --ssl flag to chroma-mcp in remote mode (#1286)
* fix: always pass --ssl flag to chroma-mcp in remote mode

The chroma-mcp CLI defaults to SSL when using --client-type http.
When CLAUDE_MEM_CHROMA_SSL is false (the common case for local
ChromaDB servers), buildCommandArgs() omitted --ssl entirely,
causing chroma-mcp to attempt an SSL connection to a plain HTTP
server and fail with "Could not connect to a Chroma server".

Always pass --ssl with an explicit true/false value so the user's
CLAUDE_MEM_CHROMA_SSL setting is faithfully forwarded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add regression tests for ChromaMcpManager SSL flag fix

Adds 4 focused test cases verifying buildCommandArgs() produces correct
--ssl args, covering SSL=false, SSL=true, unset (defaults to false), and
local mode (no --ssl flag). Requested by @xkonjin in PR #1286 review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: rebuild checked-in bundles to include SSL flag fix

Rebuild all bundles against upstream/main so the --ssl <true|false>
fix is present in the runtime artifacts that hooks and the marketplace
plugin actually execute.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:03:58 -07:00

116 lines
4.2 KiB
TypeScript

/**
* Regression tests for ChromaMcpManager SSL flag handling (PR #1286)
*
* Validates that buildCommandArgs() always emits the correct `--ssl` flag
* based on CLAUDE_MEM_CHROMA_SSL, and omits it entirely in local mode.
*
* Strategy: mock StdioClientTransport to capture the spawned args without
* actually launching a subprocess, then inspect the captured args array.
*/
import { describe, it, expect, beforeEach, mock } from 'bun:test';
// ── Mutable settings closure (updated per test) ────────────────────────
let currentSettings: Record<string, string> = {};
// ── Mock modules BEFORE importing the module under test ────────────────
// Capture the args passed to StdioClientTransport constructor
let capturedTransportOpts: { command: string; args: string[] } | null = null;
mock.module('@modelcontextprotocol/sdk/client/stdio.js', () => ({
StdioClientTransport: class FakeTransport {
// Required: ChromaMcpManager assigns transport.onclose after connect()
onclose: (() => void) | null = null;
constructor(opts: { command: string; args: string[] }) {
capturedTransportOpts = { command: opts.command, args: opts.args };
}
async close() {}
},
}));
mock.module('@modelcontextprotocol/sdk/client/index.js', () => ({
Client: class FakeClient {
constructor() {}
async connect() {}
async callTool() {
return { content: [{ type: 'text', text: '{}' }] };
}
async close() {}
},
}));
mock.module('../../../src/shared/SettingsDefaultsManager.js', () => ({
SettingsDefaultsManager: {
get: (key: string) => currentSettings[key] ?? '',
getInt: () => 0,
loadFromFile: () => currentSettings,
},
}));
mock.module('../../../src/shared/paths.js', () => ({
USER_SETTINGS_PATH: '/tmp/fake-settings.json',
}));
mock.module('../../../src/utils/logger.js', () => ({
logger: {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
failure: () => {},
},
}));
// ── Now import the module under test ───────────────────────────────────
import { ChromaMcpManager } from '../../../src/services/sync/ChromaMcpManager.js';
// ── Helpers ────────────────────────────────────────────────────────────
async function assertSslFlag(sslSetting: string | undefined, expectedValue: string) {
currentSettings = { CLAUDE_MEM_CHROMA_MODE: 'remote' };
if (sslSetting !== undefined) currentSettings.CLAUDE_MEM_CHROMA_SSL = sslSetting;
await mgr.callTool('chroma_list_collections', {});
expect(capturedTransportOpts).not.toBeNull();
const sslIdx = capturedTransportOpts!.args.indexOf('--ssl');
expect(sslIdx).not.toBe(-1);
expect(capturedTransportOpts!.args[sslIdx + 1]).toBe(expectedValue);
}
let mgr: ChromaMcpManager;
// ── Test suite ─────────────────────────────────────────────────────────
describe('ChromaMcpManager SSL flag regression (#1286)', () => {
beforeEach(async () => {
await ChromaMcpManager.reset();
capturedTransportOpts = null;
currentSettings = {};
mgr = ChromaMcpManager.getInstance();
});
it('emits --ssl false when CLAUDE_MEM_CHROMA_SSL=false', async () => {
await assertSslFlag('false', 'false');
});
it('emits --ssl true when CLAUDE_MEM_CHROMA_SSL=true', async () => {
await assertSslFlag('true', 'true');
});
it('defaults --ssl false when CLAUDE_MEM_CHROMA_SSL is not set', async () => {
await assertSslFlag(undefined, 'false');
});
it('omits --ssl entirely in local mode', async () => {
currentSettings = {
CLAUDE_MEM_CHROMA_MODE: 'local',
};
await mgr.callTool('chroma_list_collections', {});
expect(capturedTransportOpts).not.toBeNull();
const args = capturedTransportOpts!.args;
expect(args).not.toContain('--ssl');
expect(args).toContain('--client-type');
expect(args[args.indexOf('--client-type') + 1]).toBe('persistent');
});
});