89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
|
|
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<ValidationResult>,
|
||
|
|
): 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' });
|
||
|
|
});
|
||
|
|
});
|