mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 16:19:12 +00:00
375 lines
10 KiB
TypeScript
375 lines
10 KiB
TypeScript
import { expect, test, describe } from 'bun:test';
|
|
import {
|
|
isCcsWebSearchHook,
|
|
deduplicateCcsHooks,
|
|
} from '../../../../src/utils/websearch/hook-utils';
|
|
|
|
describe('isCcsWebSearchHook', () => {
|
|
test('Returns true for CCS hook with forward slashes (Unix path)', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(true);
|
|
});
|
|
|
|
test('Returns true for CCS hook with backslashes (Windows path)', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node C:\\Users\\user\\.ccs\\hooks\\websearch-transformer\\index.js',
|
|
},
|
|
],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(true);
|
|
});
|
|
|
|
test('Returns true for mixed path separators', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user\\.ccs/hooks\\websearch-transformer/index.js',
|
|
},
|
|
],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(true);
|
|
});
|
|
|
|
test('Returns true for path with double slashes (normalization)', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user//.ccs//hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(true);
|
|
});
|
|
|
|
test('Returns false for non-WebSearch matcher', () => {
|
|
const hook = {
|
|
matcher: 'SomethingElse',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(false);
|
|
});
|
|
|
|
test('Returns false for WebSearch with non-CCS hook command', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /some/other/path/custom-hook.js',
|
|
},
|
|
],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(false);
|
|
});
|
|
|
|
test('Returns false when hooks array is missing', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(false);
|
|
});
|
|
|
|
test('Returns false when hooks array is empty', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(false);
|
|
});
|
|
|
|
test('Returns false when command is missing', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [{}],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(false);
|
|
});
|
|
|
|
test('Returns false when command is not a string', () => {
|
|
const hook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 123,
|
|
},
|
|
],
|
|
};
|
|
expect(isCcsWebSearchHook(hook)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('deduplicateCcsHooks', () => {
|
|
test('No-op when 0 CCS hooks (returns false)', () => {
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: [
|
|
{
|
|
matcher: 'SomeOtherMatcher',
|
|
hooks: [{ command: 'other-command' }],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(false);
|
|
expect(settings.hooks.PreToolUse).toHaveLength(1);
|
|
});
|
|
|
|
test('No-op when 1 CCS hook (returns false)', () => {
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: [
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(false);
|
|
expect(settings.hooks.PreToolUse).toHaveLength(1);
|
|
});
|
|
|
|
test('Removes duplicates when 2+ CCS hooks (returns true, keeps first)', () => {
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: [
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node C:\\Users\\user\\.ccs\\hooks\\websearch-transformer\\index.js',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /another/path/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(true);
|
|
expect(settings.hooks.PreToolUse).toHaveLength(1);
|
|
expect(settings.hooks.PreToolUse[0]).toEqual({
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
test('Preserves non-CCS hooks in array', () => {
|
|
const nonCcsHook = {
|
|
matcher: 'SomeOtherMatcher',
|
|
hooks: [{ command: 'other-command' }],
|
|
};
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: [
|
|
nonCcsHook,
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node C:\\Users\\user\\.ccs\\hooks\\websearch-transformer\\index.js',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(true);
|
|
expect(settings.hooks.PreToolUse).toHaveLength(2);
|
|
expect(settings.hooks.PreToolUse[0]).toEqual(nonCcsHook);
|
|
});
|
|
|
|
test('Returns false when hooks is undefined', () => {
|
|
const settings = {};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
test('Returns false when PreToolUse is undefined', () => {
|
|
const settings = {
|
|
hooks: {},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
test('Handles multiple non-CCS hooks with duplicates', () => {
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: [
|
|
{
|
|
matcher: 'OtherMatcher1',
|
|
hooks: [{ command: 'command1' }],
|
|
},
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /path1/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
matcher: 'OtherMatcher2',
|
|
hooks: [{ command: 'command2' }],
|
|
},
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /path2/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(true);
|
|
expect(settings.hooks.PreToolUse).toHaveLength(3);
|
|
// First and third should be non-CCS hooks, second should be the first CCS hook
|
|
expect(settings.hooks.PreToolUse[0].matcher).toBe('OtherMatcher1');
|
|
expect(settings.hooks.PreToolUse[1].matcher).toBe('WebSearch');
|
|
expect(settings.hooks.PreToolUse[2].matcher).toBe('OtherMatcher2');
|
|
});
|
|
|
|
test('Edge case: Empty PreToolUse array', () => {
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: [],
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(false);
|
|
expect(settings.hooks.PreToolUse).toHaveLength(0);
|
|
});
|
|
|
|
test('Stress test: 15 duplicate CCS WebSearch hooks', () => {
|
|
const ccsHook = {
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /home/user/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
};
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: Array(15).fill(ccsHook),
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(true);
|
|
expect(settings.hooks.PreToolUse).toHaveLength(1);
|
|
expect(settings.hooks.PreToolUse[0]).toEqual(ccsHook);
|
|
});
|
|
|
|
test('Leaves PostToolUse and PreToolCall untouched', () => {
|
|
const postToolUseHook1 = {
|
|
matcher: 'CustomMatcher1',
|
|
hooks: [{ command: 'custom-command-1' }],
|
|
};
|
|
const postToolUseHook2 = {
|
|
matcher: 'CustomMatcher2',
|
|
hooks: [{ command: 'custom-command-2' }],
|
|
};
|
|
const preToolCallHook = {
|
|
matcher: 'CustomMatcher3',
|
|
hooks: [{ command: 'custom-command-3' }],
|
|
};
|
|
const settings = {
|
|
hooks: {
|
|
PreToolUse: [
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /path1/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /path2/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
matcher: 'WebSearch',
|
|
hooks: [
|
|
{
|
|
command: 'node /path3/.ccs/hooks/websearch-transformer/index.js',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
PostToolUse: [postToolUseHook1, postToolUseHook2],
|
|
PreToolCall: [preToolCallHook],
|
|
},
|
|
};
|
|
const result = deduplicateCcsHooks(settings);
|
|
expect(result).toBe(true);
|
|
// PreToolUse should be deduplicated to 1 hook
|
|
expect(settings.hooks.PreToolUse).toHaveLength(1);
|
|
expect(settings.hooks.PreToolUse[0].matcher).toBe('WebSearch');
|
|
// PostToolUse should remain unchanged with 2 hooks
|
|
expect(settings.hooks.PostToolUse).toHaveLength(2);
|
|
expect(settings.hooks.PostToolUse[0]).toEqual(postToolUseHook1);
|
|
expect(settings.hooks.PostToolUse[1]).toEqual(postToolUseHook2);
|
|
// PreToolCall should remain unchanged with 1 hook
|
|
expect(settings.hooks.PreToolCall).toHaveLength(1);
|
|
expect(settings.hooks.PreToolCall[0]).toEqual(preToolCallHook);
|
|
});
|
|
});
|