Initial release v1.0.0
Open-source marketplace for AI Agent skills. Features: - Next.js 15 web app with i18n (en/fa) - CLI tool for skill installation (npx skillhub) - GitHub crawler/indexer with multi-strategy discovery - Security scanning for all indexed skills - Self-hostable with Docker Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
191
packages/core/src/validator.test.ts
Normal file
191
packages/core/src/validator.test.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateSkill, isValidSkill, formatValidationSummary } from './validator.js';
|
||||
import type { ParsedSkill } from './types.js';
|
||||
|
||||
// Helper function to create a mock parsed skill
|
||||
function createMockSkill(overrides: Partial<ParsedSkill> = {}): ParsedSkill {
|
||||
return {
|
||||
metadata: {
|
||||
name: 'test-skill',
|
||||
description: 'A test skill',
|
||||
version: '1.0.0',
|
||||
license: 'MIT',
|
||||
compatibility: {
|
||||
platforms: ['claude', 'codex'],
|
||||
},
|
||||
...overrides.metadata,
|
||||
},
|
||||
content: `# Test Skill
|
||||
|
||||
This is a test skill with good content structure.
|
||||
|
||||
## Usage
|
||||
|
||||
Here's how to use this skill with an example:
|
||||
|
||||
\`\`\`bash
|
||||
npx skillhub install test/test-skill
|
||||
\`\`\`
|
||||
`,
|
||||
resources: {
|
||||
scripts: [],
|
||||
references: [],
|
||||
...overrides.resources,
|
||||
},
|
||||
validation: {
|
||||
isValid: true,
|
||||
errors: [],
|
||||
warnings: [],
|
||||
...overrides.validation,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('validateSkill', () => {
|
||||
it('should return valid for a well-formed skill', () => {
|
||||
const skill = createMockSkill();
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should detect empty content', () => {
|
||||
const skill = createMockSkill({ content: '' });
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors.some(e => e.code === 'EMPTY_CONTENT')).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about short content', () => {
|
||||
const skill = createMockSkill({ content: 'Very short.' });
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'CONTENT_TOO_SHORT')).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about placeholder content', () => {
|
||||
const skill = createMockSkill({
|
||||
content: `# Test Skill
|
||||
|
||||
TODO: Add more content here.
|
||||
|
||||
## Usage
|
||||
|
||||
FIXME: Write usage instructions.
|
||||
`,
|
||||
});
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'PLACEHOLDER_CONTENT')).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about missing version', () => {
|
||||
const skill = createMockSkill({
|
||||
metadata: {
|
||||
name: 'test-skill',
|
||||
description: 'A test skill',
|
||||
version: undefined,
|
||||
},
|
||||
});
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'NO_VERSION')).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about missing license', () => {
|
||||
const skill = createMockSkill({
|
||||
metadata: {
|
||||
name: 'test-skill',
|
||||
description: 'A test skill',
|
||||
license: undefined,
|
||||
},
|
||||
});
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'NO_LICENSE')).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about missing platforms', () => {
|
||||
const skill = createMockSkill({
|
||||
metadata: {
|
||||
name: 'test-skill',
|
||||
description: 'A test skill',
|
||||
compatibility: {},
|
||||
},
|
||||
});
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'NO_PLATFORMS')).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about unusual script extensions', () => {
|
||||
const skill = createMockSkill({
|
||||
resources: {
|
||||
scripts: [{ name: 'script.xyz', content: 'echo hello' }],
|
||||
references: [],
|
||||
},
|
||||
});
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'UNUSUAL_SCRIPT_EXT')).toBe(true);
|
||||
});
|
||||
|
||||
it('should warn about large reference files', () => {
|
||||
const skill = createMockSkill({
|
||||
resources: {
|
||||
scripts: [],
|
||||
references: [{ name: 'huge-file.md', content: 'x'.repeat(150000) }],
|
||||
},
|
||||
});
|
||||
const result = validateSkill(skill);
|
||||
|
||||
expect(result.warnings.some(w => w.code === 'LARGE_REFERENCE')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidSkill', () => {
|
||||
it('should return true for valid skills', () => {
|
||||
const skill = createMockSkill();
|
||||
expect(isValidSkill(skill)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid skills', () => {
|
||||
const skill = createMockSkill({
|
||||
validation: {
|
||||
isValid: false,
|
||||
errors: [{ code: 'MISSING_NAME', message: 'Name is required' }],
|
||||
warnings: [],
|
||||
},
|
||||
});
|
||||
expect(isValidSkill(skill)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatValidationSummary', () => {
|
||||
it('should format valid skill summary', () => {
|
||||
const result = {
|
||||
isValid: true,
|
||||
errors: [],
|
||||
warnings: [],
|
||||
};
|
||||
const summary = formatValidationSummary(result);
|
||||
|
||||
expect(summary).toContain('Skill is valid');
|
||||
});
|
||||
|
||||
it('should format errors and warnings', () => {
|
||||
const result = {
|
||||
isValid: false,
|
||||
errors: [{ code: 'EMPTY_CONTENT', message: 'Content is empty' }],
|
||||
warnings: [{ code: 'NO_LICENSE', message: 'No license', suggestion: 'Add MIT' }],
|
||||
};
|
||||
const summary = formatValidationSummary(result);
|
||||
|
||||
expect(summary).toContain('ERROR: Content is empty');
|
||||
expect(summary).toContain('WARNING: No license');
|
||||
expect(summary).toContain('Suggestion: Add MIT');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user