chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
const StructuralValidator = require('../../src/validation/validators/StructuralValidator');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
describe('StructuralValidator', () => {
|
||||
let validator;
|
||||
|
||||
beforeEach(() => {
|
||||
validator = new StructuralValidator();
|
||||
});
|
||||
|
||||
describe('Valid Component', () => {
|
||||
it('should pass validation for a well-formed agent', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent for validation testing purposes
|
||||
tools: Read, Write, Edit
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
## Overview
|
||||
This is a test agent.
|
||||
|
||||
## Usage
|
||||
Use this agent for testing.
|
||||
`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test-agent.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
expect(result.score).toBeGreaterThan(90);
|
||||
});
|
||||
|
||||
it('should validate the real frontend-developer agent', async () => {
|
||||
const agentPath = path.join(
|
||||
__dirname,
|
||||
'../../components/agents/development-team/frontend-developer.md'
|
||||
);
|
||||
|
||||
if (!fs.existsSync(agentPath)) {
|
||||
console.log('Skipping test: frontend-developer.md not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await fs.readFile(agentPath, 'utf8');
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: agentPath,
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.errorCount).toBe(0);
|
||||
expect(result.score).toBeGreaterThan(80);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Frontmatter Validation', () => {
|
||||
it('should error when frontmatter is missing', async () => {
|
||||
const content = 'This is content without frontmatter';
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_E001'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when frontmatter YAML is invalid', async () => {
|
||||
const content = `---
|
||||
name: test
|
||||
description: test
|
||||
invalid: yaml: syntax:
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_E002'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when required fields are missing', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e => e.code === 'STRUCT_E006')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('File Size Validation', () => {
|
||||
it('should error when file size exceeds limit', async () => {
|
||||
const largeContent = `---
|
||||
name: large-agent
|
||||
description: This is a large test agent
|
||||
tools: Read
|
||||
---
|
||||
|
||||
${' Large content '.repeat(20000)}`; // Create >100KB file
|
||||
|
||||
const component = {
|
||||
content: largeContent,
|
||||
path: 'large.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_E003'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when file size approaches limit', async () => {
|
||||
// Create content that is ~85KB (80% of 100KB limit)
|
||||
const largeText = 'X'.repeat(5000); // 5KB chunk
|
||||
const mediumContent = `---
|
||||
name: medium-agent
|
||||
description: This is a medium test agent
|
||||
tools: Read
|
||||
---
|
||||
|
||||
${largeText.repeat(17)}`; // 85KB total
|
||||
|
||||
const component = {
|
||||
content: mediumContent,
|
||||
path: 'medium.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'STRUCT_W002')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Description Validation', () => {
|
||||
it('should warn when description is too short', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: Short
|
||||
tools: Read
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W003'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when description is too long', async () => {
|
||||
const longDescription = 'A '.repeat(300);
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: ${longDescription}
|
||||
tools: Read
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W004'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tools Validation', () => {
|
||||
it('should validate valid tools', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent with valid tools
|
||||
tools: Read, Write, Edit, Bash
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about unknown tools', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent with unknown tools
|
||||
tools: Read, UnknownTool, InvalidTool
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W006'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model Validation', () => {
|
||||
it('should warn when model is missing', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent without model
|
||||
tools: Read
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W007'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn about unknown model', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent with unknown model
|
||||
tools: Read
|
||||
model: unknown-model
|
||||
---
|
||||
Content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W008'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Content Structure Validation', () => {
|
||||
it('should warn when content is too short', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent with minimal content
|
||||
tools: Read
|
||||
---
|
||||
Short`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W009'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when no headers are present', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent without headers in content
|
||||
tools: Read
|
||||
---
|
||||
This is content without any markdown headers at all.
|
||||
It just has plain text and no structure.`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W010'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when too many sections', async () => {
|
||||
const manyHeaders = Array.from({ length: 25 }, (_, i) => `## Section ${i + 1}`).join('\n\n');
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent with too many sections
|
||||
tools: Read
|
||||
---
|
||||
${manyHeaders}`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.warnings).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_W011'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Encoding Validation', () => {
|
||||
it('should error when null bytes are present', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent with null bytes
|
||||
tools: Read
|
||||
---
|
||||
Content with \0 null byte`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: 'STRUCT_E005'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Score Calculation', () => {
|
||||
it('should calculate score correctly', async () => {
|
||||
const content = `---
|
||||
name: test-agent
|
||||
description: A test agent for score testing purposes
|
||||
tools: Read
|
||||
---
|
||||
|
||||
## Overview
|
||||
Good content`;
|
||||
|
||||
const component = {
|
||||
content,
|
||||
path: 'test.md',
|
||||
type: 'agent'
|
||||
};
|
||||
|
||||
const result = await validator.validate(component);
|
||||
|
||||
expect(result.score).toBeGreaterThanOrEqual(0);
|
||||
expect(result.score).toBeLessThanOrEqual(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user