mirror of
https://github.com/seemueller-io/sumpin.git
synced 2025-09-08 22:56:46 +00:00
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:
394
lib/__tests__/agent-wrapper.test.ts
Normal file
394
lib/__tests__/agent-wrapper.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
});
|
220
lib/__tests__/hierarchy-generator.test.ts
Normal file
220
lib/__tests__/hierarchy-generator.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
162
lib/__tests__/hierarchy-model.test.ts
Normal file
162
lib/__tests__/hierarchy-model.test.ts
Normal 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");
|
||||
});
|
||||
});
|
487
lib/__tests__/output-formatter.test.ts
Normal file
487
lib/__tests__/output-formatter.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
327
lib/__tests__/template-manager.test.ts
Normal file
327
lib/__tests__/template-manager.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user