Files
openmaic/OpenMAIC/packages/@openmaic/generation/test/loader.test.ts
2026-08-16 14:58:47 +08:00

134 lines
4.7 KiB
TypeScript

import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { describe, expect, test } from 'vitest';
import {
PROMPT_IDS,
buildPrompt,
interpolateVariables,
loadPrompt,
processConditionalBlocks,
processSnippets,
} from '@openmaic/generation';
function createPromptsFixture(): string {
const promptsDir = mkdtempSync(join(tmpdir(), 'openmaic-generation-prompts-'));
mkdirSync(join(promptsDir, 'snippets'));
mkdirSync(join(promptsDir, 'templates'));
return promptsDir;
}
describe('loader semantics', () => {
test('loads and interpolates a known prompt', () => {
const result = buildPrompt(PROMPT_IDS.SLIDE_ACTIONS, {
title: 'Test Slide',
keyPoints: '1. point one',
description: 'desc',
elements: '[]',
courseContext: '',
agents: '',
userProfile: '',
languageDirective: 'en',
});
expect(result).not.toBeNull();
expect(result!.system.length).toBeGreaterThan(100);
expect(result!.user).toContain('Test Slide');
});
test('defaults the project summary for the previous pbl-actions variable set', () => {
const result = buildPrompt(PROMPT_IDS.PBL_ACTIONS, {
title: 'Resilient Systems',
projectTopic: 'Retry handling',
projectDescription: 'Practice resilient generation',
keyPoints: '1. Classify failures',
description: 'Build a retry-aware workflow',
courseContext: '',
agents: '',
languageDirective: 'en',
});
expect(result).not.toBeNull();
expect(result!.system).toContain(
'(No generated milestones are available; introduce the project topic without inventing any.)',
);
expect(`${result!.system}\n${result!.user}`).not.toMatch(/\{\{\w[\w-]*\}\}/);
});
test('inlines a snippet exactly once', () => {
const promptsDir = createPromptsFixture();
writeFileSync(join(promptsDir, 'snippets', 'example.md'), 'inlined content\n');
expect(processSnippets('Before {{snippet:example}} after', promptsDir)).toBe(
'Before inlined content after',
);
});
test('returns null for an unknown prompt', () => {
// @ts-expect-error -- exercise the runtime failure path.
expect(loadPrompt('does-not-exist')).toBeNull();
});
test('throws when a referenced snippet is missing', () => {
expect(() => processSnippets('{{snippet:does-not-exist}}')).toThrow(/Snippet not found/);
});
test('does not recursively process snippet markers inside snippets', () => {
const promptsDir = createPromptsFixture();
writeFileSync(join(promptsDir, 'snippets', 'outer.md'), 'outer {{snippet:inner}}\n');
writeFileSync(join(promptsDir, 'snippets', 'inner.md'), 'inner\n');
expect(processSnippets('{{snippet:outer}}', promptsDir)).toBe('outer {{snippet:inner}}');
});
test('propagates a missing snippet referenced by user.md', () => {
const promptsDir = createPromptsFixture();
const promptDir = join(promptsDir, 'templates', 'fixture');
mkdirSync(promptDir);
writeFileSync(join(promptDir, 'system.md'), 'system prompt\n');
writeFileSync(join(promptDir, 'user.md'), '{{snippet:not-packaged}}\n');
// @ts-expect-error -- fixture is intentionally outside the public PromptId union.
expect(() => loadPrompt('fixture', promptsDir)).toThrow(/Snippet not found: not-packaged/);
});
test('keeps conditional content when its condition is truthy', () => {
expect(processConditionalBlocks('A {{#if enabled}}kept{{/if}} B', { enabled: true })).toBe(
'A kept B',
);
});
test('drops conditional content when its condition is falsey', () => {
expect(processConditionalBlocks('A {{#if enabled}}dropped{{/if}} B', { enabled: false })).toBe(
'A B',
);
});
test('does not recursively process nested conditional blocks', () => {
const template = '{{#if outer}}outer {{#if inner}}inner{{/if}}{{/if}}';
expect(processConditionalBlocks(template, { outer: true, inner: true })).toBe(
'outer {{#if inner}}inner{{/if}}',
);
});
test('interpolates string variables', () => {
expect(interpolateVariables('Hello {{name}}', { name: 'Ada' })).toBe('Hello Ada');
});
test('interpolates object variables as two-space JSON', () => {
expect(interpolateVariables('{{payload}}', { payload: { nested: true } })).toBe(
'{\n "nested": true\n}',
);
});
test('preserves placeholders whose variable is undefined', () => {
expect(interpolateVariables('{{missing}}', { missing: undefined })).toBe('{{missing}}');
});
test('leaves kebab-case placeholders untouched', () => {
expect(interpolateVariables('{{next-agent}}', { 'next-agent': 'ignored' })).toBe(
'{{next-agent}}',
);
});
});