Files
portal/tools/l1-watcher.test.mjs
T
Дмитрий 2a2ded7a53 refactor(brain): C1 L1-watcher — drop broken reverse drift check
Removes the `missingInSettings` reverse check ("plugin documented in
Tooling but disabled in settings.json"). It was broken by design:
Tooling Прил. Н lists tools by human/group name ("Frontend Design
plugin", "Trail of Bits Skills") while settings.json keys are machine
IDs (`name@marketplace`) — the two namespaces never compare. The
`/#\d+\s+([\w-]+(?:@[\w-]+)?)/` scan also captured the first plain word
after "#NN" ("#1 PostgreSQL MCP" → "PostgreSQL"), so every run emitted
~190 lines of WARN noise.

ADR-011 §6.1 specifies only the settings→Tooling direction (the L1
pattern "plugin enabled without Tooling formalization"). That is the
FAIL path and is unchanged. detectDrift now returns `{ missingInTooling }`
only. CLI output is a clean single line on success.

Closes the cosmetic issue flagged in bffdaa9.

TDD: reverse-check test replaced with `not.toHaveProperty
('missingInSettings')`; 12/12 GREEN. Smoke: node tools/l1-watcher.mjs
-> exit 0, "OK — 0 drift" (no WARN block).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 07:36:21 +03:00

111 lines
4.0 KiB
JavaScript

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' });
});
});