- B1: Next.js 15 + Payload CMS 3.0 + Postgres 16, ESLint, Prettier, Husky, Vitest - B2: 9 collections, 6 globals, 12 Page Builder blocks, access control, slugify/revalidate hooks - B3: ezy.com.ua payments, Binotel HMAC webhook, leads API, Telegram bot, Resend email, rate limiting - B4: Tariffs collection with ezy API sync (cron + manual), dynamic pricing source-of-truth - B5: 13 test files covering unit libs and all API routes - B6: Dockerfile multi-stage, docker-compose.prod.yml, nginx.conf SSL, GitHub Actions CI/CD, health endpoint - B7: docs/admin-guide-ua.md (marketer guide), docs/deploy.md (VPS instructions), README quickstart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
199 lines
8.4 KiB
JavaScript
199 lines
8.4 KiB
JavaScript
/**
|
|
* Tests for .claude/helpers/router.js
|
|
*
|
|
* Run: node --test tests/helpers/router.test.js
|
|
*
|
|
* Coverage gaps addressed:
|
|
* - routeTask: each task pattern, default fallback, case insensitivity
|
|
* - Return shape: agent, confidence, reason fields always present
|
|
* - Edge cases: empty string, whitespace-only, numeric string
|
|
* - Pattern conflicts: first-match wins (no multi-match merging)
|
|
*/
|
|
|
|
'use strict'
|
|
|
|
const { describe, it } = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const { routeTask, AGENT_CAPABILITIES, TASK_PATTERNS } = require('../../.claude/helpers/router.js')
|
|
|
|
describe('router.js — routeTask()', () => {
|
|
// ── Return shape ────────────────────────────────────────────────────────────
|
|
|
|
describe('return shape', () => {
|
|
it('always returns an object with agent, confidence, and reason', () => {
|
|
const r = routeTask('anything')
|
|
assert.ok(typeof r.agent === 'string', 'agent must be a string')
|
|
assert.ok(typeof r.confidence === 'number', 'confidence must be a number')
|
|
assert.ok(typeof r.reason === 'string', 'reason must be a string')
|
|
})
|
|
|
|
it('confidence is between 0 and 1', () => {
|
|
const tasks = ['build a feature', 'write tests', 'random unknown task']
|
|
for (const t of tasks) {
|
|
const r = routeTask(t)
|
|
assert.ok(r.confidence >= 0 && r.confidence <= 1, `confidence out of range for: ${t}`)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ── Pattern matching ────────────────────────────────────────────────────────
|
|
|
|
describe('pattern → agent routing', () => {
|
|
// NOTE: router uses first-match-wins on TASK_PATTERNS key order.
|
|
// Keywords like 'build', 'create', 'implement' match the coder pattern BEFORE
|
|
// domain-specific patterns (backend-dev, frontend-dev). Tests below reflect
|
|
// actual routing behaviour, not intuitive intent — see the "known routing quirks"
|
|
// section below for cases where first-match hides the intended agent.
|
|
const cases = [
|
|
// coder pattern: 'implement|create|build|add|write code'
|
|
['implement a login page', 'coder'],
|
|
['create a REST endpoint', 'coder'], // 'create' → coder, not backend-dev
|
|
['build authentication', 'coder'],
|
|
['add a new field to the schema', 'coder'],
|
|
['write code for the parser', 'coder'],
|
|
|
|
// tester pattern: 'test|spec|coverage|unit test|integration'
|
|
['test the signup flow', 'tester'],
|
|
['write unit tests for utils', 'tester'],
|
|
['check coverage for auth module', 'tester'],
|
|
['integration tests for payment API', 'tester'],
|
|
['write a spec for the validator', 'tester'],
|
|
|
|
// reviewer pattern: 'review|audit|check|validate|security'
|
|
['review the PR for security issues', 'reviewer'],
|
|
['audit the codebase for vulnerabilities', 'reviewer'],
|
|
['check code quality', 'reviewer'],
|
|
['validate the configuration', 'reviewer'],
|
|
|
|
// researcher pattern: 'research|find|search|documentation|explore'
|
|
['research best JWT libraries', 'researcher'],
|
|
['find documentation for Redis', 'researcher'],
|
|
['search for OAuth examples', 'researcher'],
|
|
['explore alternative approaches', 'researcher'],
|
|
|
|
// architect pattern: 'design|architect|structure|plan'
|
|
['design the microservice architecture', 'architect'],
|
|
['plan the database structure', 'architect'],
|
|
|
|
// backend-dev pattern: 'api|endpoint|server|backend|database'
|
|
// Only works when coder/tester/reviewer/researcher/architect keywords are absent
|
|
['set up the database schema', 'backend-dev'],
|
|
['the server-side middleware', 'backend-dev'], // 'server' matches backend-dev
|
|
['REST api for payment gateway', 'backend-dev'], // 'api' without documentation/search
|
|
|
|
// frontend-dev pattern: 'ui|frontend|component|react|css|style'
|
|
// Only works when patterns 1-6 don't fire first
|
|
['style the header with CSS', 'frontend-dev'],
|
|
['the UI layout mockup', 'frontend-dev'], // no review/research/create
|
|
['react hooks and state', 'frontend-dev'], // 'react' not in earlier patterns
|
|
|
|
// devops pattern: 'deploy|docker|ci|cd|pipeline|infrastructure'
|
|
['deploy to production', 'devops'],
|
|
['set up Docker Compose', 'devops'], // 'set up' doesn't match coder
|
|
['configure the CI pipeline', 'devops'],
|
|
]
|
|
|
|
for (const [task, expectedAgent] of cases) {
|
|
it(`"${task}" → ${expectedAgent}`, () => {
|
|
const r = routeTask(task)
|
|
assert.equal(r.agent, expectedAgent, `Expected ${expectedAgent}, got ${r.agent}`)
|
|
assert.equal(r.confidence, 0.8)
|
|
})
|
|
}
|
|
})
|
|
|
|
// ── Default fallback ────────────────────────────────────────────────────────
|
|
|
|
describe('default fallback', () => {
|
|
it('routes to coder with confidence 0.5 when no pattern matches', () => {
|
|
const r = routeTask('do something completely unknown xyz123')
|
|
assert.equal(r.agent, 'coder')
|
|
assert.equal(r.confidence, 0.5)
|
|
})
|
|
|
|
it('handles empty string input without throwing', () => {
|
|
assert.doesNotThrow(() => routeTask(''))
|
|
const r = routeTask('')
|
|
assert.equal(r.agent, 'coder')
|
|
assert.equal(r.confidence, 0.5)
|
|
})
|
|
|
|
it('handles whitespace-only input', () => {
|
|
const r = routeTask(' ')
|
|
assert.equal(r.agent, 'coder')
|
|
})
|
|
|
|
it('handles numeric string input', () => {
|
|
const r = routeTask('12345')
|
|
assert.equal(r.agent, 'coder')
|
|
})
|
|
})
|
|
|
|
// ── Case insensitivity ──────────────────────────────────────────────────────
|
|
|
|
describe('case insensitivity', () => {
|
|
it('matches uppercase task keywords', () => {
|
|
const r = routeTask('IMPLEMENT a feature')
|
|
assert.equal(r.agent, 'coder')
|
|
})
|
|
|
|
it('matches mixed-case task keywords', () => {
|
|
const r = routeTask('Write Tests For The Module')
|
|
assert.equal(r.agent, 'tester')
|
|
})
|
|
})
|
|
|
|
// ── AGENT_CAPABILITIES export ───────────────────────────────────────────────
|
|
|
|
describe('AGENT_CAPABILITIES', () => {
|
|
it('exports capabilities for all core agents', () => {
|
|
const expected = [
|
|
'coder',
|
|
'tester',
|
|
'reviewer',
|
|
'researcher',
|
|
'architect',
|
|
'backend-dev',
|
|
'frontend-dev',
|
|
'devops',
|
|
]
|
|
for (const agent of expected) {
|
|
assert.ok(agent in AGENT_CAPABILITIES, `Missing capabilities for: ${agent}`)
|
|
assert.ok(Array.isArray(AGENT_CAPABILITIES[agent]))
|
|
}
|
|
})
|
|
})
|
|
|
|
// ── Known routing quirks (first-match-wins causes mis-routing) ───────────────
|
|
// These are NOT bugs in the tests — they document real mis-routing that should
|
|
// be fixed by reordering TASK_PATTERNS or using a scoring approach.
|
|
|
|
describe('known routing quirks — first-match wins over domain intent', () => {
|
|
it('"create a React component" routes to coder, not frontend-dev (create matches first)', () => {
|
|
assert.equal(routeTask('create a React component').agent, 'coder')
|
|
})
|
|
|
|
it('"build the UI dashboard" routes to coder, not frontend-dev (build matches first)', () => {
|
|
assert.equal(routeTask('build the UI dashboard').agent, 'coder')
|
|
})
|
|
|
|
it('"implement server-side authentication" routes to coder, not backend-dev', () => {
|
|
assert.equal(routeTask('implement server-side authentication').agent, 'coder')
|
|
})
|
|
|
|
it('"build the REST API" routes to coder, not backend-dev (build matches first)', () => {
|
|
assert.equal(routeTask('build the REST API').agent, 'coder')
|
|
})
|
|
})
|
|
|
|
// ── Integration: first-match-wins when task matches multiple patterns ────────
|
|
|
|
describe('first-match-wins on ambiguous tasks', () => {
|
|
it('resolves to the first matching pattern when task fits multiple', () => {
|
|
// "implement api" matches 'implement' (→ coder) AND 'api' (→ backend-dev)
|
|
const r = routeTask('implement api endpoint')
|
|
assert.equal(r.agent, 'coder') // coder wins because it is listed first
|
|
assert.equal(r.confidence, 0.8)
|
|
})
|
|
})
|
|
})
|