134 lines
5.5 KiB
TypeScript
134 lines
5.5 KiB
TypeScript
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { resolveConfigPath } from '../src/config.js';
|
|
|
|
function makeTempDir(prefix: string): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
}
|
|
|
|
describe('resolveConfigPath', () => {
|
|
const originalEnvValue = process.env.MCPORTER_CONFIG;
|
|
const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
let tempDirs: string[] = [];
|
|
let homedirSpy: { mockRestore(): void } | undefined;
|
|
|
|
afterEach(() => {
|
|
if (originalEnvValue === undefined) {
|
|
delete process.env.MCPORTER_CONFIG;
|
|
} else {
|
|
process.env.MCPORTER_CONFIG = originalEnvValue;
|
|
}
|
|
if (originalXdgConfigHome === undefined) {
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
} else {
|
|
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
|
|
}
|
|
homedirSpy?.mockRestore();
|
|
for (const dir of tempDirs) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
tempDirs = [];
|
|
});
|
|
|
|
it('prefers an explicit config path argument', () => {
|
|
const tempRoot = makeTempDir('mcporter-config-');
|
|
tempDirs.push(tempRoot);
|
|
const explicitPath = path.join(tempRoot, 'custom.json');
|
|
fs.writeFileSync(explicitPath, '{"mcpServers":{}}');
|
|
|
|
const resolved = resolveConfigPath(explicitPath, tempRoot);
|
|
expect(resolved.path).toBe(explicitPath);
|
|
expect(resolved.explicit).toBe(true);
|
|
});
|
|
|
|
it('uses MCPORTER_CONFIG when set', () => {
|
|
const tempRoot = makeTempDir('mcporter-config-env-');
|
|
tempDirs.push(tempRoot);
|
|
const envPath = path.join(tempRoot, 'env-config.json');
|
|
fs.writeFileSync(envPath, '{"mcpServers":{}}');
|
|
process.env.MCPORTER_CONFIG = envPath;
|
|
|
|
const resolved = resolveConfigPath(undefined, tempRoot);
|
|
expect(resolved.path).toBe(envPath);
|
|
expect(resolved.explicit).toBe(true);
|
|
});
|
|
|
|
it('falls back to the project config when it exists', () => {
|
|
const tempRoot = makeTempDir('mcporter-project-');
|
|
tempDirs.push(tempRoot);
|
|
const projectConfig = path.join(tempRoot, 'config', 'mcporter.json');
|
|
fs.mkdirSync(path.dirname(projectConfig), { recursive: true });
|
|
fs.writeFileSync(projectConfig, '{"mcpServers":{}}');
|
|
|
|
const resolved = resolveConfigPath(undefined, tempRoot);
|
|
expect(resolved.path).toBe(projectConfig);
|
|
expect(resolved.explicit).toBe(false);
|
|
});
|
|
|
|
it('falls back to the home config when project config is missing', () => {
|
|
const tempRoot = makeTempDir('mcporter-project-missing-');
|
|
const fakeHome = makeTempDir('mcporter-home-');
|
|
tempDirs.push(tempRoot, fakeHome);
|
|
const homeConfigDir = path.join(fakeHome, '.mcporter');
|
|
const homeConfigPath = path.join(homeConfigDir, 'mcporter.json');
|
|
fs.mkdirSync(homeConfigDir, { recursive: true });
|
|
fs.writeFileSync(homeConfigPath, '{"mcpServers":{}}');
|
|
homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(fakeHome);
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
|
|
const resolved = resolveConfigPath(undefined, tempRoot);
|
|
expect(resolved.path).toBe(homeConfigPath);
|
|
expect(resolved.explicit).toBe(false);
|
|
});
|
|
|
|
it('returns explicit=false when no config exists anywhere (regression for ENOENT bug)', () => {
|
|
// This test verifies the fix for the bug where mcporter crashed with ENOENT
|
|
// when run in an environment with no config file at all. The CLI was passing
|
|
// the resolved (but non-existent) path as if it were explicit.
|
|
const tempRoot = makeTempDir('mcporter-no-config-anywhere-');
|
|
const fakeHome = makeTempDir('mcporter-empty-home-');
|
|
tempDirs.push(tempRoot, fakeHome);
|
|
// No config files created - completely empty environment
|
|
homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(fakeHome);
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
|
|
const resolved = resolveConfigPath(undefined, tempRoot);
|
|
// Should return the default project path but marked as NOT explicit
|
|
expect(resolved.path).toBe(path.join(tempRoot, 'config', 'mcporter.json'));
|
|
expect(resolved.explicit).toBe(false);
|
|
});
|
|
|
|
it('discovers XDG config home before the legacy home config', () => {
|
|
const tempRoot = makeTempDir('mcporter-project-xdg-');
|
|
const fakeHome = makeTempDir('mcporter-xdg-home-');
|
|
tempDirs.push(tempRoot, fakeHome);
|
|
const xdgConfigHome = path.join(fakeHome, '.config');
|
|
const xdgConfigPath = path.join(xdgConfigHome, 'mcporter', 'mcporter.json');
|
|
fs.mkdirSync(path.dirname(xdgConfigPath), { recursive: true });
|
|
fs.writeFileSync(xdgConfigPath, '{"mcpServers":{}}');
|
|
homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(fakeHome);
|
|
process.env.XDG_CONFIG_HOME = xdgConfigHome;
|
|
|
|
const resolved = resolveConfigPath(undefined, tempRoot);
|
|
expect(resolved.path).toBe(xdgConfigPath);
|
|
expect(resolved.explicit).toBe(false);
|
|
});
|
|
|
|
it('falls back to the legacy home config when XDG config home has no mcporter config', () => {
|
|
const tempRoot = makeTempDir('mcporter-project-empty-xdg-');
|
|
const fakeHome = makeTempDir('mcporter-empty-xdg-home-');
|
|
tempDirs.push(tempRoot, fakeHome);
|
|
const legacyConfigPath = path.join(fakeHome, '.mcporter', 'mcporter.json');
|
|
fs.mkdirSync(path.dirname(legacyConfigPath), { recursive: true });
|
|
fs.writeFileSync(legacyConfigPath, '{"mcpServers":{}}');
|
|
homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(fakeHome);
|
|
process.env.XDG_CONFIG_HOME = path.join(fakeHome, '.empty-xdg-config');
|
|
|
|
const resolved = resolveConfigPath(undefined, tempRoot);
|
|
expect(resolved.path).toBe(legacyConfigPath);
|
|
expect(resolved.explicit).toBe(false);
|
|
});
|
|
});
|