import { describe, it, expect } from 'vitest'; import { ResourceRuleRegistry } from '../src/validation/resource-rules.js'; import type { ResourceRule, RuleMeta, RuleContext, ValidationResult } from '../src/validation/resource-rules.js'; const noopCtx: RuleContext = { findResource: async () => null, }; function makeRule( name: string, resource: string, matchFn: (data: unknown, meta: RuleMeta) => boolean, validateFn: () => Promise, ): ResourceRule { return { name, resource, match: matchFn, validate: validateFn }; } describe('ResourceRuleRegistry', () => { it('returns valid when no rules are registered', async () => { const registry = new ResourceRuleRegistry(); const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx); expect(result.valid).toBe(true); }); it('returns valid when no rules match the resource type', async () => { const registry = new ResourceRuleRegistry(); registry.register(makeRule('server-rule', 'server', () => true, async () => ({ valid: false, errors: ['fail'] }))); const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx); expect(result.valid).toBe(true); }); it('skips rules that do not match the predicate', async () => { const registry = new ResourceRuleRegistry(); registry.register(makeRule('skip-rule', 'prompt', () => false, async () => ({ valid: false, errors: ['fail'] }))); const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx); expect(result.valid).toBe(true); }); it('runs matching rules and returns errors', async () => { const registry = new ResourceRuleRegistry(); registry.register(makeRule('bad-rule', 'prompt', () => true, async () => ({ valid: false, errors: ['missing template var'], }))); const result = await registry.validate('prompt', { name: 'x' }, { operation: 'update' }, noopCtx); expect(result.valid).toBe(false); expect(result.errors).toEqual(['missing template var']); }); it('aggregates errors from multiple matching rules', async () => { const registry = new ResourceRuleRegistry(); registry.register(makeRule('rule-1', 'prompt', () => true, async () => ({ valid: false, errors: ['error A'], }))); registry.register(makeRule('rule-2', 'prompt', () => true, async () => ({ valid: false, errors: ['error B', 'error C'], }))); const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx); expect(result.valid).toBe(false); expect(result.errors).toEqual(['error A', 'error B', 'error C']); }); it('returns valid when all matching rules pass', async () => { const registry = new ResourceRuleRegistry(); registry.register(makeRule('ok-rule', 'prompt', () => true, async () => ({ valid: true }))); const result = await registry.validate('prompt', { name: 'x' }, { operation: 'update' }, noopCtx); expect(result.valid).toBe(true); }); it('passes meta to match function', async () => { const registry = new ResourceRuleRegistry(); let receivedMeta: RuleMeta | null = null; registry.register(makeRule('meta-check', 'prompt', (_data, meta) => { receivedMeta = meta; return false; }, async () => ({ valid: true }))); await registry.validate('prompt', { name: 'x' }, { operation: 'update', isSystemResource: true, projectName: 'sys' }, noopCtx); expect(receivedMeta).toEqual({ operation: 'update', isSystemResource: true, projectName: 'sys' }); }); });