agent-ecosystem/test/main/services/extensions/SkillValidator.test.ts

73 lines
2.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { SkillValidator } from '@main/services/extensions/skills/SkillValidator';
import type { SkillCatalogItem } from '@shared/types/extensions';
function makeSkill(overrides: Partial<SkillCatalogItem>): SkillCatalogItem {
return {
id: '/tmp/skill',
sourceType: 'filesystem',
name: 'demo',
description: 'demo',
folderName: 'demo',
scope: 'user',
rootKind: 'claude',
projectRoot: null,
discoveryRoot: '/tmp/.claude/skills',
skillDir: '/tmp/.claude/skills/demo',
skillFile: '/tmp/.claude/skills/demo/SKILL.md',
metadata: {},
invocationMode: 'auto',
flags: { hasScripts: false, hasReferences: false, hasAssets: false },
isValid: true,
issues: [],
modifiedAt: 1,
...overrides,
};
}
describe('SkillValidator', () => {
it('adds duplicate-name warnings across roots', () => {
const validator = new SkillValidator();
const result = validator.annotateCatalog([
makeSkill({ id: '/a', scope: 'project', rootKind: 'claude' }),
makeSkill({ id: '/b', scope: 'user', rootKind: 'cursor' }),
]);
expect(result[0].issues.map((issue) => issue.code)).toContain('duplicate-name');
expect(result[1].issues.map((issue) => issue.code)).toContain('duplicate-name');
});
it('does not warn when shared and codex-only overlays reuse the same skill name', () => {
const validator = new SkillValidator();
const result = validator.annotateCatalog([
makeSkill({ id: '/a', scope: 'project', rootKind: 'claude' }),
makeSkill({ id: '/b', scope: 'project', rootKind: 'codex' }),
]);
expect(result[0].issues.map((issue) => issue.code)).not.toContain('duplicate-name');
expect(result[1].issues.map((issue) => issue.code)).not.toContain('duplicate-name');
});
it('sorts by validity, scope, root precedence, then name', () => {
const validator = new SkillValidator();
const result = validator.annotateCatalog([
makeSkill({ id: '/3', name: 'z-user', scope: 'user', rootKind: 'claude' }),
makeSkill({ id: '/2', name: 'b-project-cursor', scope: 'project', rootKind: 'cursor' }),
makeSkill({ id: '/1', name: 'a-project-claude', scope: 'project', rootKind: 'claude' }),
makeSkill({ id: '/5', name: 'c-project-codex', scope: 'project', rootKind: 'codex' }),
makeSkill({
id: '/4',
name: 'invalid',
isValid: false,
issues: [{ code: 'missing-name', message: 'missing', severity: 'error' }],
}),
]);
expect(result.map((item) => item.id)).toEqual(['/1', '/2', '/5', '/3', '/4']);
});
});