digital-workers 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +136 -180
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +34 -21
- package/dist/actions.js.map +1 -1
- package/dist/agent-comms.d.ts +438 -0
- package/dist/agent-comms.d.ts.map +1 -0
- package/dist/agent-comms.js +677 -0
- package/dist/agent-comms.js.map +1 -0
- package/dist/approve.d.ts +40 -8
- package/dist/approve.d.ts.map +1 -1
- package/dist/approve.js +86 -20
- package/dist/approve.js.map +1 -1
- package/dist/ask.d.ts +38 -7
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +85 -25
- package/dist/ask.js.map +1 -1
- package/dist/browse.d.ts +223 -0
- package/dist/browse.d.ts.map +1 -0
- package/dist/browse.js +392 -0
- package/dist/browse.js.map +1 -0
- package/dist/capability-tiers.d.ts +230 -0
- package/dist/capability-tiers.d.ts.map +1 -0
- package/dist/capability-tiers.js +388 -0
- package/dist/capability-tiers.js.map +1 -0
- package/dist/cascade-context.d.ts +523 -0
- package/dist/cascade-context.d.ts.map +1 -0
- package/dist/cascade-context.js +494 -0
- package/dist/cascade-context.js.map +1 -0
- package/dist/client.d.ts +162 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +64 -0
- package/dist/client.js.map +1 -0
- package/dist/decide.d.ts +42 -6
- package/dist/decide.d.ts.map +1 -1
- package/dist/decide.js +54 -11
- package/dist/decide.js.map +1 -1
- package/dist/do.d.ts +36 -7
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +82 -39
- package/dist/do.js.map +1 -1
- package/dist/error-escalation.d.ts +416 -0
- package/dist/error-escalation.d.ts.map +1 -0
- package/dist/error-escalation.js +656 -0
- package/dist/error-escalation.js.map +1 -0
- package/dist/generate.d.ts +48 -7
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +49 -8
- package/dist/generate.js.map +1 -1
- package/dist/goals.d.ts +10 -9
- package/dist/goals.d.ts.map +1 -1
- package/dist/goals.js +30 -24
- package/dist/goals.js.map +1 -1
- package/dist/image.d.ts +189 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +528 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +59 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +92 -2
- package/dist/index.js.map +1 -1
- package/dist/is.d.ts +45 -10
- package/dist/is.d.ts.map +1 -1
- package/dist/is.js +56 -21
- package/dist/is.js.map +1 -1
- package/dist/kpis.d.ts +24 -15
- package/dist/kpis.d.ts.map +1 -1
- package/dist/kpis.js +16 -14
- package/dist/kpis.js.map +1 -1
- package/dist/load-balancing.d.ts +395 -0
- package/dist/load-balancing.d.ts.map +1 -0
- package/dist/load-balancing.js +991 -0
- package/dist/load-balancing.js.map +1 -0
- package/dist/logger.d.ts +76 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/notify.d.ts +38 -9
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +72 -17
- package/dist/notify.js.map +1 -1
- package/dist/role.d.ts +5 -4
- package/dist/role.d.ts.map +1 -1
- package/dist/role.js +13 -10
- package/dist/role.js.map +1 -1
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +510 -0
- package/dist/runtime.js.map +1 -0
- package/dist/team.d.ts +11 -6
- package/dist/team.d.ts.map +1 -1
- package/dist/team.js +22 -15
- package/dist/team.js.map +1 -1
- package/dist/transports/email.d.ts +318 -0
- package/dist/transports/email.d.ts.map +1 -0
- package/dist/transports/email.js +779 -0
- package/dist/transports/email.js.map +1 -0
- package/dist/transports/slack.d.ts +515 -0
- package/dist/transports/slack.d.ts.map +1 -0
- package/dist/transports/slack.js +844 -0
- package/dist/transports/slack.js.map +1 -0
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +44 -25
- package/dist/transports.js.map +1 -1
- package/dist/types.d.ts +149 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/id.d.ts +19 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +21 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/video.d.ts +203 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +528 -0
- package/dist/video.js.map +1 -0
- package/dist/worker.d.ts +343 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +698 -0
- package/dist/worker.js.map +1 -0
- package/package.json +24 -5
- package/src/actions.ts +48 -38
- package/src/agent-comms.ts +1200 -0
- package/src/approve.ts +91 -20
- package/src/ask.ts +99 -25
- package/src/browse.ts +627 -0
- package/src/capability-tiers.ts +545 -0
- package/src/cascade-context.ts +648 -0
- package/src/client.ts +221 -0
- package/src/decide.ts +81 -35
- package/src/do.ts +98 -52
- package/src/error-escalation.ts +1123 -0
- package/src/generate.ts +52 -18
- package/src/goals.ts +36 -27
- package/src/image.ts +816 -0
- package/src/index.ts +410 -2
- package/src/is.ts +59 -25
- package/src/kpis.ts +41 -36
- package/src/load-balancing.ts +1467 -0
- package/src/logger.ts +93 -0
- package/src/notify.ts +78 -17
- package/src/role.ts +30 -20
- package/src/runtime.ts +796 -0
- package/src/team.ts +24 -19
- package/src/transports/email.ts +1160 -0
- package/src/transports/slack.ts +1320 -0
- package/src/transports.ts +58 -43
- package/src/types.ts +182 -46
- package/src/utils/id.ts +21 -0
- package/src/video.ts +906 -0
- package/src/worker.ts +1007 -0
- package/test/agent-comms.test.ts +1397 -0
- package/test/approve.test.ts +305 -0
- package/test/ask.test.ts +274 -0
- package/test/browse.test.ts +361 -0
- package/test/capability-tiers.test.ts +631 -0
- package/test/cascade-context.test.ts +692 -0
- package/test/decide.test.ts +252 -0
- package/test/do.test.ts +144 -0
- package/test/error-escalation.test.ts +1205 -0
- package/test/error-logging.test.ts +357 -0
- package/test/generate.test.ts +319 -0
- package/test/image.test.ts +398 -0
- package/test/is.test.ts +287 -0
- package/test/load-balancing-safety.test.ts +404 -0
- package/test/load-balancing-thread-safety.test.ts +464 -0
- package/test/load-balancing.test.ts +1145 -0
- package/test/notify.test.ts +434 -0
- package/test/primitives.test.ts +320 -0
- package/test/runtime-integration.test.ts +892 -0
- package/test/transports/crypto.test.ts +230 -0
- package/test/transports/email.test.ts +866 -0
- package/test/transports/id-generation.test.ts +91 -0
- package/test/transports/slack.test.ts +760 -0
- package/test/type-safety.test.ts +834 -0
- package/test/types.test.ts +95 -2
- package/test/video.test.ts +530 -0
- package/test/worker.test.ts +1433 -0
- package/tsconfig.json +4 -1
- package/vitest.config.ts +42 -0
- package/wrangler.jsonc +36 -0
- package/.turbo/turbo-build.log +0 -5
- package/src/actions.js +0 -436
- package/src/approve.js +0 -234
- package/src/ask.js +0 -226
- package/src/decide.js +0 -244
- package/src/do.js +0 -227
- package/src/generate.js +0 -298
- package/src/goals.js +0 -205
- package/src/index.js +0 -68
- package/src/is.js +0 -317
- package/src/kpis.js +0 -270
- package/src/notify.js +0 -219
- package/src/role.js +0 -110
- package/src/team.js +0 -130
- package/src/transports.js +0 -357
- package/src/types.js +0 -71
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Safety Tests for digital-workers
|
|
3
|
+
*
|
|
4
|
+
* Tests verifying proper handling of optional properties with
|
|
5
|
+
* `exactOptionalPropertyTypes: true` TypeScript configuration.
|
|
6
|
+
*
|
|
7
|
+
* With this setting, optional properties (prop?: T) cannot have `undefined`
|
|
8
|
+
* explicitly assigned. Instead, use either:
|
|
9
|
+
* 1. Conditional assignment pattern: `if (value !== undefined) result.prop = value`
|
|
10
|
+
* 2. Spread pattern: `...(value !== undefined && { prop: value })`
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it, expect, expectTypeOf } from 'vitest'
|
|
16
|
+
import type {
|
|
17
|
+
Worker,
|
|
18
|
+
WorkerRef,
|
|
19
|
+
Contacts,
|
|
20
|
+
ContactPreferences,
|
|
21
|
+
EmailContact,
|
|
22
|
+
SlackContact,
|
|
23
|
+
PhoneContact,
|
|
24
|
+
NotifyResult,
|
|
25
|
+
AskResult,
|
|
26
|
+
ApprovalResult,
|
|
27
|
+
DecideResult,
|
|
28
|
+
DoResult,
|
|
29
|
+
WorkerTeam,
|
|
30
|
+
WorkerRole,
|
|
31
|
+
WorkerGoals,
|
|
32
|
+
WorkerKPI,
|
|
33
|
+
WorkerOKR,
|
|
34
|
+
NotifyOptions,
|
|
35
|
+
AskOptions,
|
|
36
|
+
ApproveOptions,
|
|
37
|
+
DecideOptions,
|
|
38
|
+
DoOptions,
|
|
39
|
+
GenerateOptions,
|
|
40
|
+
GenerateResult,
|
|
41
|
+
IsOptions,
|
|
42
|
+
TypeCheckResult,
|
|
43
|
+
} from '../src/types.js'
|
|
44
|
+
import type {
|
|
45
|
+
RouteResult,
|
|
46
|
+
AgentInfo,
|
|
47
|
+
TaskRequest,
|
|
48
|
+
RoutingMetrics,
|
|
49
|
+
AgentAvailability,
|
|
50
|
+
RoutingRule,
|
|
51
|
+
EscalationPath,
|
|
52
|
+
CompositeBalancerConfig,
|
|
53
|
+
} from '../src/load-balancing.js'
|
|
54
|
+
import type {
|
|
55
|
+
ClassifiedError,
|
|
56
|
+
ErrorContext,
|
|
57
|
+
EscalationPolicy,
|
|
58
|
+
RetryConfig,
|
|
59
|
+
RetryState,
|
|
60
|
+
FallbackConfig,
|
|
61
|
+
AgentForFallback,
|
|
62
|
+
RecoveryState,
|
|
63
|
+
EscalationResult,
|
|
64
|
+
HandleErrorOptions,
|
|
65
|
+
} from '../src/error-escalation.js'
|
|
66
|
+
import type {
|
|
67
|
+
EmailMessage,
|
|
68
|
+
EmailSendResult,
|
|
69
|
+
ApprovalRequestData,
|
|
70
|
+
ParsedEmailReply,
|
|
71
|
+
EmailTransportConfig,
|
|
72
|
+
} from '../src/transports/email.js'
|
|
73
|
+
import type {
|
|
74
|
+
SlackMessage,
|
|
75
|
+
SlackTransportConfig,
|
|
76
|
+
WebhookHandlerResult,
|
|
77
|
+
} from '../src/transports/slack.js'
|
|
78
|
+
import type { TransportConfig, MessagePayload, DeliveryResult, Address } from '../src/transports.js'
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Helper Type Assertions
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Type-level assertion that T is assignable to U
|
|
86
|
+
*/
|
|
87
|
+
type AssertAssignable<T, U> = T extends U ? true : false
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates an object with conditional property assignment
|
|
91
|
+
* This is the pattern used throughout the codebase
|
|
92
|
+
*/
|
|
93
|
+
function createWithConditionalProps<T extends object>(base: T, optionals: Partial<T>): T {
|
|
94
|
+
const result = { ...base }
|
|
95
|
+
for (const [key, value] of Object.entries(optionals)) {
|
|
96
|
+
if (value !== undefined) {
|
|
97
|
+
;(result as Record<string, unknown>)[key] = value
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// types.ts - Worker Types
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
describe('types.ts - Optional Property Handling', () => {
|
|
108
|
+
describe('Worker interface', () => {
|
|
109
|
+
it('should create Worker with conditional optional properties', () => {
|
|
110
|
+
const id = 'worker_1'
|
|
111
|
+
const name = 'Test Worker'
|
|
112
|
+
const role = undefined // Simulating optional value that might be undefined
|
|
113
|
+
|
|
114
|
+
// Pattern: conditional assignment
|
|
115
|
+
const worker: Worker = {
|
|
116
|
+
id,
|
|
117
|
+
name,
|
|
118
|
+
type: 'human',
|
|
119
|
+
status: 'available',
|
|
120
|
+
contacts: {},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (role !== undefined) {
|
|
124
|
+
worker.role = role as never // This branch won't execute
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
expect(worker.id).toBe('worker_1')
|
|
128
|
+
expect('role' in worker).toBe(false)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should create Worker with spread pattern for optional properties', () => {
|
|
132
|
+
const capabilityTier: 'code' | undefined = 'code'
|
|
133
|
+
const skills: string[] | undefined = undefined
|
|
134
|
+
|
|
135
|
+
const worker: Worker = {
|
|
136
|
+
id: 'worker_2',
|
|
137
|
+
name: 'Agent',
|
|
138
|
+
type: 'agent',
|
|
139
|
+
status: 'available',
|
|
140
|
+
contacts: {},
|
|
141
|
+
...(capabilityTier !== undefined && { capabilityTier }),
|
|
142
|
+
...(skills !== undefined && { skills }),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
expect(worker.capabilityTier).toBe('code')
|
|
146
|
+
expect('skills' in worker).toBe(false)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should handle nested optional contact properties', () => {
|
|
150
|
+
const slackUser: string | undefined = 'U12345'
|
|
151
|
+
const slackChannel: string | undefined = undefined
|
|
152
|
+
|
|
153
|
+
const slackContact: SlackContact = {}
|
|
154
|
+
if (slackUser !== undefined) slackContact.user = slackUser
|
|
155
|
+
if (slackChannel !== undefined) slackContact.channel = slackChannel
|
|
156
|
+
|
|
157
|
+
const worker: Worker = {
|
|
158
|
+
id: 'worker_3',
|
|
159
|
+
name: 'Worker',
|
|
160
|
+
type: 'human',
|
|
161
|
+
status: 'available',
|
|
162
|
+
contacts: {
|
|
163
|
+
slack: slackContact,
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
expect((worker.contacts.slack as SlackContact).user).toBe('U12345')
|
|
168
|
+
expect('channel' in (worker.contacts.slack as SlackContact)).toBe(false)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
describe('WorkerRef interface', () => {
|
|
173
|
+
it('should create WorkerRef with only defined optional properties', () => {
|
|
174
|
+
const type: 'human' | 'agent' | undefined = 'human'
|
|
175
|
+
const name: string | undefined = undefined
|
|
176
|
+
const role: string | undefined = 'Engineer'
|
|
177
|
+
|
|
178
|
+
const ref: WorkerRef = { id: 'ref_1' }
|
|
179
|
+
if (type !== undefined) ref.type = type
|
|
180
|
+
if (name !== undefined) ref.name = name
|
|
181
|
+
if (role !== undefined) ref.role = role
|
|
182
|
+
|
|
183
|
+
expect(ref.type).toBe('human')
|
|
184
|
+
expect('name' in ref).toBe(false)
|
|
185
|
+
expect(ref.role).toBe('Engineer')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('ContactPreferences interface', () => {
|
|
190
|
+
it('should handle all optional properties correctly', () => {
|
|
191
|
+
const primary: 'email' | undefined = 'email'
|
|
192
|
+
const urgent: 'slack' | undefined = undefined
|
|
193
|
+
const fallback: ('email' | 'slack')[] | undefined = ['email', 'slack']
|
|
194
|
+
|
|
195
|
+
const prefs: ContactPreferences = {}
|
|
196
|
+
if (primary !== undefined) prefs.primary = primary
|
|
197
|
+
if (urgent !== undefined) prefs.urgent = urgent
|
|
198
|
+
if (fallback !== undefined) prefs.fallback = fallback
|
|
199
|
+
|
|
200
|
+
expect(prefs.primary).toBe('email')
|
|
201
|
+
expect('urgent' in prefs).toBe(false)
|
|
202
|
+
expect(prefs.fallback).toEqual(['email', 'slack'])
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
describe('Result types', () => {
|
|
207
|
+
it('should create NotifyResult with conditional optional properties', () => {
|
|
208
|
+
const messageId: string | undefined = 'msg_123'
|
|
209
|
+
const sentAt: Date | undefined = undefined
|
|
210
|
+
const recipients: WorkerRef[] | undefined = [{ id: 'alice' }]
|
|
211
|
+
|
|
212
|
+
const result: NotifyResult = {
|
|
213
|
+
sent: true,
|
|
214
|
+
via: ['email'],
|
|
215
|
+
}
|
|
216
|
+
if (messageId !== undefined) result.messageId = messageId
|
|
217
|
+
if (sentAt !== undefined) result.sentAt = sentAt
|
|
218
|
+
if (recipients !== undefined) result.recipients = recipients
|
|
219
|
+
|
|
220
|
+
expect(result.messageId).toBe('msg_123')
|
|
221
|
+
expect('sentAt' in result).toBe(false)
|
|
222
|
+
expect(result.recipients).toHaveLength(1)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should create ApprovalResult with conditional optional properties', () => {
|
|
226
|
+
const approvedBy: WorkerRef | undefined = { id: 'manager' }
|
|
227
|
+
const notes: string | undefined = undefined
|
|
228
|
+
const via: 'email' | undefined = 'email'
|
|
229
|
+
|
|
230
|
+
const result: ApprovalResult = { approved: true }
|
|
231
|
+
if (approvedBy !== undefined) result.approvedBy = approvedBy
|
|
232
|
+
if (notes !== undefined) result.notes = notes
|
|
233
|
+
if (via !== undefined) result.via = via
|
|
234
|
+
|
|
235
|
+
expect(result.approvedBy?.id).toBe('manager')
|
|
236
|
+
expect('notes' in result).toBe(false)
|
|
237
|
+
expect(result.via).toBe('email')
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
describe('Options types', () => {
|
|
242
|
+
it('should create NotifyOptions with conditional properties', () => {
|
|
243
|
+
const via: 'slack' | undefined = 'slack'
|
|
244
|
+
const priority: 'high' | undefined = undefined
|
|
245
|
+
const timeout: number | undefined = 5000
|
|
246
|
+
|
|
247
|
+
const options: NotifyOptions = {}
|
|
248
|
+
if (via !== undefined) options.via = via
|
|
249
|
+
if (priority !== undefined) options.priority = priority
|
|
250
|
+
if (timeout !== undefined) options.timeout = timeout
|
|
251
|
+
|
|
252
|
+
expect(options.via).toBe('slack')
|
|
253
|
+
expect('priority' in options).toBe(false)
|
|
254
|
+
expect(options.timeout).toBe(5000)
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// load-balancing.ts - Route and Agent Types
|
|
261
|
+
// ============================================================================
|
|
262
|
+
|
|
263
|
+
describe('load-balancing.ts - Optional Property Handling', () => {
|
|
264
|
+
describe('RouteResult interface', () => {
|
|
265
|
+
it('should create RouteResult with conditional optional properties', () => {
|
|
266
|
+
const reason: string | undefined = undefined
|
|
267
|
+
const matchScore: number | undefined = 0.95
|
|
268
|
+
const matchedRule: string | null | undefined = 'skill-match'
|
|
269
|
+
|
|
270
|
+
const result: RouteResult = {
|
|
271
|
+
agent: {
|
|
272
|
+
id: 'agent_1',
|
|
273
|
+
name: 'Test Agent',
|
|
274
|
+
type: 'agent',
|
|
275
|
+
status: 'available',
|
|
276
|
+
skills: ['typescript'],
|
|
277
|
+
currentLoad: 0,
|
|
278
|
+
maxLoad: 10,
|
|
279
|
+
contacts: {},
|
|
280
|
+
metadata: {},
|
|
281
|
+
},
|
|
282
|
+
task: {
|
|
283
|
+
id: 'task_1',
|
|
284
|
+
name: 'Test Task',
|
|
285
|
+
requiredSkills: ['typescript'],
|
|
286
|
+
priority: 5,
|
|
287
|
+
metadata: {},
|
|
288
|
+
},
|
|
289
|
+
strategy: 'capability',
|
|
290
|
+
timestamp: new Date(),
|
|
291
|
+
}
|
|
292
|
+
if (reason !== undefined) result.reason = reason
|
|
293
|
+
if (matchScore !== undefined) result.matchScore = matchScore
|
|
294
|
+
if (matchedRule !== undefined) result.matchedRule = matchedRule
|
|
295
|
+
|
|
296
|
+
expect('reason' in result).toBe(false)
|
|
297
|
+
expect(result.matchScore).toBe(0.95)
|
|
298
|
+
expect(result.matchedRule).toBe('skill-match')
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should handle usedDefault and usedFallback optional flags', () => {
|
|
302
|
+
const usedDefault: boolean | undefined = true
|
|
303
|
+
const usedFallback: boolean | undefined = undefined
|
|
304
|
+
|
|
305
|
+
const result: RouteResult = {
|
|
306
|
+
agent: null,
|
|
307
|
+
task: {
|
|
308
|
+
id: 'task_2',
|
|
309
|
+
name: 'Failed Task',
|
|
310
|
+
requiredSkills: [],
|
|
311
|
+
priority: 1,
|
|
312
|
+
metadata: {},
|
|
313
|
+
},
|
|
314
|
+
strategy: 'round-robin',
|
|
315
|
+
timestamp: new Date(),
|
|
316
|
+
reason: 'no-available-agents',
|
|
317
|
+
}
|
|
318
|
+
if (usedDefault !== undefined) result.usedDefault = usedDefault
|
|
319
|
+
if (usedFallback !== undefined) result.usedFallback = usedFallback
|
|
320
|
+
|
|
321
|
+
expect(result.usedDefault).toBe(true)
|
|
322
|
+
expect('usedFallback' in result).toBe(false)
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
describe('TaskRequest interface', () => {
|
|
327
|
+
it('should create TaskRequest with conditional enqueuedAt', () => {
|
|
328
|
+
const enqueuedAt: Date | undefined = new Date()
|
|
329
|
+
|
|
330
|
+
const task: TaskRequest = {
|
|
331
|
+
id: 'task_1',
|
|
332
|
+
name: 'Test',
|
|
333
|
+
requiredSkills: [],
|
|
334
|
+
priority: 5,
|
|
335
|
+
metadata: {},
|
|
336
|
+
}
|
|
337
|
+
if (enqueuedAt !== undefined) task.enqueuedAt = enqueuedAt
|
|
338
|
+
|
|
339
|
+
expect(task.enqueuedAt).toBeInstanceOf(Date)
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('AgentAvailability interface', () => {
|
|
344
|
+
it('should handle optional load tracking', () => {
|
|
345
|
+
const currentLoad: number | undefined = 5
|
|
346
|
+
const maxLoad: number | undefined = undefined
|
|
347
|
+
|
|
348
|
+
const availability: AgentAvailability = {
|
|
349
|
+
status: 'available',
|
|
350
|
+
lastSeen: new Date(),
|
|
351
|
+
}
|
|
352
|
+
if (currentLoad !== undefined) availability.currentLoad = currentLoad
|
|
353
|
+
if (maxLoad !== undefined) availability.maxLoad = maxLoad
|
|
354
|
+
|
|
355
|
+
expect(availability.currentLoad).toBe(5)
|
|
356
|
+
expect('maxLoad' in availability).toBe(false)
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
describe('RoutingRule interface', () => {
|
|
361
|
+
it('should handle optional enabled and priority', () => {
|
|
362
|
+
const enabled: boolean | undefined = true
|
|
363
|
+
const priority: number | undefined = undefined
|
|
364
|
+
|
|
365
|
+
const rule: RoutingRule = {
|
|
366
|
+
name: 'test-rule',
|
|
367
|
+
priority: 1,
|
|
368
|
+
fromTier: 'code',
|
|
369
|
+
toTier: 'generative',
|
|
370
|
+
condition: () => true,
|
|
371
|
+
action: () => null,
|
|
372
|
+
}
|
|
373
|
+
if (enabled !== undefined) rule.enabled = enabled
|
|
374
|
+
if (priority !== undefined) rule.priority = priority
|
|
375
|
+
|
|
376
|
+
expect(rule.enabled).toBe(true)
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
describe('CompositeBalancerConfig interface', () => {
|
|
381
|
+
it('should handle optional fallbackBehavior and customStrategies', () => {
|
|
382
|
+
const fallbackBehavior: 'next-strategy' | 'none' | undefined = 'next-strategy'
|
|
383
|
+
const customStrategies:
|
|
384
|
+
| Record<string, (t: TaskRequest, a: AgentInfo[]) => AgentInfo | null>
|
|
385
|
+
| undefined = undefined
|
|
386
|
+
|
|
387
|
+
const config: CompositeBalancerConfig = {
|
|
388
|
+
strategies: ['round-robin', 'least-busy'],
|
|
389
|
+
}
|
|
390
|
+
if (fallbackBehavior !== undefined) config.fallbackBehavior = fallbackBehavior
|
|
391
|
+
if (customStrategies !== undefined) config.customStrategies = customStrategies
|
|
392
|
+
|
|
393
|
+
expect(config.fallbackBehavior).toBe('next-strategy')
|
|
394
|
+
expect('customStrategies' in config).toBe(false)
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// error-escalation.ts - Error and Recovery Types
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
describe('error-escalation.ts - Optional Property Handling', () => {
|
|
404
|
+
describe('ClassifiedError interface', () => {
|
|
405
|
+
it('should create ClassifiedError with conditional optional properties', () => {
|
|
406
|
+
const tier: 'code' | undefined = 'code'
|
|
407
|
+
const agentId: string | undefined = undefined
|
|
408
|
+
const taskId: string | undefined = 'task_123'
|
|
409
|
+
const stack: string | undefined = 'Error: test\n at test.js:1'
|
|
410
|
+
const context: ErrorContext | undefined = { workflowId: 'wf_1' }
|
|
411
|
+
|
|
412
|
+
const error: ClassifiedError = {
|
|
413
|
+
id: 'err_1',
|
|
414
|
+
original: new Error('Test error'),
|
|
415
|
+
severity: 'medium',
|
|
416
|
+
category: 'transient',
|
|
417
|
+
timestamp: new Date(),
|
|
418
|
+
}
|
|
419
|
+
if (tier !== undefined) error.tier = tier
|
|
420
|
+
if (agentId !== undefined) error.agentId = agentId
|
|
421
|
+
if (taskId !== undefined) error.taskId = taskId
|
|
422
|
+
if (stack !== undefined) error.stack = stack
|
|
423
|
+
if (context !== undefined) error.context = context
|
|
424
|
+
|
|
425
|
+
expect(error.tier).toBe('code')
|
|
426
|
+
expect('agentId' in error).toBe(false)
|
|
427
|
+
expect(error.taskId).toBe('task_123')
|
|
428
|
+
expect(error.context?.workflowId).toBe('wf_1')
|
|
429
|
+
})
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
describe('ErrorContext interface', () => {
|
|
433
|
+
it('should handle all optional fields', () => {
|
|
434
|
+
const workflowId: string | undefined = 'wf_1'
|
|
435
|
+
const stepId: string | undefined = undefined
|
|
436
|
+
const attemptNumber: number | undefined = 3
|
|
437
|
+
const metadata: Record<string, unknown> | undefined = { key: 'value' }
|
|
438
|
+
|
|
439
|
+
const context: ErrorContext = {}
|
|
440
|
+
if (workflowId !== undefined) context.workflowId = workflowId
|
|
441
|
+
if (stepId !== undefined) context.stepId = stepId
|
|
442
|
+
if (attemptNumber !== undefined) context.attemptNumber = attemptNumber
|
|
443
|
+
if (metadata !== undefined) context.metadata = metadata
|
|
444
|
+
|
|
445
|
+
expect(context.workflowId).toBe('wf_1')
|
|
446
|
+
expect('stepId' in context).toBe(false)
|
|
447
|
+
expect(context.attemptNumber).toBe(3)
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
describe('EscalationPolicy interface', () => {
|
|
452
|
+
it('should handle optional skipTierThreshold and tierPolicies', () => {
|
|
453
|
+
const skipTierThreshold: 'high' | undefined = 'high'
|
|
454
|
+
const tierPolicies: Record<string, { maxRetries?: number }> | undefined = undefined
|
|
455
|
+
|
|
456
|
+
const policy: EscalationPolicy = {
|
|
457
|
+
maxEscalationDepth: 5,
|
|
458
|
+
allowSkipTiers: true,
|
|
459
|
+
rules: [],
|
|
460
|
+
}
|
|
461
|
+
if (skipTierThreshold !== undefined) policy.skipTierThreshold = skipTierThreshold
|
|
462
|
+
if (tierPolicies !== undefined) policy.tierPolicies = tierPolicies
|
|
463
|
+
|
|
464
|
+
expect(policy.skipTierThreshold).toBe('high')
|
|
465
|
+
expect('tierPolicies' in policy).toBe(false)
|
|
466
|
+
})
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
describe('RetryState interface', () => {
|
|
470
|
+
it('should handle nullable Date properties', () => {
|
|
471
|
+
// Note: These are explicitly typed as Date | null, not optional
|
|
472
|
+
// This is correct usage - null means "not set yet"
|
|
473
|
+
const state: RetryState = {
|
|
474
|
+
attemptNumber: 0,
|
|
475
|
+
lastAttemptTime: null,
|
|
476
|
+
nextRetryTime: null,
|
|
477
|
+
exhausted: false,
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
expect(state.lastAttemptTime).toBeNull()
|
|
481
|
+
expect(state.nextRetryTime).toBeNull()
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
describe('FallbackConfig interface', () => {
|
|
486
|
+
it('should handle optional configuration fields', () => {
|
|
487
|
+
const requiredSkills: string[] | undefined = ['typescript']
|
|
488
|
+
const currentTier: 'code' | undefined = undefined
|
|
489
|
+
const excludeAgentIds: string[] | undefined = ['agent_1']
|
|
490
|
+
|
|
491
|
+
const config: FallbackConfig = {
|
|
492
|
+
strategy: 'capability-match',
|
|
493
|
+
}
|
|
494
|
+
if (requiredSkills !== undefined) config.requiredSkills = requiredSkills
|
|
495
|
+
if (currentTier !== undefined) config.currentTier = currentTier
|
|
496
|
+
if (excludeAgentIds !== undefined) config.excludeAgentIds = excludeAgentIds
|
|
497
|
+
|
|
498
|
+
expect(config.requiredSkills).toEqual(['typescript'])
|
|
499
|
+
expect('currentTier' in config).toBe(false)
|
|
500
|
+
})
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
describe('RecoveryState interface', () => {
|
|
504
|
+
it('should handle optional agentId and resolution', () => {
|
|
505
|
+
const agentId: string | undefined = 'agent_1'
|
|
506
|
+
const resolution: string | undefined = undefined
|
|
507
|
+
const isTerminal: boolean | undefined = false
|
|
508
|
+
|
|
509
|
+
const state: RecoveryState = {
|
|
510
|
+
errorId: 'err_1',
|
|
511
|
+
tier: 'code',
|
|
512
|
+
retryState: {
|
|
513
|
+
attemptNumber: 0,
|
|
514
|
+
lastAttemptTime: null,
|
|
515
|
+
nextRetryTime: null,
|
|
516
|
+
exhausted: false,
|
|
517
|
+
},
|
|
518
|
+
escalated: false,
|
|
519
|
+
resolved: false,
|
|
520
|
+
escalationPath: ['code'],
|
|
521
|
+
fallbackHistory: [],
|
|
522
|
+
}
|
|
523
|
+
if (agentId !== undefined) state.agentId = agentId
|
|
524
|
+
if (resolution !== undefined) state.resolution = resolution
|
|
525
|
+
if (isTerminal !== undefined) state.isTerminal = isTerminal
|
|
526
|
+
|
|
527
|
+
expect(state.agentId).toBe('agent_1')
|
|
528
|
+
expect('resolution' in state).toBe(false)
|
|
529
|
+
expect(state.isTerminal).toBe(false)
|
|
530
|
+
})
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
describe('EscalationResult interface', () => {
|
|
534
|
+
it('should handle conditional result properties', () => {
|
|
535
|
+
const retryDelay: number | undefined = 1000
|
|
536
|
+
const escalationPath: EscalationPath | undefined = undefined
|
|
537
|
+
const degradationLevel: 'partial' | undefined = 'partial'
|
|
538
|
+
|
|
539
|
+
const result: EscalationResult = {
|
|
540
|
+
handled: true,
|
|
541
|
+
action: 'retry',
|
|
542
|
+
classifiedError: {
|
|
543
|
+
id: 'err_1',
|
|
544
|
+
original: new Error('Test'),
|
|
545
|
+
severity: 'low',
|
|
546
|
+
category: 'transient',
|
|
547
|
+
timestamp: new Date(),
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
if (retryDelay !== undefined) result.retryDelay = retryDelay
|
|
551
|
+
if (escalationPath !== undefined) result.escalationPath = escalationPath
|
|
552
|
+
if (degradationLevel !== undefined) result.degradationLevel = degradationLevel
|
|
553
|
+
|
|
554
|
+
expect(result.retryDelay).toBe(1000)
|
|
555
|
+
expect('escalationPath' in result).toBe(false)
|
|
556
|
+
expect(result.degradationLevel).toBe('partial')
|
|
557
|
+
})
|
|
558
|
+
})
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
// ============================================================================
|
|
562
|
+
// transports/*.ts - Transport Types
|
|
563
|
+
// ============================================================================
|
|
564
|
+
|
|
565
|
+
describe('transports/email.ts - Optional Property Handling', () => {
|
|
566
|
+
describe('EmailMessage interface', () => {
|
|
567
|
+
it('should create EmailMessage with conditional optional properties', () => {
|
|
568
|
+
const replyTo: string | undefined = 'reply@example.com'
|
|
569
|
+
const text: string | undefined = undefined
|
|
570
|
+
const html: string | undefined = '<p>Hello</p>'
|
|
571
|
+
|
|
572
|
+
const message: EmailMessage = {
|
|
573
|
+
to: 'test@example.com',
|
|
574
|
+
from: 'sender@example.com',
|
|
575
|
+
subject: 'Test',
|
|
576
|
+
}
|
|
577
|
+
if (replyTo !== undefined) message.replyTo = replyTo
|
|
578
|
+
if (text !== undefined) message.text = text
|
|
579
|
+
if (html !== undefined) message.html = html
|
|
580
|
+
|
|
581
|
+
expect(message.replyTo).toBe('reply@example.com')
|
|
582
|
+
expect('text' in message).toBe(false)
|
|
583
|
+
expect(message.html).toBe('<p>Hello</p>')
|
|
584
|
+
})
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
describe('EmailSendResult interface', () => {
|
|
588
|
+
it('should handle optional messageId and error', () => {
|
|
589
|
+
const messageId: string | undefined = 'msg_123'
|
|
590
|
+
const error: string | undefined = undefined
|
|
591
|
+
|
|
592
|
+
const result: EmailSendResult = { success: true }
|
|
593
|
+
if (messageId !== undefined) result.messageId = messageId
|
|
594
|
+
if (error !== undefined) result.error = error
|
|
595
|
+
|
|
596
|
+
expect(result.messageId).toBe('msg_123')
|
|
597
|
+
expect('error' in result).toBe(false)
|
|
598
|
+
})
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
describe('ParsedEmailReply interface', () => {
|
|
602
|
+
it('should handle multiple optional fields', () => {
|
|
603
|
+
const approved: boolean | undefined = true
|
|
604
|
+
const requestId: string | undefined = 'req_123'
|
|
605
|
+
const notes: string | undefined = undefined
|
|
606
|
+
const from: string | undefined = 'user@example.com'
|
|
607
|
+
|
|
608
|
+
const reply: ParsedEmailReply = {
|
|
609
|
+
isApprovalResponse: true,
|
|
610
|
+
}
|
|
611
|
+
if (approved !== undefined) reply.approved = approved
|
|
612
|
+
if (requestId !== undefined) reply.requestId = requestId
|
|
613
|
+
if (notes !== undefined) reply.notes = notes
|
|
614
|
+
if (from !== undefined) reply.from = from
|
|
615
|
+
|
|
616
|
+
expect(reply.approved).toBe(true)
|
|
617
|
+
expect(reply.requestId).toBe('req_123')
|
|
618
|
+
expect('notes' in reply).toBe(false)
|
|
619
|
+
expect(reply.from).toBe('user@example.com')
|
|
620
|
+
})
|
|
621
|
+
})
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
describe('transports/slack.ts - Optional Property Handling', () => {
|
|
625
|
+
describe('SlackMessage interface', () => {
|
|
626
|
+
it('should handle optional thread and metadata', () => {
|
|
627
|
+
const thread_ts: string | undefined = '1234567890.123456'
|
|
628
|
+
const reply_broadcast: boolean | undefined = undefined
|
|
629
|
+
|
|
630
|
+
const message: SlackMessage = {
|
|
631
|
+
channel: '#general',
|
|
632
|
+
text: 'Hello',
|
|
633
|
+
}
|
|
634
|
+
if (thread_ts !== undefined) message.thread_ts = thread_ts
|
|
635
|
+
if (reply_broadcast !== undefined) message.reply_broadcast = reply_broadcast
|
|
636
|
+
|
|
637
|
+
expect(message.thread_ts).toBe('1234567890.123456')
|
|
638
|
+
expect('reply_broadcast' in message).toBe(false)
|
|
639
|
+
})
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
describe('WebhookHandlerResult interface', () => {
|
|
643
|
+
it('should handle multiple optional fields', () => {
|
|
644
|
+
const actionId: string | undefined = 'approve_123'
|
|
645
|
+
const userId: string | undefined = 'U12345'
|
|
646
|
+
const channelId: string | undefined = undefined
|
|
647
|
+
const error: string | undefined = undefined
|
|
648
|
+
|
|
649
|
+
const result: WebhookHandlerResult = { success: true }
|
|
650
|
+
if (actionId !== undefined) result.actionId = actionId
|
|
651
|
+
if (userId !== undefined) result.userId = userId
|
|
652
|
+
if (channelId !== undefined) result.channelId = channelId
|
|
653
|
+
if (error !== undefined) result.error = error
|
|
654
|
+
|
|
655
|
+
expect(result.actionId).toBe('approve_123')
|
|
656
|
+
expect(result.userId).toBe('U12345')
|
|
657
|
+
expect('channelId' in result).toBe(false)
|
|
658
|
+
expect('error' in result).toBe(false)
|
|
659
|
+
})
|
|
660
|
+
})
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
describe('transports.ts - Optional Property Handling', () => {
|
|
664
|
+
describe('DeliveryResult interface', () => {
|
|
665
|
+
it('should handle optional messageId, error, and metadata', () => {
|
|
666
|
+
const messageId: string | undefined = 'msg_123'
|
|
667
|
+
const error: string | undefined = undefined
|
|
668
|
+
const metadata: Record<string, unknown> | undefined = { provider: 'resend' }
|
|
669
|
+
|
|
670
|
+
const result: DeliveryResult = {
|
|
671
|
+
success: true,
|
|
672
|
+
transport: 'email',
|
|
673
|
+
}
|
|
674
|
+
if (messageId !== undefined) result.messageId = messageId
|
|
675
|
+
if (error !== undefined) result.error = error
|
|
676
|
+
if (metadata !== undefined) result.metadata = metadata
|
|
677
|
+
|
|
678
|
+
expect(result.messageId).toBe('msg_123')
|
|
679
|
+
expect('error' in result).toBe(false)
|
|
680
|
+
expect(result.metadata?.['provider']).toBe('resend')
|
|
681
|
+
})
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
describe('Address interface', () => {
|
|
685
|
+
it('should handle optional name and metadata', () => {
|
|
686
|
+
const name: string | undefined = 'Test User'
|
|
687
|
+
const metadata: Record<string, unknown> | undefined = undefined
|
|
688
|
+
|
|
689
|
+
const address: Address = {
|
|
690
|
+
transport: 'email',
|
|
691
|
+
value: 'test@example.com',
|
|
692
|
+
}
|
|
693
|
+
if (name !== undefined) address.name = name
|
|
694
|
+
if (metadata !== undefined) address.metadata = metadata
|
|
695
|
+
|
|
696
|
+
expect(address.name).toBe('Test User')
|
|
697
|
+
expect('metadata' in address).toBe(false)
|
|
698
|
+
})
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
describe('MessagePayload interface', () => {
|
|
702
|
+
it('should handle many optional message properties', () => {
|
|
703
|
+
const from: string | undefined = 'system'
|
|
704
|
+
const subject: string | undefined = undefined
|
|
705
|
+
const priority: 'high' | undefined = 'high'
|
|
706
|
+
const threadId: string | undefined = undefined
|
|
707
|
+
|
|
708
|
+
const payload: MessagePayload = {
|
|
709
|
+
to: 'user@example.com',
|
|
710
|
+
body: 'Message content',
|
|
711
|
+
type: 'notification',
|
|
712
|
+
}
|
|
713
|
+
if (from !== undefined) payload.from = from
|
|
714
|
+
if (subject !== undefined) payload.subject = subject
|
|
715
|
+
if (priority !== undefined) payload.priority = priority
|
|
716
|
+
if (threadId !== undefined) payload.threadId = threadId
|
|
717
|
+
|
|
718
|
+
expect(payload.from).toBe('system')
|
|
719
|
+
expect('subject' in payload).toBe(false)
|
|
720
|
+
expect(payload.priority).toBe('high')
|
|
721
|
+
expect('threadId' in payload).toBe(false)
|
|
722
|
+
})
|
|
723
|
+
})
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
// ============================================================================
|
|
727
|
+
// Spread Pattern Tests
|
|
728
|
+
// ============================================================================
|
|
729
|
+
|
|
730
|
+
describe('Spread Pattern for Optional Properties', () => {
|
|
731
|
+
it('should use spread pattern for object literals', () => {
|
|
732
|
+
const optional1: string | undefined = 'value1'
|
|
733
|
+
const optional2: string | undefined = undefined
|
|
734
|
+
const optional3: number | undefined = 42
|
|
735
|
+
|
|
736
|
+
interface TestInterface {
|
|
737
|
+
required: string
|
|
738
|
+
optional1?: string
|
|
739
|
+
optional2?: string
|
|
740
|
+
optional3?: number
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const result: TestInterface = {
|
|
744
|
+
required: 'base',
|
|
745
|
+
...(optional1 !== undefined && { optional1 }),
|
|
746
|
+
...(optional2 !== undefined && { optional2 }),
|
|
747
|
+
...(optional3 !== undefined && { optional3 }),
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
expect(result.required).toBe('base')
|
|
751
|
+
expect(result.optional1).toBe('value1')
|
|
752
|
+
expect('optional2' in result).toBe(false)
|
|
753
|
+
expect(result.optional3).toBe(42)
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
it('should work with nested optional properties', () => {
|
|
757
|
+
const nestedProp: { inner?: string } | undefined = { inner: 'nested' }
|
|
758
|
+
const anotherNested: { value?: number } | undefined = undefined
|
|
759
|
+
|
|
760
|
+
interface OuterInterface {
|
|
761
|
+
id: string
|
|
762
|
+
nested?: { inner?: string }
|
|
763
|
+
another?: { value?: number }
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const result: OuterInterface = {
|
|
767
|
+
id: 'test',
|
|
768
|
+
...(nestedProp !== undefined && { nested: nestedProp }),
|
|
769
|
+
...(anotherNested !== undefined && { another: anotherNested }),
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
expect(result.nested?.inner).toBe('nested')
|
|
773
|
+
expect('another' in result).toBe(false)
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
// ============================================================================
|
|
778
|
+
// Type-Level Tests
|
|
779
|
+
// ============================================================================
|
|
780
|
+
|
|
781
|
+
describe('Type-Level Assertions', () => {
|
|
782
|
+
it('should verify Worker type structure', () => {
|
|
783
|
+
// Type-level test - ensures the interface structure is correct
|
|
784
|
+
type WorkerKeys = keyof Worker
|
|
785
|
+
expectTypeOf<WorkerKeys>().toMatchTypeOf<
|
|
786
|
+
| 'id'
|
|
787
|
+
| 'name'
|
|
788
|
+
| 'type'
|
|
789
|
+
| 'status'
|
|
790
|
+
| 'contacts'
|
|
791
|
+
| 'preferences'
|
|
792
|
+
| 'role'
|
|
793
|
+
| 'teams'
|
|
794
|
+
| 'skills'
|
|
795
|
+
| 'tools'
|
|
796
|
+
| 'capabilityTier'
|
|
797
|
+
| 'capabilityProfile'
|
|
798
|
+
| 'metadata'
|
|
799
|
+
>()
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
it('should verify RouteResult optional properties are not required', () => {
|
|
803
|
+
// This should compile - reason is optional
|
|
804
|
+
const minimalResult: RouteResult = {
|
|
805
|
+
agent: null,
|
|
806
|
+
task: {
|
|
807
|
+
id: '1',
|
|
808
|
+
name: 'test',
|
|
809
|
+
requiredSkills: [],
|
|
810
|
+
priority: 1,
|
|
811
|
+
metadata: {},
|
|
812
|
+
},
|
|
813
|
+
strategy: 'round-robin',
|
|
814
|
+
timestamp: new Date(),
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
expect(minimalResult).toBeDefined()
|
|
818
|
+
expect('reason' in minimalResult).toBe(false)
|
|
819
|
+
})
|
|
820
|
+
|
|
821
|
+
it('should verify ClassifiedError optional properties are not required', () => {
|
|
822
|
+
// This should compile - tier, agentId, etc. are optional
|
|
823
|
+
const minimalError: ClassifiedError = {
|
|
824
|
+
id: 'err_1',
|
|
825
|
+
original: new Error('test'),
|
|
826
|
+
severity: 'low',
|
|
827
|
+
category: 'transient',
|
|
828
|
+
timestamp: new Date(),
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
expect(minimalError).toBeDefined()
|
|
832
|
+
expect('tier' in minimalError).toBe(false)
|
|
833
|
+
})
|
|
834
|
+
})
|