Files
brain/tools/capability-vocabulary.test.mjs
T

90 lines
3.7 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { validateVocabulary, loadVocabulary, unknownTokens } from './capability-vocabulary.mjs';
describe('validateVocabulary — форма словаря', () => {
const good = { version: '0.1.0', tokens: [
{ token: 'dast-report', label: 'отчёт DAST', description: 'результат динамики' },
{ token: 'stride-model', label: 'STRIDE', description: 'модель угроз' },
] };
it('валидный словарь → ok + Set токенов', () => {
const r = validateVocabulary(good);
expect(r.ok).toBe(true);
expect(r.errors).toEqual([]);
expect(r.tokens.has('dast-report')).toBe(true);
expect(r.tokens.size).toBe(2);
});
it('не-объект / нет tokens-массива → ошибка', () => {
expect(validateVocabulary(null).ok).toBe(false);
expect(validateVocabulary({ version: '1' }).ok).toBe(false);
});
it('токен не kebab-case → ошибка', () => {
const r = validateVocabulary({ tokens: [{ token: 'DAST_Report', label: 'x', description: 'y' }] });
expect(r.ok).toBe(false);
expect(r.errors.join(' ')).toMatch(/kebab-case/);
});
it('дубль токена → ошибка', () => {
const r = validateVocabulary({ tokens: [
{ token: 'a-b', label: 'x', description: 'y' },
{ token: 'a-b', label: 'x2', description: 'y2' },
] });
expect(r.ok).toBe(false);
expect(r.errors.join(' ')).toMatch(/duplicate/);
});
it('пустой label/description → ошибка', () => {
const r = validateVocabulary({ tokens: [{ token: 'a-b', label: '', description: '' }] });
expect(r.ok).toBe(false);
});
});
describe('loadVocabulary — fs-инъекция', () => {
it('читает файл и возвращает валидный словарь', () => {
const stub = { readFileSync: () => JSON.stringify({ tokens: [{ token: 'a-b', label: 'x', description: 'y' }] }) };
const r = loadVocabulary({ path: 'fake.json', fsImpl: stub });
expect(r.ok).toBe(true);
expect(r.tokens.has('a-b')).toBe(true);
});
it('бросает при битом JSON', () => {
const stub = { readFileSync: () => 'not-json' };
expect(() => loadVocabulary({ path: 'bad.json', fsImpl: stub })).toThrow();
});
});
describe('unknownTokens — сверка токенов контракта со словарём', () => {
const set = new Set(['running-portal', 'dast-report']);
it('все токены в словаре → пусто', () => {
const c = { needs: ['running-portal'], produces: ['dast-report'] };
expect(unknownTokens(c, set)).toEqual([]);
});
it('неизвестный токен в needs → запись {field, token}', () => {
const c = { needs: ['no-such-token'], produces: ['dast-report'] };
expect(unknownTokens(c, set)).toEqual([{ field: 'needs', token: 'no-such-token' }]);
});
it('неизвестный токен в produces → запись', () => {
const c = { needs: ['running-portal'], produces: ['ghost'] };
expect(unknownTokens(c, set)).toEqual([{ field: 'produces', token: 'ghost' }]);
});
it('пустые needs/produces → пусто (нечего сверять)', () => {
expect(unknownTokens({ needs: [], produces: [] }, set)).toEqual([]);
});
it('contract null/undefined → пусто (защита optional-chain)', () => {
expect(unknownTokens(null, set)).toEqual([]);
expect(unknownTokens(undefined, set)).toEqual([]);
});
it('tokenSet как массив (не Set) → нормализуется', () => {
const c = { needs: ['running-portal'], produces: ['ghost'] };
expect(unknownTokens(c, ['running-portal'])).toEqual([{ field: 'produces', token: 'ghost' }]);
});
});