Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
188 lines
No EOL
5.7 KiB
JavaScript
188 lines
No EOL
5.7 KiB
JavaScript
/**
|
|
* Tests for Labs Service
|
|
* @see ADR-043: Laboratories Feature
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { LabsService } from '../index.js';
|
|
|
|
describe('LabsService', () => {
|
|
describe('getNodeTypes', () => {
|
|
it('should return all node types', () => {
|
|
const types = LabsService.getNodeTypes();
|
|
expect(types).toHaveLength(8);
|
|
|
|
const typeKeys = types.map(t => t.typeKey);
|
|
expect(typeKeys).toContain('text');
|
|
expect(typeKeys).toContain('ai_agent');
|
|
expect(typeKeys).toContain('image');
|
|
expect(typeKeys).toContain('file');
|
|
expect(typeKeys).toContain('input');
|
|
expect(typeKeys).toContain('output');
|
|
expect(typeKeys).toContain('code');
|
|
expect(typeKeys).toContain('note');
|
|
});
|
|
});
|
|
|
|
describe('getNodeType', () => {
|
|
it('should return specific node type', () => {
|
|
const textNode = LabsService.getNodeType('text');
|
|
expect(textNode).toBeDefined();
|
|
expect(textNode.typeKey).toBe('text');
|
|
expect(textNode.name).toBe('Text');
|
|
});
|
|
|
|
it('should return null for unknown type', () => {
|
|
const unknownNode = LabsService.getNodeType('unknown');
|
|
expect(unknownNode).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('validateNodeConfig', () => {
|
|
it('should validate text node config', () => {
|
|
const result = LabsService.validateNodeConfig('text', {
|
|
fontSize: 16,
|
|
fontWeight: 'bold'
|
|
});
|
|
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should reject invalid config', () => {
|
|
const result = LabsService.validateNodeConfig('text', {
|
|
fontSize: 100 // Too large
|
|
});
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('fontSize must be a number between 8 and 72');
|
|
});
|
|
});
|
|
|
|
describe('createNodeWithDefaults', () => {
|
|
it('should create text node with defaults', () => {
|
|
const nodeData = LabsService.createNodeWithDefaults('text', {
|
|
title: 'My Text Node',
|
|
content: 'Hello world'
|
|
});
|
|
|
|
expect(nodeData.type_key).toBe('text');
|
|
expect(nodeData.title).toBe('My Text Node');
|
|
expect(nodeData.content).toBe('Hello world');
|
|
expect(nodeData.width).toBe(250); // Default from TextNode
|
|
expect(nodeData.height).toBe(150); // Default from TextNode
|
|
expect(nodeData.node_id).toBeDefined();
|
|
expect(nodeData.config).toEqual({
|
|
content: '',
|
|
fontSize: 14,
|
|
fontWeight: 'normal',
|
|
textAlign: 'left'
|
|
});
|
|
});
|
|
|
|
it('should merge custom config with defaults', () => {
|
|
const nodeData = LabsService.createNodeWithDefaults('text', {
|
|
title: 'Custom Node',
|
|
config: {
|
|
fontSize: 18,
|
|
customField: 'value'
|
|
}
|
|
});
|
|
|
|
expect(nodeData.config.fontSize).toBe(18); // Custom value
|
|
expect(nodeData.config.fontWeight).toBe('normal'); // Default value
|
|
expect(nodeData.config.customField).toBe('value'); // Custom field preserved
|
|
});
|
|
|
|
it('should throw error for unknown node type', () => {
|
|
expect(() => {
|
|
LabsService.createNodeWithDefaults('unknown_type', {});
|
|
}).toThrow('Unknown node type: unknown_type');
|
|
});
|
|
});
|
|
|
|
describe('validateNode', () => {
|
|
it('should validate complete node data', () => {
|
|
const nodeData = {
|
|
type_key: 'text',
|
|
title: 'Valid Node',
|
|
content: 'Content',
|
|
position_x: 100,
|
|
position_y: 200,
|
|
width: 300,
|
|
height: 150,
|
|
config: {
|
|
fontSize: 16
|
|
}
|
|
};
|
|
|
|
const result = LabsService.validateNode(nodeData);
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should reject node without type_key', () => {
|
|
const nodeData = {
|
|
title: 'Invalid Node'
|
|
};
|
|
|
|
const result = LabsService.validateNode(nodeData);
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('type_key is required');
|
|
});
|
|
|
|
it('should reject node with invalid dimensions', () => {
|
|
const nodeData = {
|
|
type_key: 'text',
|
|
title: 'Invalid Node',
|
|
width: -100,
|
|
height: 0
|
|
};
|
|
|
|
const result = LabsService.validateNode(nodeData);
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('width must be a positive number');
|
|
expect(result.errors).toContain('height must be a positive number');
|
|
});
|
|
});
|
|
|
|
describe('executeNode', () => {
|
|
it('should execute text node', async () => {
|
|
const node = {
|
|
type_key: 'text',
|
|
content: 'Test content',
|
|
config: {}
|
|
};
|
|
|
|
const result = await LabsService.executeNode(node);
|
|
expect(result.success).toBe(true);
|
|
expect(result.output).toBe('Test content');
|
|
expect(result.type).toBe('text');
|
|
});
|
|
|
|
it('should execute AI agent node (returns error in test environment)', async () => {
|
|
const node = {
|
|
type_key: 'ai_agent',
|
|
config: {
|
|
ai_agent_id: 123
|
|
}
|
|
};
|
|
|
|
const result = await LabsService.executeNode(node);
|
|
// In test environment, either agent doesn't exist or DB query fails
|
|
expect(result.success).toBe(false);
|
|
expect(result.type).toBe('ai_agent');
|
|
// Error can be "AI agent 123 not found" or "AI agent execution failed"
|
|
expect(result.error).toMatch(/AI agent/);
|
|
});
|
|
|
|
it('should handle execution errors gracefully', async () => {
|
|
const node = {
|
|
type_key: 'unknown_type'
|
|
};
|
|
|
|
const result = await LabsService.executeNode(node);
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toBe('Unknown node type: unknown_type');
|
|
});
|
|
});
|
|
}); |