|
| 1 | +/** |
| 2 | + * Intent Pattern Checks Aggregation Tests |
| 3 | + * |
| 4 | + * Tests for INTENT_PATTERN_CHECKS including: |
| 5 | + * - All 13 domains are represented |
| 6 | + * - Priority ordering (first match wins) |
| 7 | + * - False positive prevention (agent name mentions vs domain) |
| 8 | + * - Each domain returns correct agent identifier |
| 9 | + */ |
| 10 | + |
| 11 | +import { describe, it, expect } from 'vitest'; |
| 12 | +import { INTENT_PATTERN_CHECKS } from './intent-pattern-checks'; |
| 13 | + |
| 14 | +describe('INTENT_PATTERN_CHECKS', () => { |
| 15 | + describe('structure and completeness', () => { |
| 16 | + it('should contain exactly 13 domain checks', () => { |
| 17 | + expect(INTENT_PATTERN_CHECKS).toHaveLength(13); |
| 18 | + }); |
| 19 | + |
| 20 | + it('should be a readonly array', () => { |
| 21 | + // ReadonlyArray prevents push/pop at type level; |
| 22 | + // at runtime we verify it is a plain array |
| 23 | + expect(Array.isArray(INTENT_PATTERN_CHECKS)).toBe(true); |
| 24 | + }); |
| 25 | + |
| 26 | + it('should have unique agent names', () => { |
| 27 | + const agents = INTENT_PATTERN_CHECKS.map(c => c.agent); |
| 28 | + expect(new Set(agents).size).toBe(agents.length); |
| 29 | + }); |
| 30 | + |
| 31 | + it('should have unique categories', () => { |
| 32 | + const categories = INTENT_PATTERN_CHECKS.map(c => c.category); |
| 33 | + expect(new Set(categories).size).toBe(categories.length); |
| 34 | + }); |
| 35 | + |
| 36 | + it.each([ |
| 37 | + { agent: 'agent-architect', category: 'Agent' }, |
| 38 | + { agent: 'test-engineer', category: 'Test' }, |
| 39 | + { agent: 'tooling-engineer', category: 'Tooling' }, |
| 40 | + { agent: 'platform-engineer', category: 'Platform' }, |
| 41 | + { agent: 'security-engineer', category: 'Security' }, |
| 42 | + { agent: 'systems-developer', category: 'Systems' }, |
| 43 | + { agent: 'data-engineer', category: 'Data' }, |
| 44 | + { agent: 'data-scientist', category: 'DataScience' }, |
| 45 | + { agent: 'ai-ml-engineer', category: 'AI/ML' }, |
| 46 | + { agent: 'backend-developer', category: 'Backend' }, |
| 47 | + { agent: 'frontend-developer', category: 'Frontend' }, |
| 48 | + { agent: 'devops-engineer', category: 'DevOps' }, |
| 49 | + { agent: 'mobile-developer', category: 'Mobile' }, |
| 50 | + ])('should include $agent with category $category', ({ agent, category }) => { |
| 51 | + const check = INTENT_PATTERN_CHECKS.find(c => c.agent === agent); |
| 52 | + expect(check).toBeDefined(); |
| 53 | + expect(check!.category).toBe(category); |
| 54 | + }); |
| 55 | + |
| 56 | + it('should have non-empty patterns array for every check', () => { |
| 57 | + for (const check of INTENT_PATTERN_CHECKS) { |
| 58 | + expect(check.patterns.length).toBeGreaterThan(0); |
| 59 | + } |
| 60 | + }); |
| 61 | + |
| 62 | + it('should have valid IntentPattern items (pattern, confidence, description)', () => { |
| 63 | + for (const check of INTENT_PATTERN_CHECKS) { |
| 64 | + for (const p of check.patterns) { |
| 65 | + expect(p.pattern).toBeInstanceOf(RegExp); |
| 66 | + expect(typeof p.confidence).toBe('number'); |
| 67 | + expect(p.confidence).toBeGreaterThan(0); |
| 68 | + expect(p.confidence).toBeLessThanOrEqual(1); |
| 69 | + expect(typeof p.description).toBe('string'); |
| 70 | + expect(p.description.length).toBeGreaterThan(0); |
| 71 | + } |
| 72 | + } |
| 73 | + }); |
| 74 | + }); |
| 75 | + |
| 76 | + describe('priority ordering', () => { |
| 77 | + const agentIndex = (agent: string): number => |
| 78 | + INTENT_PATTERN_CHECKS.findIndex(c => c.agent === agent); |
| 79 | + |
| 80 | + it('should place agent-architect first (index 0)', () => { |
| 81 | + expect(agentIndex('agent-architect')).toBe(0); |
| 82 | + }); |
| 83 | + |
| 84 | + it('should place test-engineer second (index 1)', () => { |
| 85 | + expect(agentIndex('test-engineer')).toBe(1); |
| 86 | + }); |
| 87 | + |
| 88 | + it('should place mobile-developer last (index 12)', () => { |
| 89 | + expect(agentIndex('mobile-developer')).toBe(12); |
| 90 | + }); |
| 91 | + |
| 92 | + it('should place agent-architect before backend-developer', () => { |
| 93 | + expect(agentIndex('agent-architect')).toBeLessThan(agentIndex('backend-developer')); |
| 94 | + }); |
| 95 | + |
| 96 | + it('should place test-engineer before backend-developer', () => { |
| 97 | + expect(agentIndex('test-engineer')).toBeLessThan(agentIndex('backend-developer')); |
| 98 | + }); |
| 99 | + |
| 100 | + it('should place test-engineer before frontend-developer', () => { |
| 101 | + expect(agentIndex('test-engineer')).toBeLessThan(agentIndex('frontend-developer')); |
| 102 | + }); |
| 103 | + |
| 104 | + it('should place security-engineer before backend-developer', () => { |
| 105 | + expect(agentIndex('security-engineer')).toBeLessThan(agentIndex('backend-developer')); |
| 106 | + }); |
| 107 | + |
| 108 | + it('should place data-engineer before backend-developer', () => { |
| 109 | + expect(agentIndex('data-engineer')).toBeLessThan(agentIndex('backend-developer')); |
| 110 | + }); |
| 111 | + |
| 112 | + it('should place backend-developer before mobile-developer', () => { |
| 113 | + expect(agentIndex('backend-developer')).toBeLessThan(agentIndex('mobile-developer')); |
| 114 | + }); |
| 115 | + |
| 116 | + it('should place frontend-developer before mobile-developer', () => { |
| 117 | + expect(agentIndex('frontend-developer')).toBeLessThan(agentIndex('mobile-developer')); |
| 118 | + }); |
| 119 | + |
| 120 | + const EXPECTED_ORDER = [ |
| 121 | + 'agent-architect', |
| 122 | + 'test-engineer', |
| 123 | + 'tooling-engineer', |
| 124 | + 'platform-engineer', |
| 125 | + 'security-engineer', |
| 126 | + 'systems-developer', |
| 127 | + 'data-engineer', |
| 128 | + 'data-scientist', |
| 129 | + 'ai-ml-engineer', |
| 130 | + 'backend-developer', |
| 131 | + 'frontend-developer', |
| 132 | + 'devops-engineer', |
| 133 | + 'mobile-developer', |
| 134 | + ]; |
| 135 | + |
| 136 | + it('should follow the exact documented priority order', () => { |
| 137 | + const actual = INTENT_PATTERN_CHECKS.map(c => c.agent); |
| 138 | + expect(actual).toEqual(EXPECTED_ORDER); |
| 139 | + }); |
| 140 | + }); |
| 141 | + |
| 142 | + describe('false positive prevention (first-match-wins simulation)', () => { |
| 143 | + /** |
| 144 | + * Simulates the for-of loop in act-agent.strategy.ts: |
| 145 | + * Iterates INTENT_PATTERN_CHECKS in order and returns the first match. |
| 146 | + */ |
| 147 | + const firstMatchAgent = (prompt: string): string | null => { |
| 148 | + for (const { agent, patterns } of INTENT_PATTERN_CHECKS) { |
| 149 | + if (patterns.some(({ pattern }) => pattern.test(prompt))) { |
| 150 | + return agent; |
| 151 | + } |
| 152 | + } |
| 153 | + return null; |
| 154 | + }; |
| 155 | + |
| 156 | + it('should match "에이전트 설계 for Mobile Developer" to agent-architect (agent patterns checked first)', () => { |
| 157 | + const result = firstMatchAgent('에이전트 설계 for Mobile Developer'); |
| 158 | + expect(result).toBe('agent-architect'); |
| 159 | + }); |
| 160 | + |
| 161 | + it('should match "agent design for mobile" to agent-architect', () => { |
| 162 | + const result = firstMatchAgent('agent design for mobile platform'); |
| 163 | + expect(result).toBe('agent-architect'); |
| 164 | + }); |
| 165 | + |
| 166 | + it('should match "implement Mobile Developer agent" to mobile-developer (greedy mobile pattern)', () => { |
| 167 | + // "Mobile Developer" matches /mobile\s*(app|develop|screen)/i before agent patterns |
| 168 | + const result = firstMatchAgent('implement Mobile Developer agent'); |
| 169 | + expect(result).toBe('mobile-developer'); |
| 170 | + }); |
| 171 | + |
| 172 | + it('should match "write unit tests for API" to test-engineer, not backend-developer', () => { |
| 173 | + const result = firstMatchAgent('write unit tests for API endpoints'); |
| 174 | + expect(result).toBe('test-engineer'); |
| 175 | + }); |
| 176 | + |
| 177 | + it('should match "TDD로 NestJS 서버 개발" to test-engineer (TDD keyword)', () => { |
| 178 | + const result = firstMatchAgent('TDD로 NestJS 서버 개발'); |
| 179 | + expect(result).toBe('test-engineer'); |
| 180 | + }); |
| 181 | + |
| 182 | + it('should match "React Native 앱 만들어줘" to mobile-developer (no ambiguity)', () => { |
| 183 | + const result = firstMatchAgent('React Native 앱 만들어줘'); |
| 184 | + expect(result).toBe('mobile-developer'); |
| 185 | + }); |
| 186 | + |
| 187 | + it('should match "MCP 서버 만들어줘" to agent-architect', () => { |
| 188 | + const result = firstMatchAgent('MCP 서버 만들어줘'); |
| 189 | + expect(result).toBe('agent-architect'); |
| 190 | + }); |
| 191 | + |
| 192 | + it('should match "Docker compose 설정" to devops-engineer', () => { |
| 193 | + const result = firstMatchAgent('Docker compose 설정'); |
| 194 | + expect(result).toBe('devops-engineer'); |
| 195 | + }); |
| 196 | + |
| 197 | + it('should match "Kubernetes 클러스터 설정" to platform-engineer', () => { |
| 198 | + const result = firstMatchAgent('Kubernetes 클러스터 설정'); |
| 199 | + expect(result).toBe('platform-engineer'); |
| 200 | + }); |
| 201 | + |
| 202 | + it('should match "데이터베이스 마이그레이션" to data-engineer', () => { |
| 203 | + const result = firstMatchAgent('데이터베이스 마이그레이션'); |
| 204 | + expect(result).toBe('data-engineer'); |
| 205 | + }); |
| 206 | + |
| 207 | + it('should match "React 컴포넌트 만들어줘" to frontend-developer', () => { |
| 208 | + const result = firstMatchAgent('React 컴포넌트 만들어줘'); |
| 209 | + expect(result).toBe('frontend-developer'); |
| 210 | + }); |
| 211 | + |
| 212 | + it('should match "NestJS API 만들어줘" to backend-developer', () => { |
| 213 | + const result = firstMatchAgent('NestJS API 만들어줘'); |
| 214 | + expect(result).toBe('backend-developer'); |
| 215 | + }); |
| 216 | + |
| 217 | + it('should match "webpack 설정 변경" to tooling-engineer', () => { |
| 218 | + const result = firstMatchAgent('webpack 설정 변경'); |
| 219 | + expect(result).toBe('tooling-engineer'); |
| 220 | + }); |
| 221 | + |
| 222 | + it('should match "XSS 취약점 수정" to security-engineer', () => { |
| 223 | + const result = firstMatchAgent('XSS 취약점 수정'); |
| 224 | + expect(result).toBe('security-engineer'); |
| 225 | + }); |
| 226 | + |
| 227 | + it('should match "Rust FFI 바인딩 구현" to systems-developer', () => { |
| 228 | + const result = firstMatchAgent('Rust FFI 바인딩 구현'); |
| 229 | + expect(result).toBe('systems-developer'); |
| 230 | + }); |
| 231 | + |
| 232 | + it('should match "pandas 데이터 분석" to data-scientist', () => { |
| 233 | + const result = firstMatchAgent('pandas 데이터 분석'); |
| 234 | + expect(result).toBe('data-scientist'); |
| 235 | + }); |
| 236 | + |
| 237 | + it('should match "LLM 파인튜닝 구현" to ai-ml-engineer', () => { |
| 238 | + const result = firstMatchAgent('LLM 파인튜닝 구현'); |
| 239 | + expect(result).toBe('ai-ml-engineer'); |
| 240 | + }); |
| 241 | + |
| 242 | + it('should match "Flutter 위젯 구현" to mobile-developer', () => { |
| 243 | + const result = firstMatchAgent('Flutter 위젯 구현'); |
| 244 | + expect(result).toBe('mobile-developer'); |
| 245 | + }); |
| 246 | + |
| 247 | + it('should return null for unrecognized prompt', () => { |
| 248 | + const result = firstMatchAgent('hello world'); |
| 249 | + expect(result).toBeNull(); |
| 250 | + }); |
| 251 | + |
| 252 | + it('should return null for empty string', () => { |
| 253 | + const result = firstMatchAgent(''); |
| 254 | + expect(result).toBeNull(); |
| 255 | + }); |
| 256 | + }); |
| 257 | + |
| 258 | + describe('individual domain pattern matching', () => { |
| 259 | + const matchesDomain = (agent: string, prompt: string): boolean => { |
| 260 | + const check = INTENT_PATTERN_CHECKS.find(c => c.agent === agent); |
| 261 | + if (!check) return false; |
| 262 | + return check.patterns.some(({ pattern }) => pattern.test(prompt)); |
| 263 | + }; |
| 264 | + |
| 265 | + describe('agent-architect patterns', () => { |
| 266 | + it('should match MCP server', () => { |
| 267 | + expect(matchesDomain('agent-architect', 'MCP 서버 만들어줘')).toBe(true); |
| 268 | + }); |
| 269 | + |
| 270 | + it('should match agent development', () => { |
| 271 | + expect(matchesDomain('agent-architect', 'agent design for mobile')).toBe(true); |
| 272 | + }); |
| 273 | + |
| 274 | + it('should match workflow automation', () => { |
| 275 | + expect(matchesDomain('agent-architect', 'workflow automation 구현')).toBe(true); |
| 276 | + }); |
| 277 | + |
| 278 | + it('should not match plain backend prompt', () => { |
| 279 | + expect(matchesDomain('agent-architect', 'NestJS API 만들어줘')).toBe(false); |
| 280 | + }); |
| 281 | + }); |
| 282 | + |
| 283 | + describe('test-engineer patterns', () => { |
| 284 | + it('should match TDD keyword', () => { |
| 285 | + expect(matchesDomain('test-engineer', 'TDD로 개발해줘')).toBe(true); |
| 286 | + }); |
| 287 | + |
| 288 | + it('should match unit test', () => { |
| 289 | + expect(matchesDomain('test-engineer', 'unit test 작성')).toBe(true); |
| 290 | + }); |
| 291 | + |
| 292 | + it('should not match plain React prompt', () => { |
| 293 | + expect(matchesDomain('test-engineer', 'React 컴포넌트 개발')).toBe(false); |
| 294 | + }); |
| 295 | + }); |
| 296 | + |
| 297 | + describe('mobile-developer patterns', () => { |
| 298 | + it('should match React Native', () => { |
| 299 | + expect(matchesDomain('mobile-developer', 'React Native 앱')).toBe(true); |
| 300 | + }); |
| 301 | + |
| 302 | + it('should match Flutter', () => { |
| 303 | + expect(matchesDomain('mobile-developer', 'Flutter 위젯')).toBe(true); |
| 304 | + }); |
| 305 | + |
| 306 | + it('should match SwiftUI', () => { |
| 307 | + expect(matchesDomain('mobile-developer', 'SwiftUI 뷰 만들어줘')).toBe(true); |
| 308 | + }); |
| 309 | + |
| 310 | + it('should match Jetpack Compose', () => { |
| 311 | + expect(matchesDomain('mobile-developer', 'Jetpack Compose UI')).toBe(true); |
| 312 | + }); |
| 313 | + |
| 314 | + it('should match Korean mobile app pattern', () => { |
| 315 | + expect(matchesDomain('mobile-developer', '모바일 앱 개발')).toBe(true); |
| 316 | + }); |
| 317 | + |
| 318 | + it('should not match plain agent prompt', () => { |
| 319 | + expect(matchesDomain('mobile-developer', 'agent 설계해줘')).toBe(false); |
| 320 | + }); |
| 321 | + }); |
| 322 | + |
| 323 | + describe('backend-developer patterns', () => { |
| 324 | + it('should match NestJS', () => { |
| 325 | + expect(matchesDomain('backend-developer', 'NestJS 서비스 개발')).toBe(true); |
| 326 | + }); |
| 327 | + |
| 328 | + it('should match REST API', () => { |
| 329 | + expect(matchesDomain('backend-developer', 'REST API 설계')).toBe(true); |
| 330 | + }); |
| 331 | + }); |
| 332 | + |
| 333 | + describe('frontend-developer patterns', () => { |
| 334 | + it('should match React component', () => { |
| 335 | + expect(matchesDomain('frontend-developer', 'React 컴포넌트 만들어줘')).toBe(true); |
| 336 | + }); |
| 337 | + }); |
| 338 | + |
| 339 | + describe('devops-engineer patterns', () => { |
| 340 | + it('should match Docker compose', () => { |
| 341 | + expect(matchesDomain('devops-engineer', 'Docker compose 설정')).toBe(true); |
| 342 | + }); |
| 343 | + |
| 344 | + it('should match CI/CD', () => { |
| 345 | + expect(matchesDomain('devops-engineer', 'CI/CD 파이프라인 구축')).toBe(true); |
| 346 | + }); |
| 347 | + |
| 348 | + it('should match GitHub Actions', () => { |
| 349 | + expect(matchesDomain('devops-engineer', 'GitHub Actions 워크플로우')).toBe(true); |
| 350 | + }); |
| 351 | + }); |
| 352 | + |
| 353 | + describe('security-engineer patterns', () => { |
| 354 | + it('should match XSS', () => { |
| 355 | + expect(matchesDomain('security-engineer', 'XSS 취약점 방지')).toBe(true); |
| 356 | + }); |
| 357 | + }); |
| 358 | + |
| 359 | + describe('systems-developer patterns', () => { |
| 360 | + it('should match Rust', () => { |
| 361 | + expect(matchesDomain('systems-developer', 'Rust 코드 작성')).toBe(true); |
| 362 | + }); |
| 363 | + }); |
| 364 | + |
| 365 | + describe('data-engineer patterns', () => { |
| 366 | + it('should match database migration', () => { |
| 367 | + expect(matchesDomain('data-engineer', '데이터베이스 마이그레이션')).toBe(true); |
| 368 | + }); |
| 369 | + }); |
| 370 | + |
| 371 | + describe('data-scientist patterns', () => { |
| 372 | + it('should match pandas', () => { |
| 373 | + expect(matchesDomain('data-scientist', 'pandas 데이터 분석')).toBe(true); |
| 374 | + }); |
| 375 | + }); |
| 376 | + |
| 377 | + describe('ai-ml-engineer patterns', () => { |
| 378 | + it('should match LLM', () => { |
| 379 | + expect(matchesDomain('ai-ml-engineer', 'LLM 파인튜닝')).toBe(true); |
| 380 | + }); |
| 381 | + }); |
| 382 | + |
| 383 | + describe('tooling-engineer patterns', () => { |
| 384 | + it('should match webpack', () => { |
| 385 | + expect(matchesDomain('tooling-engineer', 'webpack 설정 변경')).toBe(true); |
| 386 | + }); |
| 387 | + }); |
| 388 | + |
| 389 | + describe('platform-engineer patterns', () => { |
| 390 | + it('should match Kubernetes', () => { |
| 391 | + expect(matchesDomain('platform-engineer', 'Kubernetes 클러스터 설정')).toBe(true); |
| 392 | + }); |
| 393 | + }); |
| 394 | + }); |
| 395 | +}); |
0 commit comments