Skip to content

Commit c30299a

Browse files
committed
reset
1 parent a10053a commit c30299a

File tree

9 files changed

+1939
-16
lines changed

9 files changed

+1939
-16
lines changed

packages/core/src/create-test-integration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ class TestDestination<T, AudienceSettings = any> extends Destination<T, Audience
6868
async testDynamicField(
6969
action: string,
7070
fieldKey: string,
71-
data: ExecuteDynamicFieldInput<T, object>,
72-
dynamicFn?: RequestFn<any, any, DynamicFieldResponse, AudienceSettings>
71+
data: ExecuteDynamicFieldInput<T, object, AudienceSettings>,
72+
dynamicFn?: RequestFn<unknown, unknown, DynamicFieldResponse, AudienceSettings>
7373
) {
7474
return await super.executeDynamicField(action, fieldKey, data, dynamicFn)
7575
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { Action } from '../action'
2+
import { AsyncOperationResponse, PollResponse } from '../types'
3+
import { InputData } from '../../mapping-kit'
4+
5+
// Mock settings type for testing
6+
interface TestSettings {
7+
apiKey: string
8+
endpoint: string
9+
}
10+
11+
describe('Async Actions', () => {
12+
const mockSettings: TestSettings = {
13+
apiKey: 'test-key',
14+
endpoint: 'https://api.example.com'
15+
}
16+
17+
const mockPayloads: InputData[] = [
18+
{
19+
userId: 'user1',
20+
21+
name: 'User 1'
22+
},
23+
{
24+
userId: 'user2',
25+
26+
name: 'User 2'
27+
}
28+
]
29+
30+
it('should support async operations with performBatch returning AsyncOperationResponse', async () => {
31+
const asyncActionDefinition = {
32+
title: 'Test Async Action',
33+
description: 'Test action that supports async operations',
34+
fields: {
35+
userId: {
36+
label: 'User ID',
37+
description: 'The user ID',
38+
type: 'string' as const,
39+
required: true
40+
},
41+
email: {
42+
label: 'Email',
43+
description: 'The user email',
44+
type: 'string' as const,
45+
required: true
46+
}
47+
},
48+
perform: jest.fn(),
49+
performBatch: jest.fn().mockImplementation(async (_request, data) => {
50+
// Simulate async operation initiation and store context in stateContext
51+
const operationId = `op_${Date.now()}`
52+
53+
// Store context data in stateContext for poll method to access
54+
const contextData = {
55+
batchSize: data.payload.length,
56+
createdAt: new Date().toISOString(),
57+
processingEndpoint: 'https://api.example.com/process',
58+
metadata: { priority: 'high', retries: 3 }
59+
}
60+
data.stateContext?.setResponseContext?.(operationId, contextData, {})
61+
62+
return {
63+
operationId,
64+
status: 'pending' as const,
65+
message: 'Batch operation initiated'
66+
} as AsyncOperationResponse
67+
}),
68+
poll: jest.fn().mockImplementation(async (_request, data) => {
69+
// Get context data from stateContext that was stored in performBatch
70+
const context = data.stateContext?.getRequestContext?.(data.operationId)
71+
const isHighPriority = context?.metadata?.priority === 'high'
72+
73+
// Simulate polling for operation status
74+
if (data.operationId.includes('completed')) {
75+
return {
76+
status: 'completed' as const,
77+
message: 'Operation completed successfully',
78+
result: {
79+
processedRecords: context?.batchSize || 0,
80+
priority: context?.metadata?.priority,
81+
processingTime: isHighPriority ? '2s' : '5s'
82+
}
83+
} as PollResponse
84+
}
85+
86+
return {
87+
status: 'pending' as const,
88+
message: `Operation still in progress (priority: ${context?.metadata?.priority})`
89+
} as PollResponse
90+
})
91+
}
92+
93+
const action = new Action('test-destination', asyncActionDefinition)
94+
95+
// Verify async support detection
96+
expect(action.hasAsyncSupport).toBe(true)
97+
expect(action.hasBatchSupport).toBe(true)
98+
99+
// Test batch execution with async response
100+
const mockStateContext = {
101+
setResponseContext: jest.fn(),
102+
getRequestContext: jest.fn().mockReturnValue({
103+
batchSize: 2,
104+
metadata: { priority: 'high', retries: 3 }
105+
})
106+
}
107+
108+
const batchResult = await action.executeBatch({
109+
data: mockPayloads,
110+
settings: mockSettings,
111+
mapping: {
112+
userId: { '@path': '$.userId' },
113+
email: { '@path': '$.email' }
114+
},
115+
auth: undefined,
116+
stateContext: mockStateContext as any
117+
})
118+
119+
expect(batchResult).toHaveLength(2) // Should have responses for both payloads
120+
expect(batchResult[0]).toMatchObject({
121+
status: 202, // Accepted status for async operation
122+
body: expect.objectContaining({
123+
operationId: expect.stringContaining('op_'),
124+
status: 'pending',
125+
message: 'Batch operation initiated'
126+
})
127+
})
128+
129+
// Test polling functionality
130+
const pollResult = await action.executePoll({
131+
operationId: 'op_completed_123',
132+
settings: mockSettings,
133+
auth: undefined,
134+
stateContext: mockStateContext as any
135+
})
136+
137+
expect(pollResult).toMatchObject({
138+
status: 'completed',
139+
message: 'Operation completed successfully',
140+
result: { processedRecords: 2 }
141+
})
142+
143+
// Test polling for pending operation
144+
const pendingPollResult = await action.executePoll({
145+
operationId: 'op_pending_456',
146+
settings: mockSettings,
147+
auth: undefined,
148+
stateContext: mockStateContext as any
149+
})
150+
151+
expect(pendingPollResult).toMatchObject({
152+
status: 'pending',
153+
message: 'Operation still in progress (priority: high)'
154+
})
155+
})
156+
157+
it('should throw error when polling is not supported', async () => {
158+
const syncActionDefinition = {
159+
title: 'Test Sync Action',
160+
description: 'Test action that does not support async operations',
161+
fields: {
162+
userId: {
163+
label: 'User ID',
164+
description: 'The user ID',
165+
type: 'string' as const,
166+
required: true
167+
}
168+
},
169+
perform: jest.fn()
170+
}
171+
172+
const action = new Action('test-destination', syncActionDefinition)
173+
174+
expect(action.hasAsyncSupport).toBe(false)
175+
176+
// Should throw error when trying to poll
177+
await expect(
178+
action.executePoll({
179+
operationId: 'test-op-id',
180+
settings: mockSettings,
181+
auth: undefined
182+
})
183+
).rejects.toThrow('This action does not support async operations or polling')
184+
})
185+
186+
it('should throw error when poll method is missing but async is expected', async () => {
187+
const incompleteAsyncActionDefinition = {
188+
title: 'Test Incomplete Async Action',
189+
description: 'Test action with performBatch but no poll method',
190+
fields: {
191+
userId: {
192+
label: 'User ID',
193+
description: 'The user ID',
194+
type: 'string' as const,
195+
required: true
196+
}
197+
},
198+
perform: jest.fn(),
199+
performBatch: jest.fn()
200+
// Note: missing poll method
201+
}
202+
203+
const action = new Action('test-destination', incompleteAsyncActionDefinition)
204+
205+
expect(action.hasAsyncSupport).toBe(false)
206+
207+
await expect(
208+
action.executePoll({
209+
operationId: 'test-op-id',
210+
settings: mockSettings,
211+
auth: undefined
212+
})
213+
).rejects.toThrow('This action does not support async operations or polling')
214+
})
215+
})

0 commit comments

Comments
 (0)