import assert from 'node:assert/strict'; import { afterEach, describe, it } from 'node:test'; import { loadConfig } from '../src/config.ts'; const SAVED = { ...process.env }; afterEach(() => { process.env = { ...SAVED }; }); describe('loadConfig', () => { it('requires the instance URL and token', () => { delete process.env.TSC_URL; process.env.TSC_JWT_COOKIE = 'x'; assert.throws(() => loadConfig(), /TSC_URL/); }); it('strips trailing slashes so paths concatenate cleanly', () => { process.env.TSC_URL = 'https://example.org///'; process.env.TSC_JWT_COOKIE = 'x'; assert.equal(loadConfig().baseUrl, 'https://example.org'); }); it('rejects a non-numeric port rather than silently defaulting', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; process.env.PORT = 'not-a-number'; assert.throws(() => loadConfig(), /PORT/); }); it('treats a blank auth token as absent', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; process.env.MCP_AUTH_TOKEN = ' '; assert.equal(loadConfig().authToken, undefined); }); }); describe('loadConfig: secret MCP path and state directory', () => { it('accepts a long URL-safe secret and leaves it off by default', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; assert.equal(loadConfig().mcpPathSecret, undefined); process.env.MCP_PATH_SECRET = '0123456789abcdef0123456789abcdef'; assert.equal(loadConfig().mcpPathSecret, '0123456789abcdef0123456789abcdef'); }); it('refuses a short or unsafe secret without echoing it', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; for (const secret of ['short-secret', 'has spaces in it but is long enough 1234', 'slash/in/the/middle/0123456789abcdefgh']) { process.env.MCP_PATH_SECRET = secret; assert.throws( () => loadConfig(), (error: Error) => /MCP_PATH_SECRET/.test(error.message) && !error.message.includes(secret), ); } }); it('resolves the state directory to an absolute path', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; process.env.STATE_DIR = 'tmp/state'; assert.match(loadConfig().stateDir ?? '', /^\/.*\/tmp\/state$/); }); }); describe('loadConfig: connector token', () => { const connector = 'connector-0123456789abcdef0123456789'; it('is off by default, and accepted alongside the main token', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; process.env.MCP_AUTH_TOKEN = 'main-token'; assert.equal(loadConfig().connectorToken, undefined); process.env.MCP_CONNECTOR_TOKEN = connector; assert.equal(loadConfig().connectorToken, connector); }); it('refuses a short or spaced token without echoing it', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; process.env.MCP_AUTH_TOKEN = 'main-token'; for (const token of ['too-short', 'long enough but with spaces in it 0123']) { process.env.MCP_CONNECTOR_TOKEN = token; assert.throws( () => loadConfig(), (error: Error) => /MCP_CONNECTOR_TOKEN/.test(error.message) && !error.message.includes(token), ); } }); it('refuses to leave /api open, or to be the main token under another name', () => { process.env.TSC_URL = 'https://example.org'; process.env.TSC_JWT_COOKIE = 'x'; process.env.MCP_CONNECTOR_TOKEN = connector; delete process.env.MCP_AUTH_TOKEN; assert.throws(() => loadConfig(), /needs MCP_AUTH_TOKEN/); process.env.MCP_AUTH_TOKEN = connector; assert.throws(() => loadConfig(), /must differ from MCP_AUTH_TOKEN/); }); });