import { describe, it, expect } from 'vitest'; import { detectDrift, parseAliases } from './l1-watcher.mjs'; describe('detectDrift', () => { it('finds plugins in settings but not in tooling', () => { const settings = { enabledPlugins: { 'foo@org': true, 'bar@org': true } }; const tooling = 'Описание #56 foo@org интегрирован.'; const drift = detectDrift(settings, tooling); expect(drift.missingInTooling).toEqual(['bar@org']); }); it('reports only the settings→Tooling direction (no reverse check)', () => { const settings = { enabledPlugins: { 'foo@org': true } }; const tooling = '#56 foo@org. #57 baz@org включён.'; const drift = detectDrift(settings, tooling); expect(drift).not.toHaveProperty('missingInSettings'); }); it('returns empty missingInTooling when in sync', () => { const settings = { enabledPlugins: { 'foo@org': true } }; const tooling = '#56 foo@org описан.'; const drift = detectDrift(settings, tooling); expect(drift.missingInTooling).toEqual([]); }); it('handles disabled plugins (value false)', () => { const settings = { enabledPlugins: { 'foo@org': false, 'bar@org': true } }; const tooling = '#56 bar@org.'; const drift = detectDrift(settings, tooling); expect(drift.missingInTooling).toEqual([]); }); it('uses alias to match plugin under group/human name in tooling', () => { const settings = { enabledPlugins: { 'frontend-design@claude-plugins-official': true }, }; const tooling = '#30 Frontend Design plugin (paired stack)'; const aliases = { 'frontend-design@claude-plugins-official': 'Frontend Design plugin', }; const drift = detectDrift(settings, tooling, aliases); expect(drift.missingInTooling).toEqual([]); }); it('several plugins share one alias (Trail of Bits group)', () => { const settings = { enabledPlugins: { 'differential-review@trailofbits': true, 'sharp-edges@trailofbits': true, }, }; const tooling = '#39 Trail of Bits Skills (субсет 8 audit-плагинов)'; const aliases = { 'differential-review@trailofbits': 'Trail of Bits Skills', 'sharp-edges@trailofbits': 'Trail of Bits Skills', }; const drift = detectDrift(settings, tooling, aliases); expect(drift.missingInTooling).toEqual([]); }); it('falls back to direct match when no alias defined', () => { const settings = { enabledPlugins: { 'foo@org': true } }; const tooling = 'no foo here'; const drift = detectDrift(settings, tooling, {}); expect(drift.missingInTooling).toEqual(['foo@org']); }); it('alias does NOT save a plugin when alias substring is also absent', () => { const settings = { enabledPlugins: { 'foo@org': true } }; const tooling = 'unrelated text'; const aliases = { 'foo@org': 'Some Group Name' }; const drift = detectDrift(settings, tooling, aliases); expect(drift.missingInTooling).toEqual(['foo@org']); }); }); describe('parseAliases', () => { it('parses key=value lines and ignores comments and blanks', () => { const raw = [ '# header comment', '', 'frontend-design@claude-plugins-official=Frontend Design plugin', ' ', '# another comment', 'sharp-edges@trailofbits=Trail of Bits Skills', '', ].join('\n'); const aliases = parseAliases(raw); expect(aliases).toEqual({ 'frontend-design@claude-plugins-official': 'Frontend Design plugin', 'sharp-edges@trailofbits': 'Trail of Bits Skills', }); }); it('returns empty object on empty/null input', () => { expect(parseAliases('')).toEqual({}); expect(parseAliases(null)).toEqual({}); expect(parseAliases(undefined)).toEqual({}); }); it('trims whitespace around key and value', () => { const raw = ' foo@org = Bar Name '; expect(parseAliases(raw)).toEqual({ 'foo@org': 'Bar Name' }); }); it('handles values that contain = sign (only splits on first =)', () => { const raw = 'a@b=v1=v2'; expect(parseAliases(raw)).toEqual({ 'a@b': 'v1=v2' }); }); }); import { loadInputs } from './l1-watcher.mjs'; describe('loadInputs — tool_registry_path (Task 7 follow-up)', () => { it('toolRegistryPath пуст → toolingPresent:false (skip-сигнал)', () => { expect(loadInputs(process.cwd(), '').toolingPresent).toBe(false); }); });