basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2025 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, expect, it } from 'vitest';8import { normalizeDisabledToolList } from './normalizeDisabledTools.js';9 10describe('normalizeDisabledToolList', () => {11 describe('non-array short-circuit', () => {12 it('returns [] for undefined', () => {13 expect(normalizeDisabledToolList(undefined)).toEqual([]);14 });15 it('returns [] for null', () => {16 expect(normalizeDisabledToolList(null)).toEqual([]);17 });18 it('returns [] for a plain object', () => {19 expect(normalizeDisabledToolList({ 0: 'Foo' })).toEqual([]);20 });21 it('returns [] for a number', () => {22 expect(normalizeDisabledToolList(42)).toEqual([]);23 });24 it('returns [] for a string', () => {25 expect(normalizeDisabledToolList('Foo')).toEqual([]);26 });27 it('returns [] for a boolean', () => {28 expect(normalizeDisabledToolList(true)).toEqual([]);29 });30 });31 32 describe('typeof-string filter', () => {33 it('drops non-string entries individually without aborting', () => {34 expect(35 normalizeDisabledToolList([36 42,37 'Foo',38 null,39 'Bar',40 { name: 'Baz' },41 true,42 'Qux',43 ]),44 ).toEqual(['Foo', 'Bar', 'Qux']);45 });46 });47 48 describe('trim + empty-skip', () => {49 it('trims surrounding whitespace', () => {50 expect(normalizeDisabledToolList([' Foo ', '\tBar\n'])).toEqual([51 'Foo',52 'Bar',53 ]);54 });55 it('drops empty-after-trim entries', () => {56 expect(normalizeDisabledToolList(['', ' ', '\t', '\n', 'Foo'])).toEqual([57 'Foo',58 ]);59 });60 it('returns [] when every entry is whitespace-only', () => {61 expect(normalizeDisabledToolList(['', ' ', '\t', '\n'])).toEqual([]);62 });63 });64 65 describe('dedupe', () => {66 it('removes exact duplicates, preserving first-occurrence order', () => {67 expect(normalizeDisabledToolList(['Foo', 'Bar', 'Foo', 'Baz'])).toEqual([68 'Foo',69 'Bar',70 'Baz',71 ]);72 });73 it('dedupes after trim — whitespace variants collapse', () => {74 expect(normalizeDisabledToolList(['Foo', ' Foo', 'Foo '])).toEqual([75 'Foo',76 ]);77 });78 it('does NOT case-fold — `Foo` and `foo` stay distinct', () => {79 expect(normalizeDisabledToolList(['Foo', 'foo', 'FOO'])).toEqual([80 'Foo',81 'foo',82 'FOO',83 ]);84 });85 });86 87 describe('boot/restart parity scenarios (BkwQW class — wenshao #4329)', () => {88 it("['Foo', ' Foo ', ''] → ['Foo'] (the bug that the helper was extracted to prevent)", () => {89 // Pre-extraction, this scenario was handled at boot (config.ts) but90 // the MCP restart path (acpAgent.ts) had only typeof-string filter.91 // After fold-in, both call sites share this helper so a hand-edited92 // `tools.disabled: [' Foo ']` produces Set(['Foo']) at boot AND93 // after every subsequent MCP restart.94 expect(normalizeDisabledToolList(['Foo', ' Foo ', ''])).toEqual([95 'Foo',96 ]);97 });98 99 it('mixed real-world settings — typo + extra whitespace + dup', () => {100 expect(101 normalizeDisabledToolList([102 'ShellTool',103 'WebFetch',104 ' ShellTool', // typo: extra space105 '', // operator pressed Enter106 'WebFetch ', // trailing whitespace107 ]),108 ).toEqual(['ShellTool', 'WebFetch']);109 });110 });111 112 describe('order preservation', () => {113 it('first-occurrence order survives dedupe + trim', () => {114 expect(115 normalizeDisabledToolList(['Zebra', 'Apple', ' Zebra', 'Banana']),116 ).toEqual(['Zebra', 'Apple', 'Banana']);117 });118 });119});120 