/** * The shipped `templates/*.yaml` are seeded into mcpd and are what `mcpctl * create server --from-template` builds from, so drift there ships broken * servers. The unifi-network template had drifted on every field that * mattered — python runtime for an npm package, an env contract * (UNIFI_HOST/USERNAME/PASSWORD) the package doesn't read, and a comment * disabling its health check for a reason that had stopped being true — and * nothing caught it because no test ever read the files. */ import { describe, it, expect } from 'vitest'; import { readdirSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import yaml from 'js-yaml'; import { CreateTemplateSchema } from '../src/validation/template.schema.js'; const TEMPLATES_DIR = fileURLToPath(new URL('../../../templates', import.meta.url)); const files = readdirSync(TEMPLATES_DIR).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml')); interface RawTemplate { name?: string; runtime?: string; packageName?: string; dockerImage?: string; externalUrl?: string; healthCheck?: { tool?: string }; env?: Array<{ name?: string }>; } function load(file: string): RawTemplate { return yaml.load(readFileSync(join(TEMPLATES_DIR, file), 'utf-8')) as RawTemplate; } describe('shipped templates', () => { it('ships at least one template', () => { expect(files.length).toBeGreaterThan(0); }); it.each(files)('%s validates against CreateTemplateSchema', (file) => { const parsed = CreateTemplateSchema.safeParse(load(file)); expect(parsed.success ? null : parsed.error.issues).toBeNull(); }); it.each(files)('%s declares a runner the orchestrator knows', (file) => { const tpl = load(file); // `runtime` only means anything for package-based servers, and only // 'node' (npx) and 'python' (uvx) are wired in buildRuntimeSpawnCmd. if (tpl.runtime !== undefined) { expect(['node', 'python']).toContain(tpl.runtime); } }); it.each(files)('%s says how to actually run the server', (file) => { const tpl = load(file); const runnable = tpl.packageName !== undefined || tpl.dockerImage !== undefined || tpl.externalUrl !== undefined; expect(runnable, `${file} has no packageName, dockerImage, or externalUrl`).toBe(true); }); it.each(files)('%s names a readiness probe tool, not a bare liveness probe', (file) => { const tpl = load(file); // Without a `tool`, an instance from this template can only ever report // `live` — nothing would ever check its upstream. See docs/reliability.md. expect(tpl.healthCheck?.tool, `${file} has no healthCheck.tool`).toBeTruthy(); }); it.each(files)('%s declares uniquely-named env entries', (file) => { const names = (load(file).env ?? []).map((e) => e.name); expect(new Set(names).size).toBe(names.length); }); it('has no template for a retired server', () => { // node-red was retired 2026-08-09: it answered on neither its Tailscale // nor its LAN address and had no deployment anywhere. expect(files).not.toContain('node-red.yaml'); }); });