Skip to content

Commit ba649fb

Browse files
Copilothotlong
andcommitted
Add test files for automation trigger-registry, sync, approval, and etl schemas
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 5765077 commit ba649fb

4 files changed

Lines changed: 1228 additions & 0 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
ApproverType,
4+
ApprovalActionType,
5+
ApprovalActionSchema,
6+
ApprovalStepSchema,
7+
ApprovalProcessSchema,
8+
ApprovalProcess,
9+
} from './approval.zod';
10+
11+
describe('ApproverType', () => {
12+
it('should accept all valid approver types', () => {
13+
['user', 'role', 'manager', 'field', 'queue'].forEach(t => {
14+
expect(() => ApproverType.parse(t)).not.toThrow();
15+
});
16+
});
17+
18+
it('should reject invalid approver type', () => {
19+
expect(() => ApproverType.parse('group')).toThrow();
20+
});
21+
});
22+
23+
describe('ApprovalActionType', () => {
24+
it('should accept all valid action types', () => {
25+
['field_update', 'email_alert', 'webhook', 'script', 'connector_action'].forEach(t => {
26+
expect(() => ApprovalActionType.parse(t)).not.toThrow();
27+
});
28+
});
29+
30+
it('should reject invalid action type', () => {
31+
expect(() => ApprovalActionType.parse('sms')).toThrow();
32+
});
33+
});
34+
35+
describe('ApprovalActionSchema', () => {
36+
it('should accept valid action', () => {
37+
expect(() => ApprovalActionSchema.parse({
38+
type: 'field_update',
39+
name: 'Set Status',
40+
config: { field: 'status', value: 'approved' },
41+
})).not.toThrow();
42+
});
43+
44+
it('should accept connector action with optional fields', () => {
45+
expect(() => ApprovalActionSchema.parse({
46+
type: 'connector_action',
47+
name: 'Notify Slack',
48+
config: { channel: '#approvals' },
49+
connectorId: 'slack',
50+
actionId: 'send_message',
51+
})).not.toThrow();
52+
});
53+
54+
it('should reject missing type', () => {
55+
expect(() => ApprovalActionSchema.parse({
56+
name: 'Bad Action',
57+
config: {},
58+
})).toThrow();
59+
});
60+
61+
it('should reject missing name', () => {
62+
expect(() => ApprovalActionSchema.parse({
63+
type: 'webhook',
64+
config: { url: 'https://example.com' },
65+
})).toThrow();
66+
});
67+
68+
it('should reject missing config', () => {
69+
expect(() => ApprovalActionSchema.parse({
70+
type: 'email_alert',
71+
name: 'Send Email',
72+
})).toThrow();
73+
});
74+
});
75+
76+
describe('ApprovalStepSchema', () => {
77+
const minimalStep = {
78+
name: 'manager_review',
79+
label: 'Manager Review',
80+
approvers: [{ type: 'manager', value: 'direct_manager' }],
81+
};
82+
83+
it('should accept minimal step with defaults', () => {
84+
const result = ApprovalStepSchema.parse(minimalStep);
85+
expect(result.behavior).toBe('first_response');
86+
expect(result.rejectionBehavior).toBe('reject_process');
87+
});
88+
89+
it('should accept full step', () => {
90+
expect(() => ApprovalStepSchema.parse({
91+
name: 'vp_review',
92+
label: 'VP Review',
93+
description: 'VP must approve expenses over $10k',
94+
entryCriteria: 'amount > 10000',
95+
approvers: [
96+
{ type: 'role', value: 'vp_finance' },
97+
{ type: 'user', value: 'user_001' },
98+
],
99+
behavior: 'unanimous',
100+
rejectionBehavior: 'back_to_previous',
101+
onApprove: [{ type: 'field_update', name: 'Update Status', config: { field: 'status', value: 'vp_approved' } }],
102+
onReject: [{ type: 'email_alert', name: 'Notify Submitter', config: { template: 'rejection' } }],
103+
})).not.toThrow();
104+
});
105+
106+
it('should reject invalid name (not snake_case)', () => {
107+
expect(() => ApprovalStepSchema.parse({
108+
...minimalStep,
109+
name: 'ManagerReview',
110+
})).toThrow();
111+
});
112+
113+
it('should reject empty approvers array', () => {
114+
expect(() => ApprovalStepSchema.parse({
115+
...minimalStep,
116+
approvers: [],
117+
})).toThrow();
118+
});
119+
120+
it('should reject missing approvers', () => {
121+
expect(() => ApprovalStepSchema.parse({
122+
name: 'bad_step',
123+
label: 'Bad Step',
124+
})).toThrow();
125+
});
126+
});
127+
128+
describe('ApprovalProcessSchema', () => {
129+
const minimalProcess = {
130+
name: 'expense_approval',
131+
label: 'Expense Approval',
132+
object: 'expense_report',
133+
steps: [{
134+
name: 'manager_review',
135+
label: 'Manager Review',
136+
approvers: [{ type: 'manager', value: 'direct_manager' }],
137+
}],
138+
};
139+
140+
it('should accept minimal process with defaults', () => {
141+
const result = ApprovalProcessSchema.parse(minimalProcess);
142+
expect(result.active).toBe(false);
143+
expect(result.lockRecord).toBe(true);
144+
});
145+
146+
it('should accept full process', () => {
147+
expect(() => ApprovalProcessSchema.parse({
148+
name: 'purchase_approval',
149+
label: 'Purchase Approval',
150+
object: 'purchase_order',
151+
active: true,
152+
description: 'Multi-step purchase approval',
153+
entryCriteria: 'amount > 1000',
154+
lockRecord: false,
155+
steps: [
156+
{
157+
name: 'manager_review',
158+
label: 'Manager Review',
159+
approvers: [{ type: 'manager', value: 'direct_manager' }],
160+
},
161+
{
162+
name: 'finance_review',
163+
label: 'Finance Review',
164+
entryCriteria: 'amount > 5000',
165+
approvers: [{ type: 'role', value: 'finance_team' }],
166+
behavior: 'unanimous',
167+
},
168+
],
169+
onSubmit: [{ type: 'field_update', name: 'Lock', config: { field: 'status', value: 'submitted' } }],
170+
onFinalApprove: [{ type: 'email_alert', name: 'Approved', config: { template: 'approved' } }],
171+
onFinalReject: [{ type: 'webhook', name: 'Notify', config: { url: 'https://example.com/reject' } }],
172+
onRecall: [{ type: 'field_update', name: 'Reset', config: { field: 'status', value: 'draft' } }],
173+
})).not.toThrow();
174+
});
175+
176+
it('should reject empty steps array', () => {
177+
expect(() => ApprovalProcessSchema.parse({
178+
...minimalProcess,
179+
steps: [],
180+
})).toThrow();
181+
});
182+
183+
it('should reject invalid process name', () => {
184+
expect(() => ApprovalProcessSchema.parse({
185+
...minimalProcess,
186+
name: 'ExpenseApproval',
187+
})).toThrow();
188+
});
189+
190+
it('should reject missing object', () => {
191+
expect(() => ApprovalProcessSchema.parse({
192+
name: 'test_process',
193+
label: 'Test',
194+
steps: [{
195+
name: 'step_one',
196+
label: 'Step One',
197+
approvers: [{ type: 'user', value: 'admin' }],
198+
}],
199+
})).toThrow();
200+
});
201+
});
202+
203+
describe('ApprovalProcess.create', () => {
204+
it('should return the config object as-is', () => {
205+
const config = {
206+
name: 'quick_approval',
207+
label: 'Quick Approval',
208+
object: 'invoice',
209+
steps: [{
210+
name: 'review',
211+
label: 'Review',
212+
approvers: [{ type: 'role' as const, value: 'reviewer' }],
213+
}],
214+
};
215+
const result = ApprovalProcess.create(config);
216+
expect(result).toEqual(config);
217+
});
218+
});

0 commit comments

Comments
 (0)