Skip to content

Commit a6051e6

Browse files
Copilothotlong
andcommitted
Add test files for qa/testing, ui/component, ai/feedback-loop, and studio/plugin schemas
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 70d10cb commit a6051e6

4 files changed

Lines changed: 878 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
MetadataSourceSchema,
4+
IssueSchema,
5+
ResolutionSchema,
6+
FeedbackLoopSchema,
7+
} from './feedback-loop.zod';
8+
9+
describe('MetadataSourceSchema', () => {
10+
it('should accept empty object (all fields optional)', () => {
11+
const result = MetadataSourceSchema.parse({});
12+
expect(result.file).toBeUndefined();
13+
expect(result.line).toBeUndefined();
14+
expect(result.column).toBeUndefined();
15+
expect(result.package).toBeUndefined();
16+
expect(result.object).toBeUndefined();
17+
expect(result.field).toBeUndefined();
18+
expect(result.component).toBeUndefined();
19+
});
20+
21+
it('should accept full source metadata', () => {
22+
const source = {
23+
file: 'src/objects/account.ts',
24+
line: 42,
25+
column: 10,
26+
package: 'crm',
27+
object: 'account',
28+
field: 'status',
29+
component: 'StatusDropdown',
30+
};
31+
expect(() => MetadataSourceSchema.parse(source)).not.toThrow();
32+
});
33+
34+
it('should reject non-object values', () => {
35+
expect(() => MetadataSourceSchema.parse('string')).toThrow();
36+
});
37+
});
38+
39+
describe('IssueSchema', () => {
40+
const validIssue = {
41+
id: 'issue-001',
42+
severity: 'error',
43+
message: 'Field validation failed',
44+
timestamp: '2024-01-15T10:30:00Z',
45+
};
46+
47+
it('should accept minimal valid issue', () => {
48+
const result = IssueSchema.parse(validIssue);
49+
expect(result.id).toBe('issue-001');
50+
expect(result.severity).toBe('error');
51+
expect(result.stackTrace).toBeUndefined();
52+
expect(result.userId).toBeUndefined();
53+
expect(result.context).toBeUndefined();
54+
expect(result.source).toBeUndefined();
55+
});
56+
57+
it('should accept all severity levels', () => {
58+
const severities = ['critical', 'error', 'warning', 'info'] as const;
59+
severities.forEach(severity => {
60+
expect(() => IssueSchema.parse({ ...validIssue, severity })).not.toThrow();
61+
});
62+
});
63+
64+
it('should accept full issue with all optional fields', () => {
65+
const issue = {
66+
...validIssue,
67+
stackTrace: 'Error at line 42\n at module.ts:10',
68+
userId: 'user-123',
69+
context: { recordId: 'rec-456', action: 'save' },
70+
source: { file: 'src/objects/account.ts', line: 42 },
71+
};
72+
expect(() => IssueSchema.parse(issue)).not.toThrow();
73+
});
74+
75+
it('should reject invalid severity', () => {
76+
expect(() => IssueSchema.parse({ ...validIssue, severity: 'fatal' })).toThrow();
77+
});
78+
79+
it('should reject missing required fields', () => {
80+
expect(() => IssueSchema.parse({ id: 'x' })).toThrow();
81+
expect(() => IssueSchema.parse({ id: 'x', severity: 'error' })).toThrow();
82+
});
83+
84+
it('should reject invalid timestamp format', () => {
85+
expect(() => IssueSchema.parse({ ...validIssue, timestamp: 'not-a-date' })).toThrow();
86+
});
87+
});
88+
89+
describe('ResolutionSchema', () => {
90+
const metadataChangeResolution = {
91+
issueId: 'issue-001',
92+
reasoning: 'The field type is incompatible with the data',
93+
confidence: 0.85,
94+
fix: {
95+
type: 'metadata_change',
96+
changeSet: {
97+
id: '550e8400-e29b-41d4-a716-446655440000',
98+
name: 'Fix field type',
99+
operations: [{ type: 'add_field', objectName: 'account', fieldName: 'status', field: { name: 'status', type: 'text' } }],
100+
},
101+
},
102+
};
103+
104+
const manualResolution = {
105+
issueId: 'issue-002',
106+
reasoning: 'Requires manual database migration',
107+
confidence: 0.6,
108+
fix: {
109+
type: 'manual_intervention',
110+
instructions: 'Run ALTER TABLE to update the column type',
111+
},
112+
};
113+
114+
it('should accept metadata_change resolution', () => {
115+
const result = ResolutionSchema.parse(metadataChangeResolution);
116+
expect(result.fix.type).toBe('metadata_change');
117+
expect(result.confidence).toBe(0.85);
118+
});
119+
120+
it('should accept manual_intervention resolution', () => {
121+
const result = ResolutionSchema.parse(manualResolution);
122+
expect(result.fix.type).toBe('manual_intervention');
123+
});
124+
125+
it('should reject confidence below 0', () => {
126+
expect(() => ResolutionSchema.parse({ ...metadataChangeResolution, confidence: -0.1 })).toThrow();
127+
});
128+
129+
it('should reject confidence above 1', () => {
130+
expect(() => ResolutionSchema.parse({ ...metadataChangeResolution, confidence: 1.1 })).toThrow();
131+
});
132+
133+
it('should reject invalid fix type', () => {
134+
expect(() => ResolutionSchema.parse({
135+
issueId: 'x',
136+
reasoning: 'test',
137+
confidence: 0.5,
138+
fix: { type: 'auto_fix', data: {} },
139+
})).toThrow();
140+
});
141+
142+
it('should reject missing required fields', () => {
143+
expect(() => ResolutionSchema.parse({ issueId: 'x' })).toThrow();
144+
});
145+
});
146+
147+
describe('FeedbackLoopSchema', () => {
148+
const validIssue = {
149+
id: 'issue-001',
150+
severity: 'warning',
151+
message: 'Slow query detected',
152+
timestamp: '2024-06-01T12:00:00Z',
153+
};
154+
155+
it('should accept minimal feedback loop with defaults', () => {
156+
const result = FeedbackLoopSchema.parse({ issue: validIssue });
157+
expect(result.status).toBe('open');
158+
expect(result.analysis).toBeUndefined();
159+
expect(result.resolutions).toBeUndefined();
160+
});
161+
162+
it('should accept all status values', () => {
163+
const statuses = ['open', 'analyzing', 'resolved', 'ignored'] as const;
164+
statuses.forEach(status => {
165+
expect(() => FeedbackLoopSchema.parse({ issue: validIssue, status })).not.toThrow();
166+
});
167+
});
168+
169+
it('should accept full feedback loop', () => {
170+
const loop = {
171+
issue: validIssue,
172+
analysis: 'The query lacks proper indexing',
173+
resolutions: [{
174+
issueId: 'issue-001',
175+
reasoning: 'Add index on account.status',
176+
confidence: 0.9,
177+
fix: { type: 'manual_intervention', instructions: 'CREATE INDEX idx_status ON account(status)' },
178+
}],
179+
status: 'resolved',
180+
};
181+
expect(() => FeedbackLoopSchema.parse(loop)).not.toThrow();
182+
});
183+
184+
it('should reject without issue', () => {
185+
expect(() => FeedbackLoopSchema.parse({ status: 'open' })).toThrow();
186+
});
187+
188+
it('should reject invalid status', () => {
189+
expect(() => FeedbackLoopSchema.parse({ issue: validIssue, status: 'pending' })).toThrow();
190+
});
191+
});
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
TestContextSchema,
4+
TestActionTypeSchema,
5+
TestActionSchema,
6+
TestAssertionTypeSchema,
7+
TestAssertionSchema,
8+
TestStepSchema,
9+
TestScenarioSchema,
10+
TestSuiteSchema,
11+
} from './testing.zod';
12+
13+
describe('TestContextSchema', () => {
14+
it('should accept a valid context record', () => {
15+
expect(() => TestContextSchema.parse({ userId: '123', debug: true })).not.toThrow();
16+
});
17+
18+
it('should accept an empty record', () => {
19+
expect(() => TestContextSchema.parse({})).not.toThrow();
20+
});
21+
22+
it('should reject non-object values', () => {
23+
expect(() => TestContextSchema.parse('invalid')).toThrow();
24+
});
25+
});
26+
27+
describe('TestActionTypeSchema', () => {
28+
it('should accept all valid action types', () => {
29+
const types = ['create_record', 'update_record', 'delete_record', 'read_record', 'query_records', 'api_call', 'run_script', 'wait'];
30+
types.forEach(t => {
31+
expect(() => TestActionTypeSchema.parse(t)).not.toThrow();
32+
});
33+
});
34+
35+
it('should reject invalid action type', () => {
36+
expect(() => TestActionTypeSchema.parse('invalid_type')).toThrow();
37+
});
38+
});
39+
40+
describe('TestActionSchema', () => {
41+
it('should accept minimal valid action', () => {
42+
const action = { type: 'create_record', target: 'account' };
43+
const result = TestActionSchema.parse(action);
44+
expect(result.type).toBe('create_record');
45+
expect(result.target).toBe('account');
46+
expect(result.payload).toBeUndefined();
47+
expect(result.user).toBeUndefined();
48+
});
49+
50+
it('should accept full action with all optional fields', () => {
51+
const action = {
52+
type: 'api_call',
53+
target: '/api/v1/accounts',
54+
payload: { name: 'Test Account' },
55+
user: 'admin',
56+
};
57+
expect(() => TestActionSchema.parse(action)).not.toThrow();
58+
});
59+
60+
it('should reject action without target', () => {
61+
expect(() => TestActionSchema.parse({ type: 'create_record' })).toThrow();
62+
});
63+
64+
it('should reject action with invalid type', () => {
65+
expect(() => TestActionSchema.parse({ type: 'bad_type', target: 'x' })).toThrow();
66+
});
67+
});
68+
69+
describe('TestAssertionTypeSchema', () => {
70+
it('should accept all valid assertion types', () => {
71+
const types = ['equals', 'not_equals', 'contains', 'not_contains', 'is_null', 'not_null', 'gt', 'gte', 'lt', 'lte', 'error'];
72+
types.forEach(t => {
73+
expect(() => TestAssertionTypeSchema.parse(t)).not.toThrow();
74+
});
75+
});
76+
77+
it('should reject invalid assertion type', () => {
78+
expect(() => TestAssertionTypeSchema.parse('unknown')).toThrow();
79+
});
80+
});
81+
82+
describe('TestAssertionSchema', () => {
83+
it('should accept a valid assertion', () => {
84+
const assertion = { field: 'body.status', operator: 'equals', expectedValue: 'active' };
85+
const result = TestAssertionSchema.parse(assertion);
86+
expect(result.field).toBe('body.status');
87+
expect(result.operator).toBe('equals');
88+
expect(result.expectedValue).toBe('active');
89+
});
90+
91+
it('should reject assertion without field', () => {
92+
expect(() => TestAssertionSchema.parse({ operator: 'equals', expectedValue: 1 })).toThrow();
93+
});
94+
95+
it('should reject assertion with invalid operator', () => {
96+
expect(() => TestAssertionSchema.parse({ field: 'x', operator: 'invalid', expectedValue: 1 })).toThrow();
97+
});
98+
});
99+
100+
describe('TestStepSchema', () => {
101+
const minimalStep = {
102+
name: 'Create account',
103+
action: { type: 'create_record', target: 'account' },
104+
};
105+
106+
it('should accept minimal step', () => {
107+
const result = TestStepSchema.parse(minimalStep);
108+
expect(result.name).toBe('Create account');
109+
expect(result.description).toBeUndefined();
110+
expect(result.assertions).toBeUndefined();
111+
expect(result.capture).toBeUndefined();
112+
});
113+
114+
it('should accept step with all optional fields', () => {
115+
const step = {
116+
...minimalStep,
117+
description: 'Creates a new account record',
118+
assertions: [{ field: 'body._id', operator: 'not_null', expectedValue: null }],
119+
capture: { newId: 'body._id' },
120+
};
121+
expect(() => TestStepSchema.parse(step)).not.toThrow();
122+
});
123+
124+
it('should reject step without name', () => {
125+
expect(() => TestStepSchema.parse({ action: { type: 'read_record', target: 'x' } })).toThrow();
126+
});
127+
128+
it('should reject step without action', () => {
129+
expect(() => TestStepSchema.parse({ name: 'step1' })).toThrow();
130+
});
131+
});
132+
133+
describe('TestScenarioSchema', () => {
134+
const minimalScenario = {
135+
id: 'sc-001',
136+
name: 'Account CRUD Test',
137+
steps: [{ name: 'step1', action: { type: 'create_record', target: 'account' } }],
138+
};
139+
140+
it('should accept minimal scenario', () => {
141+
const result = TestScenarioSchema.parse(minimalScenario);
142+
expect(result.id).toBe('sc-001');
143+
expect(result.name).toBe('Account CRUD Test');
144+
expect(result.steps).toHaveLength(1);
145+
expect(result.description).toBeUndefined();
146+
expect(result.tags).toBeUndefined();
147+
expect(result.setup).toBeUndefined();
148+
expect(result.teardown).toBeUndefined();
149+
expect(result.requires).toBeUndefined();
150+
});
151+
152+
it('should accept full scenario with all optional fields', () => {
153+
const scenario = {
154+
...minimalScenario,
155+
description: 'Tests full CRUD lifecycle',
156+
tags: ['critical', 'regression'],
157+
setup: [{ name: 'setup-data', action: { type: 'run_script', target: 'seed' } }],
158+
teardown: [{ name: 'cleanup', action: { type: 'delete_record', target: 'account' } }],
159+
requires: {
160+
params: ['API_KEY'],
161+
plugins: ['crm'],
162+
},
163+
};
164+
expect(() => TestScenarioSchema.parse(scenario)).not.toThrow();
165+
});
166+
167+
it('should reject scenario without steps', () => {
168+
expect(() => TestScenarioSchema.parse({ id: 'sc-001', name: 'Test' })).toThrow();
169+
});
170+
171+
it('should reject scenario without id', () => {
172+
expect(() => TestScenarioSchema.parse({ name: 'Test', steps: [] })).toThrow();
173+
});
174+
});
175+
176+
describe('TestSuiteSchema', () => {
177+
it('should accept a valid test suite', () => {
178+
const suite = {
179+
name: 'CRM Test Suite',
180+
scenarios: [{
181+
id: 'sc-001',
182+
name: 'Account Test',
183+
steps: [{ name: 'step1', action: { type: 'read_record', target: 'account' } }],
184+
}],
185+
};
186+
const result = TestSuiteSchema.parse(suite);
187+
expect(result.name).toBe('CRM Test Suite');
188+
expect(result.scenarios).toHaveLength(1);
189+
});
190+
191+
it('should accept suite with empty scenarios', () => {
192+
expect(() => TestSuiteSchema.parse({ name: 'Empty Suite', scenarios: [] })).not.toThrow();
193+
});
194+
195+
it('should reject suite without name', () => {
196+
expect(() => TestSuiteSchema.parse({ scenarios: [] })).toThrow();
197+
});
198+
199+
it('should reject suite without scenarios', () => {
200+
expect(() => TestSuiteSchema.parse({ name: 'Suite' })).toThrow();
201+
});
202+
});

0 commit comments

Comments
 (0)