113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
|
|
import { describe, it, expect, vi } from 'vitest';
|
||
|
|
import { resolveServerEnv } from '../src/services/env-resolver.js';
|
||
|
|
import type { ISecretRepository } from '../src/repositories/interfaces.js';
|
||
|
|
import type { McpServer } from '@prisma/client';
|
||
|
|
|
||
|
|
function makeServer(env: unknown[]): McpServer {
|
||
|
|
return {
|
||
|
|
id: 'srv-1',
|
||
|
|
name: 'test-server',
|
||
|
|
description: '',
|
||
|
|
packageName: null,
|
||
|
|
dockerImage: 'test:latest',
|
||
|
|
transport: 'STDIO',
|
||
|
|
repositoryUrl: null,
|
||
|
|
externalUrl: null,
|
||
|
|
command: null,
|
||
|
|
containerPort: null,
|
||
|
|
replicas: 1,
|
||
|
|
env,
|
||
|
|
version: 1,
|
||
|
|
createdAt: new Date(),
|
||
|
|
updatedAt: new Date(),
|
||
|
|
} as McpServer;
|
||
|
|
}
|
||
|
|
|
||
|
|
function mockSecretRepo(secrets: Record<string, Record<string, string>>): ISecretRepository {
|
||
|
|
return {
|
||
|
|
findAll: vi.fn(async () => []),
|
||
|
|
findById: vi.fn(async () => null),
|
||
|
|
findByName: vi.fn(async (name: string) => {
|
||
|
|
const data = secrets[name];
|
||
|
|
if (!data) return null;
|
||
|
|
return { id: `sec-${name}`, name, data, version: 1, createdAt: new Date(), updatedAt: new Date() };
|
||
|
|
}),
|
||
|
|
create: vi.fn(async () => ({} as never)),
|
||
|
|
update: vi.fn(async () => ({} as never)),
|
||
|
|
delete: vi.fn(async () => {}),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('resolveServerEnv', () => {
|
||
|
|
it('resolves inline values', async () => {
|
||
|
|
const server = makeServer([
|
||
|
|
{ name: 'FOO', value: 'bar' },
|
||
|
|
{ name: 'BAZ', value: 'qux' },
|
||
|
|
]);
|
||
|
|
const repo = mockSecretRepo({});
|
||
|
|
const result = await resolveServerEnv(server, repo);
|
||
|
|
expect(result).toEqual({ FOO: 'bar', BAZ: 'qux' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('resolves secret references', async () => {
|
||
|
|
const server = makeServer([
|
||
|
|
{ name: 'TOKEN', valueFrom: { secretRef: { name: 'ha-creds', key: 'HOMEASSISTANT_TOKEN' } } },
|
||
|
|
]);
|
||
|
|
const repo = mockSecretRepo({
|
||
|
|
'ha-creds': { HOMEASSISTANT_TOKEN: 'secret-token-123' },
|
||
|
|
});
|
||
|
|
const result = await resolveServerEnv(server, repo);
|
||
|
|
expect(result).toEqual({ TOKEN: 'secret-token-123' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('handles mixed inline and secret refs', async () => {
|
||
|
|
const server = makeServer([
|
||
|
|
{ name: 'URL', value: 'https://ha.local' },
|
||
|
|
{ name: 'TOKEN', valueFrom: { secretRef: { name: 'creds', key: 'TOKEN' } } },
|
||
|
|
]);
|
||
|
|
const repo = mockSecretRepo({
|
||
|
|
creds: { TOKEN: 'my-token' },
|
||
|
|
});
|
||
|
|
const result = await resolveServerEnv(server, repo);
|
||
|
|
expect(result).toEqual({ URL: 'https://ha.local', TOKEN: 'my-token' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('caches secret lookups', async () => {
|
||
|
|
const server = makeServer([
|
||
|
|
{ name: 'A', valueFrom: { secretRef: { name: 'shared', key: 'KEY_A' } } },
|
||
|
|
{ name: 'B', valueFrom: { secretRef: { name: 'shared', key: 'KEY_B' } } },
|
||
|
|
]);
|
||
|
|
const repo = mockSecretRepo({
|
||
|
|
shared: { KEY_A: 'val-a', KEY_B: 'val-b' },
|
||
|
|
});
|
||
|
|
const result = await resolveServerEnv(server, repo);
|
||
|
|
expect(result).toEqual({ A: 'val-a', B: 'val-b' });
|
||
|
|
expect(repo.findByName).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('throws when secret not found', async () => {
|
||
|
|
const server = makeServer([
|
||
|
|
{ name: 'TOKEN', valueFrom: { secretRef: { name: 'missing', key: 'TOKEN' } } },
|
||
|
|
]);
|
||
|
|
const repo = mockSecretRepo({});
|
||
|
|
await expect(resolveServerEnv(server, repo)).rejects.toThrow("Secret 'missing' not found");
|
||
|
|
});
|
||
|
|
|
||
|
|
it('throws when secret key not found', async () => {
|
||
|
|
const server = makeServer([
|
||
|
|
{ name: 'TOKEN', valueFrom: { secretRef: { name: 'creds', key: 'NONEXISTENT' } } },
|
||
|
|
]);
|
||
|
|
const repo = mockSecretRepo({
|
||
|
|
creds: { OTHER_KEY: 'val' },
|
||
|
|
});
|
||
|
|
await expect(resolveServerEnv(server, repo)).rejects.toThrow("Key 'NONEXISTENT' not found in secret 'creds'");
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns empty map for empty env', async () => {
|
||
|
|
const server = makeServer([]);
|
||
|
|
const repo = mockSecretRepo({});
|
||
|
|
const result = await resolveServerEnv(server, repo);
|
||
|
|
expect(result).toEqual({});
|
||
|
|
});
|
||
|
|
});
|