/** * Domain Module Unit Tests * Tests for category inference, contract extraction, and route discovery. * Uses Node's built-in test runner with AAA (Arrange, Act, Assert) pattern. */ import { test } from 'node:test' import assert from 'node:assert' import { inferCategory } from '../domain/category.js' import { extractContract } from '../domain/contract.js' import { discoverRoutes } from '../domain/discovery.js' // ============================================================================ import type { RouteContract } from '../types.js' // Category Inference Tests // ============================================================================ test('inferCategory returns utility for exact utility path /reset', () => { // Arrange const path = '/reset' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'utility') }) test('inferCategory returns utility for utility path with trailing slash', () => { // Arrange const path = '/health/' const method = 'GET' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'utility') }) test('inferCategory returns utility for all registered utility paths', () => { // Arrange const utilityPaths = ['/ping', '/login', '/logout', '/auth', '/callback', '/purge', '/clear', '/initialize', '/setup', '/webhook'] // Act & Assert for (const path of utilityPaths) { const result = inferCategory(path, 'GET', undefined) assert.strictEqual(result, 'utility', `Expected utility for path ${path}`) } }) test('inferCategory returns observer for GET method on non-utility path', () => { // Arrange const path = '/users' const method = 'GET' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory returns observer for observer suffix /search', () => { // Arrange const path = '/users/search' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory returns observer for observer suffix /count', () => { // Arrange const path = '/items/count' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory returns observer for observer suffix /stats', () => { // Arrange const path = '/metrics/stats' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory returns observer for observer suffix /status', () => { // Arrange const path = '/system/status' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory returns constructor for POST on collection path', () => { // Arrange const path = '/users' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'constructor') }) test('inferCategory returns constructor for POST on nested collection path', () => { // Arrange const path = '/api/v1/users' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'constructor') }) test('inferCategory returns mutator for PUT method', () => { // Arrange const path = '/users/:id' const method = 'PUT' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'mutator') }) test('inferCategory returns mutator for PATCH method', () => { // Arrange const path = '/users/:id' const method = 'PATCH' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'mutator') }) test('inferCategory returns mutator for DELETE method', () => { // Arrange const path = '/users/:id' const method = 'DELETE' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'mutator') }) test('inferCategory returns mutator for POST with path parameter', () => { // Arrange const path = '/users/:id' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'mutator') }) test('inferCategory returns observer for POST without collection path or path param', () => { // Arrange const path = '/search' const method = 'POST' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory handles method case insensitively', () => { // Arrange const path = '/users' const method = 'get' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory respects override when provided', () => { // Arrange const path = '/users' const method = 'POST' const override = 'observer' // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) test('inferCategory ignores empty string override', () => { // Arrange const path = '/users' const method = 'POST' const override = '' // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'constructor') }) test('inferCategory returns utility when override is utility', () => { // Arrange const path = '/users' const method = 'POST' const override = 'utility' // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'utility') }) test('inferCategory returns mutator when override is mutator', () => { // Arrange const path = '/users' const method = 'GET' const override = 'mutator' // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'mutator') }) test('inferCategory returns observer as default fallback', () => { // Arrange const path = '/' const method = 'HEAD' const override = undefined // Act const result = inferCategory(path, method, override) // Assert assert.strictEqual(result, 'observer') }) // ============================================================================ // Contract Extraction Tests // ============================================================================ test('extractContract extracts basic contract with defaults', () => { // Arrange const path = '/users' const method = 'GET' const schema = undefined // Act const result = extractContract(path, method, schema) // Assert assert.strictEqual(result.path, '/users') assert.strictEqual(result.method, 'GET') assert.strictEqual(result.category, 'observer') assert.deepStrictEqual(result.requires, []) assert.deepStrictEqual(result.ensures, []) assert.deepStrictEqual(result.invariants, []) assert.deepStrictEqual(result.regexPatterns, {}) assert.strictEqual(result.validateRuntime, true) assert.deepStrictEqual(result.schema, {}) }) test('extractContract extracts x-requires array', () => { // Arrange const path = '/users' const method = 'POST' const schema = { 'x-requires': ['admin', 'authenticated'], } // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.requires, ['admin', 'authenticated']) }) test('extractContract extracts x-ensures array', () => { // Arrange const path = '/users' const method = 'POST' const schema = { 'x-ensures': ['user.created', 'email.sent'], } // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.ensures, ['user.created', 'email.sent']) }) test('extractContract ignores x-invariants (removed in v1.0)', () => { // Arrange const path = '/users' const method = 'POST' const schema = { 'x-invariants': ['unique.email', 'active.status'], } // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.invariants, []) }) test('extractContract ignores x-regex (removed in v1.0)', () => { // Arrange const path = '/users' const method = 'POST' const schema = { 'x-regex': { email: '^[\\w.-]+@[\\w.-]+\\.\\w+$', phone: '^\\+?[1-9]\\d{1,14}$', }, } // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.regexPatterns, {}) }) test('extractContract handles x-validate-runtime false', () => { // Arrange const path = '/users' const method = 'GET' const schema = { 'x-validate-runtime': false, } // Act const result = extractContract(path, method, schema) // Assert assert.strictEqual(result.validateRuntime, false) }) test('extractContract defaults validateRuntime to true when not specified', () => { // Arrange const path = '/users' const method = 'GET' const schema = {} // Act const result = extractContract(path, method, schema) // Assert assert.strictEqual(result.validateRuntime, true) }) test('extractContract respects x-category override', () => { // Arrange const path = '/users' const method = 'POST' const schema = { 'x-category': 'utility', } // Act const result = extractContract(path, method, schema) // Assert assert.strictEqual(result.category, 'utility') }) test('extractContract ignores non-string x-category', () => { // Arrange const path = '/users' const method = 'POST' const schema = { 'x-category': 123, } // Act const result = extractContract(path, method, schema) // Assert assert.strictEqual(result.category, 'constructor') }) test('extractContract handles empty schema object', () => { // Arrange const path = '/users' const method = 'GET' const schema = {} // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.requires, []) assert.deepStrictEqual(result.ensures, []) assert.deepStrictEqual(result.invariants, []) assert.deepStrictEqual(result.regexPatterns, {}) assert.strictEqual(result.validateRuntime, true) }) test('extractContract handles null x-regex gracefully', () => { // Arrange const path = '/users' const method = 'GET' const schema = { 'x-regex': null, } // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.regexPatterns, {}) }) test('extractContract handles non-object x-regex gracefully', () => { // Arrange const path = '/users' const method = 'GET' const schema = { 'x-regex': 'invalid', } // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.regexPatterns, {}) }) test('extractContract normalizes method to uppercase', () => { // Arrange const path = '/users' const method = 'post' const schema = undefined // Act const result = extractContract(path, method, schema) // Assert assert.strictEqual(result.method, 'POST') }) test('extractContract preserves original schema in contract', () => { // Arrange const path = '/users' const method = 'GET' const schema = { type: 'object', properties: { name: { type: 'string' }, }, } // Act const result = extractContract(path, method, schema) // Assert assert.deepStrictEqual(result.schema, schema) }) // ============================================================================ // Route Discovery Tests // ============================================================================ test('discoverRoutes returns empty array for empty routes', () => { // Arrange const instance = { routes: [] } // Act const result = discoverRoutes(instance) // Assert assert.deepStrictEqual(result, []) }) test('discoverRoutes returns empty array when routes is undefined', () => { // Arrange const instance = {} // Act const result = discoverRoutes(instance) // Assert assert.deepStrictEqual(result, []) }) test('discoverRoutes discovers single route', () => { // Arrange const instance = { routes: [ { method: 'GET', url: '/users', schema: {} }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 1) assert.strictEqual(result[0]!.path, '/users') assert.strictEqual(result[0]!.method, 'GET') assert.strictEqual(result[0]!.category, 'observer') }) test('discoverRoutes discovers multiple routes', () => { // Arrange const instance = { routes: [ { method: 'GET', url: '/users' }, { method: 'POST', url: '/users' }, { method: 'GET', url: '/users/:id' }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 3) assert.strictEqual(result[0]!.category, 'observer') assert.strictEqual(result[1]!.category, 'constructor') assert.strictEqual(result[2]!.category, 'observer') }) test('discoverRoutes handles routes with schemas', () => { // Arrange const instance = { routes: [ { method: 'POST', url: '/users', schema: { 'x-requires': ['admin'], 'x-ensures': ['user.created'], 'x-category': 'constructor', }, }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 1) assert.strictEqual(result[0]!.path, '/users') assert.deepStrictEqual(result[0]!.requires, ['admin']) assert.deepStrictEqual(result[0]!.ensures, ['user.created']) assert.strictEqual(result[0]!.category, 'constructor') }) test('discoverRoutes handles routes without schemas', () => { // Arrange const instance = { routes: [ { method: 'DELETE', url: '/users/:id' }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 1) assert.strictEqual(result[0]!.path, '/users/:id') assert.strictEqual(result[0]!.method, 'DELETE') assert.strictEqual(result[0]!.category, 'mutator') assert.deepStrictEqual(result[0]!.requires, []) }) test('discoverRoutes handles mixed route configurations', () => { // Arrange const instance = { routes: [ { method: 'GET', url: '/health' }, { method: 'POST', url: '/users', schema: { 'x-requires': ['auth'] } }, { method: 'GET', url: '/users/:id' }, { method: 'DELETE', url: '/users/:id' }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 4) assert.strictEqual(result[0]!.category, 'utility') assert.deepStrictEqual(result[0]!.requires, []) assert.strictEqual(result[1]!.category, 'constructor') assert.deepStrictEqual(result[1]!.requires, ['auth']) assert.strictEqual(result[2]!.category, 'observer') assert.deepStrictEqual(result[2]!.invariants, []) assert.strictEqual(result[3]!.category, 'mutator') }) test('discoverRoutes ignores x-regex (removed in v1.0)', () => { // Arrange const instance = { routes: [ { method: 'POST', url: '/users', schema: { 'x-regex': { email: '^[\\w.-]+@[\\w.-]+\\.\\w+$', }, }, }, ], } // Act const result = discoverRoutes(instance) // Assert assert.deepStrictEqual(result[0]!.regexPatterns, {}) }) test('discoverRoutes handles route with validateRuntime disabled', () => { // Arrange const instance = { routes: [ { method: 'GET', url: '/public', schema: { 'x-validate-runtime': false, }, }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result[0]!.validateRuntime, false) }) test('discoverRoutes discovers utility routes correctly', () => { // Arrange const instance = { routes: [ { method: 'GET', url: '/reset' }, { method: 'POST', url: '/login' }, { method: 'GET', url: '/callback' }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 3) for (const contract of result) { assert.strictEqual(contract.category, 'utility') } }) test('discoverRoutes discovers observer suffix routes', () => { // Arrange const instance = { routes: [ { method: 'POST', url: '/users/search' }, { method: 'GET', url: '/items/count' }, { method: 'POST', url: '/system/stats' }, { method: 'GET', url: '/service/status' }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 4) for (const contract of result) { assert.strictEqual(contract.category, 'observer') } }) test('discoverRoutes handles non-array routes property', () => { // Arrange const instance = { routes: 'invalid' as unknown as Array<{ method: string; url: string; schema?: Record }>, } // Act const result = discoverRoutes(instance) // Assert assert.deepStrictEqual(result, []) }) test('discoverRoutes handles null instance gracefully', () => { // Arrange const instance = null as unknown as { routes?: Array<{ method: string; url: string; schema?: Record }> } // Act & Assert assert.throws(() => { discoverRoutes(instance) }, /Cannot read properties of null/) }) test('discoverRoutes handles route with empty schema', () => { // Arrange const instance = { routes: [ { method: 'GET', url: '/empty', schema: {} }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 1) assert.strictEqual(result[0]!.path, '/empty') assert.deepStrictEqual(result[0]!.requires, []) assert.deepStrictEqual(result[0]!.ensures, []) assert.deepStrictEqual(result[0]!.invariants, []) }) test('discoverRoutes handles route with all x-annotations', () => { // Arrange const instance = { routes: [ { method: 'POST', url: '/users', schema: { 'x-category': 'constructor', 'x-requires': ['auth', 'admin'], 'x-ensures': ['created'], 'x-invariants': ['unique'], 'x-regex': { name: '^[a-z]+$' }, 'x-validate-runtime': true, }, }, ], } // Act const result = discoverRoutes(instance) // Assert assert.strictEqual(result.length, 1) const contract = result[0]! assert.strictEqual(contract.category, 'constructor') assert.deepStrictEqual(contract.requires, ['auth', 'admin']) assert.deepStrictEqual(contract.ensures, ['created']) assert.deepStrictEqual(contract.invariants, []) assert.deepStrictEqual(contract.regexPatterns, {}) assert.strictEqual(contract.validateRuntime, true) })