Implement more tests

- Add comprehensive unit tests and CI pipeline

- Introduced unit tests for `agent-wrapper`, `cli`, and `generate-template` modules covering key functionality like structure, integration, argument parsing, filename handling, and error scenarios.

- Implemented a new CI workflow with Bun and Rust testing.
This commit is contained in:
geoffsee
2025-07-11 17:07:47 -04:00
parent 8545aa8699
commit bbc9d8d971
11 changed files with 2264 additions and 1 deletions

View File

@@ -0,0 +1,394 @@
import { expect, test, describe, mock, beforeEach } from "bun:test";
// Since agent-wrapper.ts has complex external dependencies, we'll test the structure
// and methods that can be tested without actual API calls
describe("HierarchyAgent", () => {
describe("module structure", () => {
test("should export HierarchyAgent class", async () => {
const module = await import("../agent-wrapper");
expect(typeof module.default).toBe("function"); // Constructor function
});
test("should export AgentConfig interface", async () => {
// Test that we can import the module without errors
const module = await import("../agent-wrapper");
expect(module).toBeDefined();
});
test("should export HierarchyGenerationOptions interface", async () => {
// Test that we can import the module without errors
const module = await import("../agent-wrapper");
expect(module).toBeDefined();
});
});
describe("class instantiation", () => {
test("should create HierarchyAgent instance with config", async () => {
// Mock the Agent class to avoid actual OpenAI dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
const mockAgentConstructor = mock(() => mockAgent);
// Mock the @openai/agents module
mock.module('@openai/agents', () => ({
Agent: mockAgentConstructor
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
expect(() => {
new HierarchyAgent(config);
}).not.toThrow();
});
});
describe("method signatures", () => {
test("should have required methods", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
// Test that methods exist
expect(typeof agent.generateHierarchy).toBe("function");
expect(typeof agent.generateHierarchyWithStreaming).toBe("function");
expect(typeof agent.generateExample).toBe("function");
expect(typeof agent.getAvailableTemplates).toBe("function");
expect(typeof agent.getTemplatesByDomain).toBe("function");
expect(typeof agent.getTemplatesByVersion).toBe("function");
expect(typeof agent.addCustomTemplate).toBe("function");
expect(typeof agent.validateHierarchy).toBe("function");
expect(typeof agent.optimizeHierarchy).toBe("function");
expect(typeof agent.generateMultipleExamples).toBe("function");
});
});
describe("template management", () => {
test("should manage templates through TemplateManager", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
// Test template methods don't throw
expect(() => {
agent.getAvailableTemplates();
}).not.toThrow();
expect(() => {
agent.getTemplatesByDomain("technology");
}).not.toThrow();
expect(() => {
agent.getTemplatesByVersion("v1");
}).not.toThrow();
});
test("should add custom templates", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
const customTemplate = {
version: 'v1' as const,
domain: 'Custom',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'Custom template',
commonSkills: ['Skill1'],
commonTools: ['Tool1'],
examples: ['Example1']
};
expect(() => {
agent.addCustomTemplate('custom-key', customTemplate);
}).not.toThrow();
});
});
describe("validation methods", () => {
test("should validate hierarchy data", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
const validHierarchy = {
version: "v1",
domain: "Technology",
structure: ["Domain", "Specialization", "Role", "Responsibility"],
description: "Test hierarchy"
};
expect(() => {
agent.validateHierarchy(validHierarchy);
}).not.toThrow();
});
test("should optimize hierarchy data", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
const hierarchyData = {
version: "v1",
domain: "Technology",
structure: ["Domain", "Specialization", "Role", "Responsibility"],
description: "Test hierarchy"
};
expect(() => {
agent.optimizeHierarchy(hierarchyData);
}).not.toThrow();
});
});
describe("configuration handling", () => {
test("should handle different model configurations", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const configs = [
{
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
},
{
apiKey: "test-key",
model: "gpt-4",
instructions: "Different instructions"
}
];
configs.forEach(config => {
expect(() => {
new HierarchyAgent(config);
}).not.toThrow();
});
});
test("should handle optional configuration parameters", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const minimalConfig = {
apiKey: "test-key"
};
expect(() => {
new HierarchyAgent(minimalConfig);
}).not.toThrow();
});
});
describe("error handling", () => {
test("should handle missing API key gracefully", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const invalidConfig = {
model: "gpt-4o-mini",
instructions: "Test instructions"
// Missing apiKey
};
// The constructor should still work, but API calls would fail
expect(() => {
new HierarchyAgent(invalidConfig);
}).not.toThrow();
});
});
describe("integration points", () => {
test("should integrate with HierarchyGenerator", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
// Test that the agent has the expected structure for integration
expect(agent).toBeDefined();
expect(typeof agent.generateExample).toBe("function");
});
test("should integrate with TemplateManager", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
// Test template management methods
expect(typeof agent.getAvailableTemplates).toBe("function");
expect(typeof agent.addCustomTemplate).toBe("function");
});
test("should integrate with OutputFormatter", async () => {
// Mock dependencies
const mockAgent = {
name: "test-agent",
instructions: "test instructions"
};
mock.module('@openai/agents', () => ({
Agent: mock(() => mockAgent)
}));
const { default: HierarchyAgent } = await import("../agent-wrapper");
const config = {
apiKey: "test-key",
model: "gpt-4o-mini",
instructions: "Test instructions"
};
const agent = new HierarchyAgent(config);
// Test that methods exist for output formatting integration
expect(typeof agent.generateMultipleExamples).toBe("function");
});
});
});

View File

@@ -0,0 +1,220 @@
import { expect, test, describe, mock, beforeEach } from "bun:test";
import { HierarchyGenerator } from "../components/hierarchy-generator";
import type { HierarchyTemplate, GenerationParams } from "../components/hierarchy-generator";
// Mock the @openai/agents module
const mockRun = mock(() => Promise.resolve({ finalOutput: "Generated hierarchy content" }));
const mockRunStream = mock(() => Promise.resolve({
finalOutput: "Streamed hierarchy content",
[Symbol.asyncIterator]: async function* () {
yield { type: 'raw_model_stream_event', data: { delta: 'test' }, delta: 'test' };
yield { type: 'agent_updated_stream_event', agent: { name: 'TestAgent' } };
yield { type: 'run_item_stream_event', item: { type: 'tool_call_item' } };
yield { type: 'run_item_stream_event', item: { type: 'message_output_item' } };
}
}));
mock.module('@openai/agents', () => ({
Agent: class MockAgent {},
run: mockRun,
StreamedRunResult: class MockStreamedRunResult {}
}));
describe("HierarchyGenerator", () => {
const mockAgent = {} as any; // Mock agent instance
let generator: HierarchyGenerator;
beforeEach(() => {
generator = new HierarchyGenerator(mockAgent);
mockRun.mockClear();
mockRunStream.mockClear();
});
describe("generateFromTemplate", () => {
const mockTemplate: HierarchyTemplate = {
version: 'v1',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'Test template'
};
const mockParams: GenerationParams = {
domain: 'Technology',
complexity: 'medium',
includeSkills: true,
includeTools: true,
includeExamples: true,
stream: false
};
test("should generate hierarchy without streaming", async () => {
const result = await generator.generateFromTemplate(mockTemplate, mockParams);
expect(mockRun).toHaveBeenCalledTimes(1);
expect(result).toBe("Generated hierarchy content");
});
test("should generate hierarchy with streaming when stream is true", async () => {
const streamParams = { ...mockParams, stream: true };
mockRun.mockResolvedValueOnce(mockRunStream());
const result = await generator.generateFromTemplate(mockTemplate, streamParams);
expect(mockRun).toHaveBeenCalledWith(mockAgent, expect.any(String), { stream: true });
});
test("should build correct prompt for v1 template", async () => {
await generator.generateFromTemplate(mockTemplate, mockParams);
const calledPrompt = mockRun.mock.calls[0][1];
expect(calledPrompt).toContain('Technology domain');
expect(calledPrompt).toContain('Domain → Specialization → Role → Responsibility');
expect(calledPrompt).toContain('medium');
expect(calledPrompt).toContain('✓ Include relevant skills');
expect(calledPrompt).toContain('✓ Include tools and technologies');
expect(calledPrompt).toContain('✓ Include practical examples');
expect(calledPrompt).toContain('import { Enterprise, DomainModel, SpecializationModel, RoleModel, ResponsibilityModel } from "../../lib/v1"');
});
test("should build correct prompt for v2 template", async () => {
const v2Template: HierarchyTemplate = {
version: 'v2',
structure: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task'],
description: 'Test v2 template'
};
await generator.generateFromTemplate(v2Template, mockParams);
const calledPrompt = mockRun.mock.calls[0][1];
expect(calledPrompt).toContain('Domain → Industry → Profession → Field → Role → Task');
expect(calledPrompt).toContain('import { Enterprise, DomainModel, IndustryModel, ProfessionModel, FieldModel, RoleModel, TaskModel } from "../../lib/v2"');
});
test("should handle different complexity levels", async () => {
// Test simple complexity
const simpleParams = { ...mockParams, complexity: 'simple' as const };
await generator.generateFromTemplate(mockTemplate, simpleParams);
let calledPrompt = mockRun.mock.calls[0][1];
expect(calledPrompt).toContain('Keep it simple with essential elements only');
mockRun.mockClear();
// Test complex complexity
const complexParams = { ...mockParams, complexity: 'complex' as const };
await generator.generateFromTemplate(mockTemplate, complexParams);
calledPrompt = mockRun.mock.calls[0][1];
expect(calledPrompt).toContain('Include multiple branches and detailed attributes');
});
test("should handle optional features being disabled", async () => {
const minimalParams: GenerationParams = {
domain: 'Technology',
complexity: 'medium',
includeSkills: false,
includeTools: false,
includeExamples: false,
stream: false
};
await generator.generateFromTemplate(mockTemplate, minimalParams);
const calledPrompt = mockRun.mock.calls[0][1];
expect(calledPrompt).not.toContain('✓ Include relevant skills');
expect(calledPrompt).not.toContain('✓ Include tools and technologies');
expect(calledPrompt).not.toContain('✓ Include practical examples');
});
});
describe("generateFromTemplateWithStreaming", () => {
const mockTemplate: HierarchyTemplate = {
version: 'v1',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'Test template'
};
const mockParams: GenerationParams = {
domain: 'Technology',
complexity: 'medium',
includeSkills: true,
includeTools: true,
includeExamples: true
};
test("should handle streaming with custom event handler", async () => {
const mockEventHandler = mock(() => {});
// Mock console.log to avoid output during tests
const originalConsoleLog = console.log;
console.log = mock(() => {});
// Mock process.stdout.write
const originalWrite = process.stdout.write;
process.stdout.write = mock(() => true);
mockRun.mockResolvedValueOnce(mockRunStream());
const result = await generator.generateFromTemplateWithStreaming(
mockTemplate,
mockParams,
mockEventHandler
);
expect(result).toBe("Streamed hierarchy content");
expect(mockEventHandler).toHaveBeenCalled();
// Restore original functions
console.log = originalConsoleLog;
process.stdout.write = originalWrite;
});
test("should handle streaming without custom event handler", async () => {
// Mock console.log to avoid output during tests
const originalConsoleLog = console.log;
console.log = mock(() => {});
// Mock process.stdout.write
const originalWrite = process.stdout.write;
process.stdout.write = mock(() => true);
mockRun.mockResolvedValueOnce(mockRunStream());
const result = await generator.generateFromTemplateWithStreaming(mockTemplate, mockParams);
expect(result).toBe("Streamed hierarchy content");
// Restore original functions
console.log = originalConsoleLog;
process.stdout.write = originalWrite;
});
});
describe("buildPrompt", () => {
test("should create valid prompt structure", async () => {
const template: HierarchyTemplate = {
version: 'v1',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'Test template'
};
const params: GenerationParams = {
domain: 'Healthcare',
complexity: 'complex',
includeSkills: true,
includeTools: false,
includeExamples: true
};
await generator.generateFromTemplate(template, params);
const prompt = mockRun.mock.calls[0][1];
// Check that prompt contains all expected elements
expect(prompt).toContain('Generate ONLY TypeScript code');
expect(prompt).toContain('Healthcare domain');
expect(prompt).toContain('complex');
expect(prompt).toContain('IMPORTANT: Output ONLY valid TypeScript code');
expect(prompt).toContain('Requirements:');
expect(prompt).toContain('The code should demonstrate:');
expect(prompt).toContain('Output format: Pure TypeScript code only');
});
});
});

View File

@@ -0,0 +1,162 @@
import { expect, test, describe } from "bun:test";
import { HierarchyModel, HierarchyStore } from "../hierarchy-model";
describe("HierarchyModel", () => {
test("should create a hierarchy model with all required fields", () => {
const hierarchyData = {
version: "v1",
domain: "Technology",
structure: ["Domain", "Specialization", "Role", "Responsibility"],
description: "A technology hierarchy for software development",
commonSkills: ["Programming", "Problem Solving", "Communication"],
commonTools: ["IDE", "Git", "Testing Frameworks"],
examples: ["Web Development", "Mobile Development", "DevOps"]
};
const hierarchy = HierarchyModel.create(hierarchyData);
expect(hierarchy.version).toBe("v1");
expect(hierarchy.domain).toBe("Technology");
expect(hierarchy.structure).toEqual(["Domain", "Specialization", "Role", "Responsibility"]);
expect(hierarchy.description).toBe("A technology hierarchy for software development");
expect(hierarchy.commonSkills).toEqual(["Programming", "Problem Solving", "Communication"]);
expect(hierarchy.commonTools).toEqual(["IDE", "Git", "Testing Frameworks"]);
expect(hierarchy.examples).toEqual(["Web Development", "Mobile Development", "DevOps"]);
});
test("should create a v2 hierarchy model", () => {
const hierarchyData = {
version: "v2",
domain: "Healthcare",
structure: ["Domain", "Industry", "Profession", "Field", "Role", "Task"],
description: "A healthcare hierarchy for medical services",
commonSkills: ["Patient Care", "Medical Knowledge", "Communication"],
commonTools: ["EMR Systems", "Medical Devices", "Diagnostic Tools"],
examples: ["Emergency Medicine", "Primary Care", "Specialized Surgery"]
};
const hierarchy = HierarchyModel.create(hierarchyData);
expect(hierarchy.version).toBe("v2");
expect(hierarchy.domain).toBe("Healthcare");
expect(hierarchy.structure).toHaveLength(6);
});
test("should handle empty arrays for optional fields", () => {
const hierarchyData = {
version: "v1",
domain: "Finance",
structure: ["Domain", "Specialization", "Role", "Responsibility"],
description: "A finance hierarchy",
commonSkills: [],
commonTools: [],
examples: []
};
const hierarchy = HierarchyModel.create(hierarchyData);
expect(hierarchy.commonSkills).toEqual([]);
expect(hierarchy.commonTools).toEqual([]);
expect(hierarchy.examples).toEqual([]);
});
});
describe("HierarchyStore", () => {
test("should create an empty hierarchy store", () => {
const store = HierarchyStore.create({
items: {}
});
expect(store.items.size).toBe(0);
});
test("should add hierarchy to store", () => {
const store = HierarchyStore.create({
items: {}
});
const hierarchyData = {
version: "v1",
domain: "Technology",
structure: ["Domain", "Specialization", "Role", "Responsibility"],
description: "A technology hierarchy",
commonSkills: ["Programming"],
commonTools: ["IDE"],
examples: ["Web Development"]
};
const hierarchy = HierarchyModel.create(hierarchyData);
store.add(hierarchy);
expect(store.items.size).toBe(1);
expect(store.items.get("Technology")).toBe(hierarchy);
});
test("should add multiple hierarchies to store", () => {
const store = HierarchyStore.create({
items: {}
});
const techHierarchy = HierarchyModel.create({
version: "v1",
domain: "Technology",
structure: ["Domain", "Specialization", "Role", "Responsibility"],
description: "A technology hierarchy",
commonSkills: ["Programming"],
commonTools: ["IDE"],
examples: ["Web Development"]
});
const financeHierarchy = HierarchyModel.create({
version: "v2",
domain: "Finance",
structure: ["Domain", "Industry", "Profession", "Field", "Role", "Task"],
description: "A finance hierarchy",
commonSkills: ["Analysis"],
commonTools: ["Excel"],
examples: ["Investment Banking"]
});
store.add(techHierarchy);
store.add(financeHierarchy);
expect(store.items.size).toBe(2);
expect(store.items.get("Technology")).toBe(techHierarchy);
expect(store.items.get("Finance")).toBe(financeHierarchy);
});
test("should overwrite hierarchy with same domain", () => {
const store = HierarchyStore.create({
items: {}
});
const hierarchy1 = HierarchyModel.create({
version: "v1",
domain: "Technology",
structure: ["Domain", "Specialization", "Role", "Responsibility"],
description: "First tech hierarchy",
commonSkills: ["Programming"],
commonTools: ["IDE"],
examples: ["Web Development"]
});
const hierarchy2 = HierarchyModel.create({
version: "v2",
domain: "Technology",
structure: ["Domain", "Industry", "Profession", "Field", "Role", "Task"],
description: "Second tech hierarchy",
commonSkills: ["Advanced Programming"],
commonTools: ["Advanced IDE"],
examples: ["AI Development"]
});
store.add(hierarchy1);
expect(store.items.size).toBe(1);
expect(store.items.get("Technology")?.description).toBe("First tech hierarchy");
store.add(hierarchy2);
expect(store.items.size).toBe(1);
expect(store.items.get("Technology")?.description).toBe("Second tech hierarchy");
expect(store.items.get("Technology")?.version).toBe("v2");
});
});

View File

@@ -0,0 +1,487 @@
import { expect, test, describe, beforeEach } from "bun:test";
import { OutputFormatter } from "../components/output-formatter";
import type { OutputOptions, FormattedOutput } from "../components/output-formatter";
describe("OutputFormatter", () => {
let formatter: OutputFormatter;
const sampleContent = `const enterprise = Enterprise.create({});
const domain = DomainModel.create({
name: "Technology",
description: "Technology domain"
});
enterprise.addDomain(domain);`;
beforeEach(() => {
formatter = new OutputFormatter();
});
describe("formatOutput", () => {
test("should format TypeScript output", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.extension).toBe('ts');
expect(result.filename).toBe('technology-hierarchy-example');
expect(result.content).toContain('import {');
expect(result.content).toContain('DomainModel, SpecializationModel, RoleModel, ResponsibilityModel');
expect(result.content).toContain('Technology Professional Hierarchy Example');
expect(result.metadata?.domain).toBe('Technology');
expect(result.metadata?.version).toBe('v1');
});
test("should format Markdown output", () => {
const options: OutputOptions = {
format: 'markdown',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Healthcare", "v2", options);
expect(result.extension).toBe('md');
expect(result.filename).toBe('healthcare-hierarchy-example');
expect(result.content).toContain('# Healthcare Professional Hierarchy Example');
expect(result.content).toContain('6-layer hierarchy');
expect(result.content).toContain('Domain → Industry → Profession → Field → Role → Task');
expect(result.content).toContain('```typescript');
expect(result.metadata?.domain).toBe('Healthcare');
expect(result.metadata?.version).toBe('v2');
});
test("should format JSON output", () => {
const options: OutputOptions = {
format: 'json',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Finance", "v1", options);
expect(result.extension).toBe('json');
expect(result.filename).toBe('finance-hierarchy-example');
const parsed = JSON.parse(result.content);
expect(parsed.domain).toBe('Finance');
expect(parsed.version).toBe('v1');
expect(parsed.structure).toEqual(['Domain', 'Specialization', 'Role', 'Responsibility']);
expect(parsed.generatedContent).toBe(sampleContent);
expect(parsed.metadata).toBeDefined();
});
test("should format YAML output", () => {
const options: OutputOptions = {
format: 'yaml',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Education", "v2", options);
expect(result.extension).toBe('yaml');
expect(result.filename).toBe('education-hierarchy-example');
expect(result.content).toContain('domain: Education');
expect(result.content).toContain('version: v2');
expect(result.content).toContain('- Domain');
expect(result.content).toContain('- Industry');
expect(result.content).toContain('- Profession');
expect(result.content).toContain('generated_content: |');
expect(result.metadata?.domain).toBe('Education');
});
test("should throw error for unsupported format", () => {
const options = {
format: 'xml' as any,
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
expect(() => {
formatter.formatOutput(sampleContent, "Technology", "v1", options);
}).toThrow('Unsupported format: xml');
});
});
describe("formatTypeScript", () => {
test("should include comments when enabled", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).toContain('/**');
expect(result.content).toContain('Technology Professional Hierarchy Example');
expect(result.content).toContain('Model Version: v1');
expect(result.content).toContain('4-layer hierarchy');
});
test("should exclude comments when disabled", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: true,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).not.toContain('/**');
expect(result.content).not.toContain('Technology Professional Hierarchy Example');
expect(result.content).toContain('import {');
});
test("should include timestamp when enabled", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: true,
includeTimestamp: true,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).toContain('Generated on:');
expect(result.metadata?.generatedAt).toBeDefined();
});
test("should use correct imports for v1", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: true,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).toContain('DomainModel, SpecializationModel, RoleModel, ResponsibilityModel');
expect(result.content).toContain('from "../../lib/v1"');
});
test("should use correct imports for v2", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: true,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(sampleContent, "Technology", "v2", options);
expect(result.content).toContain('DomainModel, IndustryModel, ProfessionModel, FieldModel, RoleModel, TaskModel');
expect(result.content).toContain('from "../../lib/v2"');
});
});
describe("extractTypeScriptCode", () => {
test("should extract code from markdown code blocks", () => {
const markdownContent = `# Some Title
Here's the TypeScript code:
\`\`\`typescript
const example = "test";
console.log(example);
\`\`\`
Some other text.`;
const options: OutputOptions = {
format: 'typescript',
includeMetadata: false,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(markdownContent, "Test", "v1", options);
expect(result.content).toContain('const example = "test";');
expect(result.content).toContain('console.log(example);');
expect(result.content).not.toContain('# Some Title');
});
test("should handle content without code blocks", () => {
const plainContent = `const test = "value";
function example() {
return test;
}`;
const options: OutputOptions = {
format: 'typescript',
includeMetadata: false,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(plainContent, "Test", "v1", options);
expect(result.content).toContain('const test = "value";');
expect(result.content).toContain('function example()');
});
test("should filter out markdown formatting", () => {
const mixedContent = `# Title
**Bold text**
*Italic text*
- List item
const code = "actual code";`;
const options: OutputOptions = {
format: 'typescript',
includeMetadata: false,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(mixedContent, "Test", "v1", options);
expect(result.content).toContain('const code = "actual code";');
expect(result.content).not.toContain('# Title');
expect(result.content).not.toContain('**Bold text**');
expect(result.content).not.toContain('- List item');
});
});
describe("formatMarkdown", () => {
test("should create proper markdown structure", () => {
const options: OutputOptions = {
format: 'markdown',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).toContain('# Technology Professional Hierarchy Example');
expect(result.content).toContain('## Overview');
expect(result.content).toContain('## Structure');
expect(result.content).toContain('## Generated Content');
expect(result.content).toContain('## Usage');
expect(result.content).toContain('4-layer hierarchy');
expect(result.content).toContain('Domain → Specialization → Role → Responsibility');
});
test("should handle v2 structure correctly", () => {
const options: OutputOptions = {
format: 'markdown',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Healthcare", "v2", options);
expect(result.content).toContain('6-layer hierarchy');
expect(result.content).toContain('Domain → Industry → Profession → Field → Role → Task');
});
test("should include timestamp when enabled", () => {
const options: OutputOptions = {
format: 'markdown',
includeMetadata: true,
includeTimestamp: true,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).toContain('**Generated on:**');
});
});
describe("formatJSON", () => {
test("should create valid JSON structure", () => {
const options: OutputOptions = {
format: 'json',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
const parsed = JSON.parse(result.content);
expect(parsed.domain).toBe('Technology');
expect(parsed.version).toBe('v1');
expect(parsed.structure).toEqual(['Domain', 'Specialization', 'Role', 'Responsibility']);
expect(parsed.generatedContent).toBe(sampleContent);
expect(parsed.metadata).toBeDefined();
expect(parsed.metadata.generator).toBe('OpenAI Agents SDK + Sumpin');
expect(parsed.metadata.hierarchyType).toBe('4-layer hierarchy');
});
test("should handle v2 structure", () => {
const options: OutputOptions = {
format: 'json',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Healthcare", "v2", options);
const parsed = JSON.parse(result.content);
expect(parsed.version).toBe('v2');
expect(parsed.structure).toEqual(['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task']);
expect(parsed.metadata.hierarchyType).toBe('6-layer hierarchy');
});
test("should exclude metadata when disabled", () => {
const options: OutputOptions = {
format: 'json',
includeMetadata: false,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
const parsed = JSON.parse(result.content);
expect(parsed.metadata).toBeUndefined();
});
test("should include timestamp when enabled", () => {
const options: OutputOptions = {
format: 'json',
includeMetadata: true,
includeTimestamp: true,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
const parsed = JSON.parse(result.content);
expect(parsed.metadata.generatedAt).toBeDefined();
});
});
describe("formatYAML", () => {
test("should create valid YAML structure", () => {
const options: OutputOptions = {
format: 'yaml',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).toContain('domain: Technology');
expect(result.content).toContain('version: v1');
expect(result.content).toContain('structure:');
expect(result.content).toContain(' - Domain');
expect(result.content).toContain(' - Specialization');
expect(result.content).toContain(' - Role');
expect(result.content).toContain(' - Responsibility');
expect(result.content).toContain('generated_content: |');
expect(result.content).toContain('metadata:');
expect(result.content).toContain('hierarchy_type: "4-layer hierarchy"');
});
test("should handle v2 structure", () => {
const options: OutputOptions = {
format: 'yaml',
includeMetadata: true,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Healthcare", "v2", options);
expect(result.content).toContain('version: v2');
expect(result.content).toContain(' - Industry');
expect(result.content).toContain(' - Profession');
expect(result.content).toContain(' - Field');
expect(result.content).toContain(' - Task');
expect(result.content).toContain('hierarchy_type: "6-layer hierarchy"');
});
test("should exclude metadata when disabled", () => {
const options: OutputOptions = {
format: 'yaml',
includeMetadata: false,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(sampleContent, "Technology", "v1", options);
expect(result.content).not.toContain('metadata:');
expect(result.content).not.toContain('generated_at:');
expect(result.content).not.toContain('generator:');
});
test("should properly indent content", () => {
const multiLineContent = `const test = "value";
function example() {
return test;
}`;
const options: OutputOptions = {
format: 'yaml',
includeMetadata: false,
includeTimestamp: false,
includeComments: true
};
const result = formatter.formatOutput(multiLineContent, "Test", "v1", options);
expect(result.content).toContain('generated_content: |');
// Check that content lines are properly indented
const lines = result.content.split('\n');
const contentStartIndex = lines.findIndex(line => line.includes('generated_content: |'));
expect(lines[contentStartIndex + 1]).toMatch(/^ /); // Should start with 2 spaces
});
});
describe("getDefaultOptions", () => {
test("should return correct default options", () => {
const defaults = formatter.getDefaultOptions();
expect(defaults.format).toBe('typescript');
expect(defaults.includeMetadata).toBe(true);
expect(defaults.includeTimestamp).toBe(true);
expect(defaults.includeComments).toBe(true);
});
});
describe("filename generation", () => {
test("should generate lowercase filenames", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: false,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(sampleContent, "TECHNOLOGY", "v1", options);
expect(result.filename).toBe('technology-hierarchy-example');
});
test("should handle domains with spaces", () => {
const options: OutputOptions = {
format: 'typescript',
includeMetadata: false,
includeTimestamp: false,
includeComments: false
};
const result = formatter.formatOutput(sampleContent, "Health Care", "v1", options);
expect(result.filename).toBe('health care-hierarchy-example');
});
});
});

View File

@@ -0,0 +1,327 @@
import { expect, test, describe, beforeEach } from "bun:test";
import { TemplateManager } from "../components/template-manager";
import type { DomainTemplate } from "../components/template-manager";
describe("TemplateManager", () => {
let templateManager: TemplateManager;
beforeEach(() => {
templateManager = new TemplateManager();
});
describe("constructor and initialization", () => {
test("should initialize with default templates", () => {
const allTemplates = templateManager.getAllTemplates();
expect(allTemplates.length).toBeGreaterThan(0);
});
test("should have v1 and v2 templates", () => {
const v1Templates = templateManager.getTemplatesByVersion('v1');
const v2Templates = templateManager.getTemplatesByVersion('v2');
expect(v1Templates.length).toBeGreaterThan(0);
expect(v2Templates.length).toBeGreaterThan(0);
});
test("should have technology templates", () => {
const techTemplates = templateManager.getTemplatesByDomain('technology');
expect(techTemplates.length).toBeGreaterThan(0);
});
});
describe("addTemplate", () => {
test("should add a new template", () => {
const customTemplate: DomainTemplate = {
version: 'v1',
domain: 'Custom',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'A custom template',
commonSkills: ['Skill1'],
commonTools: ['Tool1'],
examples: ['Example1']
};
templateManager.addTemplate('custom-v1', customTemplate);
const retrieved = templateManager.getTemplate('custom-v1');
expect(retrieved).toBeDefined();
expect(retrieved?.domain).toBe('Custom');
expect(retrieved?.version).toBe('v1');
});
test("should overwrite existing template with same key", () => {
const template1: DomainTemplate = {
version: 'v1',
domain: 'First',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'First template',
commonSkills: [],
commonTools: [],
examples: []
};
const template2: DomainTemplate = {
version: 'v2',
domain: 'Second',
structure: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task'],
description: 'Second template',
commonSkills: [],
commonTools: [],
examples: []
};
templateManager.addTemplate('test-key', template1);
expect(templateManager.getTemplate('test-key')?.domain).toBe('First');
templateManager.addTemplate('test-key', template2);
expect(templateManager.getTemplate('test-key')?.domain).toBe('Second');
expect(templateManager.getTemplate('test-key')?.version).toBe('v2');
});
});
describe("getTemplate", () => {
test("should return template for valid key", () => {
const customTemplate: DomainTemplate = {
version: 'v1',
domain: 'Test',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'Test template',
commonSkills: [],
commonTools: [],
examples: []
};
templateManager.addTemplate('test-template', customTemplate);
const retrieved = templateManager.getTemplate('test-template');
expect(retrieved).toBeDefined();
expect(retrieved?.domain).toBe('Test');
});
test("should return undefined for invalid key", () => {
const retrieved = templateManager.getTemplate('non-existent-key');
expect(retrieved).toBeUndefined();
});
});
describe("getTemplatesByVersion", () => {
test("should return only v1 templates", () => {
const v1Templates = templateManager.getTemplatesByVersion('v1');
expect(v1Templates.length).toBeGreaterThan(0);
v1Templates.forEach(template => {
expect(template.version).toBe('v1');
expect(template.structure).toHaveLength(4);
});
});
test("should return only v2 templates", () => {
const v2Templates = templateManager.getTemplatesByVersion('v2');
expect(v2Templates.length).toBeGreaterThan(0);
v2Templates.forEach(template => {
expect(template.version).toBe('v2');
expect(template.structure).toHaveLength(6);
});
});
test("should return empty array if no templates match version", () => {
// Since TemplateManager initializes with default templates,
// we can't easily test an empty scenario. Instead, let's test
// that filtering works correctly by checking that v1 and v2
// templates are properly separated
const v1Templates = templateManager.getTemplatesByVersion('v1');
const v2Templates = templateManager.getTemplatesByVersion('v2');
// Ensure no v2 templates are in v1 results
v1Templates.forEach(template => {
expect(template.version).toBe('v1');
});
// Ensure no v1 templates are in v2 results
v2Templates.forEach(template => {
expect(template.version).toBe('v2');
});
// Ensure they don't overlap
const allTemplates = templateManager.getAllTemplates();
expect(v1Templates.length + v2Templates.length).toBe(allTemplates.length);
});
});
describe("getTemplatesByDomain", () => {
test("should return templates matching domain (case insensitive)", () => {
const techTemplates = templateManager.getTemplatesByDomain('Technology');
expect(techTemplates.length).toBeGreaterThan(0);
const techTemplatesLower = templateManager.getTemplatesByDomain('technology');
expect(techTemplatesLower.length).toBe(techTemplates.length);
});
test("should return templates with partial domain match", () => {
const techTemplates = templateManager.getTemplatesByDomain('tech');
expect(techTemplates.length).toBeGreaterThan(0);
techTemplates.forEach(template => {
expect(template.domain.toLowerCase()).toContain('tech');
});
});
test("should return empty array for non-matching domain", () => {
const nonExistentTemplates = templateManager.getTemplatesByDomain('NonExistentDomain');
expect(nonExistentTemplates).toHaveLength(0);
});
test("should handle empty domain string", () => {
const allTemplates = templateManager.getAllTemplates();
const emptyDomainTemplates = templateManager.getTemplatesByDomain('');
expect(emptyDomainTemplates.length).toBe(allTemplates.length);
});
});
describe("getAllTemplates", () => {
test("should return all templates", () => {
const allTemplates = templateManager.getAllTemplates();
expect(allTemplates.length).toBeGreaterThan(0);
// Should include both v1 and v2 templates
const v1Count = allTemplates.filter(t => t.version === 'v1').length;
const v2Count = allTemplates.filter(t => t.version === 'v2').length;
expect(v1Count).toBeGreaterThan(0);
expect(v2Count).toBeGreaterThan(0);
expect(v1Count + v2Count).toBe(allTemplates.length);
});
test("should return array copy, not reference", () => {
const templates1 = templateManager.getAllTemplates();
const templates2 = templateManager.getAllTemplates();
expect(templates1).not.toBe(templates2); // Different array instances
expect(templates1).toEqual(templates2); // Same content
});
});
describe("createCustomTemplate", () => {
test("should create v1 template with correct structure", () => {
const template = templateManager.createCustomTemplate(
'custom-v1',
'CustomDomain',
'v1'
);
expect(template.version).toBe('v1');
expect(template.domain).toBe('CustomDomain');
expect(template.structure).toEqual(['Domain', 'Specialization', 'Role', 'Responsibility']);
expect(template.description).toBe('CustomDomain professional hierarchy');
expect(template.commonSkills).toEqual([]);
expect(template.commonTools).toEqual([]);
expect(template.examples).toEqual([]);
});
test("should create v2 template with correct structure", () => {
const template = templateManager.createCustomTemplate(
'custom-v2',
'CustomDomain',
'v2'
);
expect(template.version).toBe('v2');
expect(template.domain).toBe('CustomDomain');
expect(template.structure).toEqual(['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task']);
expect(template.description).toBe('CustomDomain professional hierarchy');
});
test("should apply custom options", () => {
const options = {
description: 'Custom description',
commonSkills: ['Skill1', 'Skill2'],
commonTools: ['Tool1', 'Tool2'],
examples: ['Example1', 'Example2']
};
const template = templateManager.createCustomTemplate(
'custom-with-options',
'TestDomain',
'v1',
options
);
expect(template.description).toBe('Custom description');
expect(template.commonSkills).toEqual(['Skill1', 'Skill2']);
expect(template.commonTools).toEqual(['Tool1', 'Tool2']);
expect(template.examples).toEqual(['Example1', 'Example2']);
});
test("should add created template to manager", () => {
const template = templateManager.createCustomTemplate(
'auto-added',
'AutoDomain',
'v1'
);
const retrieved = templateManager.getTemplate('auto-added');
expect(retrieved).toBeDefined();
expect(retrieved).toEqual(template);
});
test("should override default values with options", () => {
const template = templateManager.createCustomTemplate(
'override-test',
'OverrideDomain',
'v1',
{
structure: ['Custom', 'Structure', 'Override'], // This should override default v1 structure
description: 'Override description'
}
);
expect(template.structure).toEqual(['Custom', 'Structure', 'Override']);
expect(template.description).toBe('Override description');
expect(template.domain).toBe('OverrideDomain'); // Should keep the domain parameter
});
});
describe("template validation", () => {
test("all default templates should have required fields", () => {
const allTemplates = templateManager.getAllTemplates();
allTemplates.forEach(template => {
expect(template.version).toMatch(/^v[12]$/);
expect(template.domain).toBeTruthy();
expect(template.structure).toBeDefined();
expect(template.structure.length).toBeGreaterThan(0);
expect(template.description).toBeTruthy();
expect(Array.isArray(template.commonSkills)).toBe(true);
expect(Array.isArray(template.commonTools)).toBe(true);
expect(Array.isArray(template.examples)).toBe(true);
});
});
test("v1 templates should have 4-layer structure", () => {
const v1Templates = templateManager.getTemplatesByVersion('v1');
v1Templates.forEach(template => {
expect(template.structure).toHaveLength(4);
expect(template.structure).toContain('Domain');
expect(template.structure).toContain('Specialization');
expect(template.structure).toContain('Role');
expect(template.structure).toContain('Responsibility');
});
});
test("v2 templates should have 6-layer structure", () => {
const v2Templates = templateManager.getTemplatesByVersion('v2');
v2Templates.forEach(template => {
expect(template.structure).toHaveLength(6);
expect(template.structure).toContain('Domain');
expect(template.structure).toContain('Industry');
expect(template.structure).toContain('Profession');
expect(template.structure).toContain('Field');
expect(template.structure).toContain('Role');
expect(template.structure).toContain('Task');
});
});
});
});