90 lines
3.7 KiB
JavaScript
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' }]);
|
|
});
|
|
});
|