Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
273 lines
10 KiB
JavaScript
273 lines
10 KiB
JavaScript
/**
|
|
* ADR-091 Phase 2 / Ticket #41161 (AC17): Unit tests for response-mode utilities
|
|
*
|
|
* Tests for isMessageRelevantToAgent() and extractKeywords().
|
|
* These functions power the `topic_only` response mode, where an agent
|
|
* only auto-responds when the message matches its area of expertise.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { isMessageRelevantToAgent, extractKeywords, STOP_WORDS } from '../response-mode.js';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// extractKeywords
|
|
// ---------------------------------------------------------------------------
|
|
describe('extractKeywords', () => {
|
|
it('extracts meaningful words from plain text', () => {
|
|
const keywords = extractKeywords('The quick brown fox jumps over the lazy dog');
|
|
expect(keywords).toContain('quick');
|
|
expect(keywords).toContain('brown');
|
|
expect(keywords).toContain('fox');
|
|
expect(keywords).toContain('jumps');
|
|
expect(keywords).toContain('lazy');
|
|
expect(keywords).toContain('dog');
|
|
});
|
|
|
|
it('filters out common stop words', () => {
|
|
const keywords = extractKeywords('the a an is are was were be been being have has had');
|
|
expect(keywords).toEqual([]);
|
|
});
|
|
|
|
it('filters out words shorter than 3 characters', () => {
|
|
const keywords = extractKeywords('AI is an ok ML tool');
|
|
// 'tool' is the only word with 3+ chars that is not a stop word
|
|
expect(keywords).toContain('tool');
|
|
expect(keywords).not.toContain('AI');
|
|
expect(keywords).not.toContain('is');
|
|
expect(keywords).not.toContain('an');
|
|
expect(keywords).not.toContain('ok');
|
|
expect(keywords).not.toContain('ML');
|
|
});
|
|
|
|
it('returns lowercased keywords', () => {
|
|
const keywords = extractKeywords('JavaScript TypeScript React');
|
|
expect(keywords).toContain('javascript');
|
|
expect(keywords).toContain('typescript');
|
|
expect(keywords).toContain('react');
|
|
});
|
|
|
|
it('de-duplicates keywords', () => {
|
|
const keywords = extractKeywords('sales sales Sales SALES');
|
|
const salesCount = keywords.filter(k => k === 'sales').length;
|
|
expect(salesCount).toBe(1);
|
|
});
|
|
|
|
it('handles punctuation and special characters', () => {
|
|
const keywords = extractKeywords('CRM-based, project! management (system)');
|
|
expect(keywords).toContain('crm');
|
|
expect(keywords).toContain('based');
|
|
expect(keywords).toContain('project');
|
|
expect(keywords).toContain('management');
|
|
expect(keywords).toContain('system');
|
|
});
|
|
|
|
it('returns empty array for null/undefined/empty input', () => {
|
|
expect(extractKeywords(null)).toEqual([]);
|
|
expect(extractKeywords(undefined)).toEqual([]);
|
|
expect(extractKeywords('')).toEqual([]);
|
|
});
|
|
|
|
it('returns empty array for non-string input', () => {
|
|
expect(extractKeywords(123)).toEqual([]);
|
|
expect(extractKeywords({})).toEqual([]);
|
|
});
|
|
|
|
it('handles text with only stop words', () => {
|
|
expect(extractKeywords('the and for with from')).toEqual([]);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// STOP_WORDS set
|
|
// ---------------------------------------------------------------------------
|
|
describe('STOP_WORDS', () => {
|
|
it('is a non-empty Set', () => {
|
|
expect(STOP_WORDS).toBeInstanceOf(Set);
|
|
expect(STOP_WORDS.size).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('contains common English stop words', () => {
|
|
expect(STOP_WORDS.has('the')).toBe(true);
|
|
expect(STOP_WORDS.has('and')).toBe(true);
|
|
expect(STOP_WORDS.has('for')).toBe(true);
|
|
expect(STOP_WORDS.has('with')).toBe(true);
|
|
});
|
|
|
|
it('contains agent-domain generic words', () => {
|
|
expect(STOP_WORDS.has('agent')).toBe(true);
|
|
expect(STOP_WORDS.has('assistant')).toBe(true);
|
|
expect(STOP_WORDS.has('help')).toBe(true);
|
|
expect(STOP_WORDS.has('respond')).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// isMessageRelevantToAgent
|
|
// ---------------------------------------------------------------------------
|
|
describe('isMessageRelevantToAgent', () => {
|
|
// --- Matching cases ---
|
|
|
|
it('returns true when message contains keyword from agent description', () => {
|
|
const agent = {
|
|
name: 'Sales Bot',
|
|
description: 'Handles sales pipeline and revenue tracking',
|
|
system_prompt: 'You are a sales assistant.'
|
|
};
|
|
expect(isMessageRelevantToAgent('What is our current revenue?', agent)).toBe(true);
|
|
});
|
|
|
|
it('returns true when message contains keyword from agent name', () => {
|
|
const agent = {
|
|
name: 'Billing Support',
|
|
description: 'Answers billing questions'
|
|
};
|
|
expect(isMessageRelevantToAgent('I need billing information', agent)).toBe(true);
|
|
});
|
|
|
|
it('returns true when message contains keyword from system_prompt', () => {
|
|
const agent = {
|
|
name: 'Code Helper',
|
|
description: 'Helps with code',
|
|
system_prompt: 'You specialize in JavaScript, TypeScript, and Python programming.'
|
|
};
|
|
expect(isMessageRelevantToAgent('How do I write a function in Python?', agent)).toBe(true);
|
|
});
|
|
|
|
it('returns true when message contains keyword from main_instructions', () => {
|
|
const agent = {
|
|
name: 'HR Bot',
|
|
main_instructions: 'Handle recruitment, onboarding, and payroll inquiries.'
|
|
};
|
|
expect(isMessageRelevantToAgent('When is my next payroll date?', agent)).toBe(true);
|
|
});
|
|
|
|
it('returns true when message contains keyword from tags (array)', () => {
|
|
const agent = {
|
|
name: 'Data Bot',
|
|
description: 'Data tasks',
|
|
tags: ['analytics', 'reporting', 'dashboards']
|
|
};
|
|
expect(isMessageRelevantToAgent('Can you build me a dashboard for reporting?', agent)).toBe(true);
|
|
});
|
|
|
|
it('returns true when message contains keyword from tags (string)', () => {
|
|
const agent = {
|
|
name: 'Ops Bot',
|
|
description: 'Operations',
|
|
tags: 'infrastructure deployment monitoring'
|
|
};
|
|
expect(isMessageRelevantToAgent('Check the monitoring status', agent)).toBe(true);
|
|
});
|
|
|
|
it('is case-insensitive', () => {
|
|
const agent = {
|
|
name: 'Marketing Agent',
|
|
description: 'SEO and Content Marketing campaigns'
|
|
};
|
|
expect(isMessageRelevantToAgent('Tell me about SEO best practices', agent)).toBe(true);
|
|
expect(isMessageRelevantToAgent('tell me about seo best practices', agent)).toBe(true);
|
|
expect(isMessageRelevantToAgent('TELL ME ABOUT SEO BEST PRACTICES', agent)).toBe(true);
|
|
});
|
|
|
|
// --- Non-matching cases ---
|
|
|
|
it('returns false when message has no overlap with agent keywords', () => {
|
|
const agent = {
|
|
name: 'Sales Bot',
|
|
description: 'Handles sales pipeline and revenue tracking',
|
|
system_prompt: 'You are a sales assistant.'
|
|
};
|
|
expect(isMessageRelevantToAgent('What is the weather today?', agent)).toBe(false);
|
|
});
|
|
|
|
it('returns false when message contains only stop words that match', () => {
|
|
const agent = {
|
|
name: 'The Helper Agent',
|
|
description: 'An assistant that helps with things'
|
|
};
|
|
// "the", "helper", "agent", "assistant", "help", "things" -- "helper" and "things" are not stop words
|
|
// But the message only has stop words
|
|
expect(isMessageRelevantToAgent('the and or for with', agent)).toBe(false);
|
|
});
|
|
|
|
// --- Edge cases ---
|
|
|
|
it('returns false for null messageContent', () => {
|
|
const agent = { name: 'Bot', description: 'test' };
|
|
expect(isMessageRelevantToAgent(null, agent)).toBe(false);
|
|
});
|
|
|
|
it('returns false for empty string messageContent', () => {
|
|
const agent = { name: 'Bot', description: 'test' };
|
|
expect(isMessageRelevantToAgent('', agent)).toBe(false);
|
|
});
|
|
|
|
it('returns false for non-string messageContent', () => {
|
|
const agent = { name: 'Bot', description: 'test' };
|
|
expect(isMessageRelevantToAgent(123, agent)).toBe(false);
|
|
expect(isMessageRelevantToAgent({}, agent)).toBe(false);
|
|
});
|
|
|
|
it('returns false for null agentConfig', () => {
|
|
expect(isMessageRelevantToAgent('some message', null)).toBe(false);
|
|
});
|
|
|
|
it('returns false for undefined agentConfig', () => {
|
|
expect(isMessageRelevantToAgent('some message', undefined)).toBe(false);
|
|
});
|
|
|
|
it('returns false for non-object agentConfig', () => {
|
|
expect(isMessageRelevantToAgent('some message', 'not an object')).toBe(false);
|
|
});
|
|
|
|
it('returns false for agent with no name/description/prompt/tags', () => {
|
|
expect(isMessageRelevantToAgent('some message', {})).toBe(false);
|
|
expect(isMessageRelevantToAgent('some message', { id: 1 })).toBe(false);
|
|
});
|
|
|
|
it('returns false when agent config yields only stop words as keywords', () => {
|
|
const agent = {
|
|
name: 'The',
|
|
description: 'An assistant that can help',
|
|
};
|
|
// All meaningful words from config ("assistant", "help") are in STOP_WORDS
|
|
expect(isMessageRelevantToAgent('random unrelated message', agent)).toBe(false);
|
|
});
|
|
|
|
it('handles agent config where description contains numbers', () => {
|
|
const agent = {
|
|
name: 'Finance Bot',
|
|
description: 'Tracks Q4 2025 quarterly earnings and 10K filings'
|
|
};
|
|
expect(isMessageRelevantToAgent('What about our quarterly earnings?', agent)).toBe(true);
|
|
});
|
|
|
|
it('handles substring matching correctly (keyword is part of message word)', () => {
|
|
const agent = {
|
|
name: 'Data Analyst',
|
|
description: 'Handles database queries and data analysis'
|
|
};
|
|
// "database" contains "data" as a substring, and "data" is a keyword
|
|
expect(isMessageRelevantToAgent('I need database queries', agent)).toBe(true);
|
|
});
|
|
|
|
it('works with a realistic multi-field agent config', () => {
|
|
const agent = {
|
|
name: 'DevOps Engineer',
|
|
description: 'Manages CI/CD pipelines, Kubernetes clusters, and cloud infrastructure',
|
|
system_prompt: 'You are a DevOps specialist. Help with Docker, Terraform, AWS, and deployment automation.',
|
|
main_instructions: 'Monitor uptime, handle incidents, optimize costs.',
|
|
tags: ['devops', 'infrastructure', 'cloud', 'kubernetes']
|
|
};
|
|
|
|
// Relevant messages
|
|
expect(isMessageRelevantToAgent('How do I deploy to Kubernetes?', agent)).toBe(true);
|
|
expect(isMessageRelevantToAgent('Set up a Docker container for the API', agent)).toBe(true);
|
|
expect(isMessageRelevantToAgent('What are our cloud infrastructure costs?', agent)).toBe(true);
|
|
expect(isMessageRelevantToAgent('Help with terraform modules', agent)).toBe(true);
|
|
|
|
// Irrelevant messages
|
|
expect(isMessageRelevantToAgent('What are the sales numbers for Q4?', agent)).toBe(false);
|
|
expect(isMessageRelevantToAgent('Schedule a team meeting for Monday', agent)).toBe(false);
|
|
});
|
|
});
|