@outputai/core 0.14.0 → 0.14.1-next.1ec4416.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/dist/credentials/credentials.d.ts +6 -0
- package/dist/credentials/credentials.js +87 -0
- package/dist/credentials/encrypted_yaml_provider.d.ts +2 -0
- package/dist/credentials/encrypted_yaml_provider.js +77 -0
- package/dist/credentials/encryption.d.ts +3 -0
- package/dist/credentials/encryption.js +6 -0
- package/dist/credentials/errors.d.ts +13 -0
- package/dist/credentials/errors.js +39 -0
- package/dist/credentials/index.d.ts +6 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/init.d.ts +1 -0
- package/dist/credentials/init.js +7 -0
- package/dist/credentials/paths.d.ts +7 -0
- package/dist/credentials/paths.js +14 -0
- package/dist/credentials/provider_registry.d.ts +3 -0
- package/dist/credentials/provider_registry.js +6 -0
- package/dist/credentials/types.d.ts +12 -0
- package/dist/credentials/types.js +1 -0
- package/package.json +25 -14
- package/src/async_storage.d.ts +4 -0
- package/src/consts.js +0 -2
- package/src/helpers/object.d.ts +2 -0
- package/src/hooks/index.d.ts +0 -8
- package/src/hooks/index.js +0 -3
- package/src/logger/index.d.ts +4 -0
- package/src/worker/index.js +3 -1
- package/bin/healthcheck.spec.js +0 -90
- package/src/bus.spec.js +0 -196
- package/src/errors.spec.js +0 -19
- package/src/helpers/activity_options.spec.js +0 -55
- package/src/helpers/component.spec.js +0 -54
- package/src/helpers/error_serializer.spec.js +0 -479
- package/src/helpers/errors.spec.js +0 -189
- package/src/helpers/fetch.spec.js +0 -292
- package/src/helpers/function.spec.js +0 -24
- package/src/helpers/object.spec.js +0 -425
- package/src/helpers/promise.spec.js +0 -62
- package/src/helpers/redact.spec.js +0 -66
- package/src/helpers/string.spec.js +0 -99
- package/src/helpers/temporal_context.spec.ts +0 -83
- package/src/helpers/trace_info.spec.js +0 -45
- package/src/helpers/workflow_context.spec.js +0 -46
- package/src/hooks/index.spec.js +0 -265
- package/src/hooks/pending_hooks.spec.js +0 -62
- package/src/interface/evaluator.spec.js +0 -685
- package/src/interface/logger.spec.js +0 -172
- package/src/interface/step.spec.js +0 -128
- package/src/interface/validations/index.spec.js +0 -197
- package/src/interface/validations/schemas.spec.js +0 -229
- package/src/interface/webhook.spec.js +0 -170
- package/src/interface/workflow.spec.js +0 -710
- package/src/interface/workflow_utils.spec.js +0 -271
- package/src/interface/zod_integration.spec.js +0 -646
- package/src/internal_activities/index.spec.js +0 -186
- package/src/logger/development.spec.js +0 -87
- package/src/logger/index.spec.js +0 -27
- package/src/logger/production.spec.js +0 -71
- package/src/sdk/helpers/component_metadata.spec.js +0 -30
- package/src/sdk/helpers/objects.spec.js +0 -16
- package/src/sdk/helpers/path.spec.js +0 -102
- package/src/sdk/runtime/context.spec.js +0 -37
- package/src/sdk/runtime/events.spec.js +0 -40
- package/src/temporal/client.spec.js +0 -124
- package/src/tracing/processors/local/index.spec.js +0 -204
- package/src/tracing/processors/s3/configs.spec.js +0 -64
- package/src/tracing/processors/s3/index.spec.js +0 -185
- package/src/tracing/processors/s3/redis_client.spec.js +0 -179
- package/src/tracing/processors/s3/s3_client.spec.js +0 -68
- package/src/tracing/tools/build_trace_tree.spec.js +0 -216
- package/src/tracing/trace_engine.spec.js +0 -299
- package/src/worker/bundle.spec.js +0 -53
- package/src/worker/bundler_options.spec.js +0 -62
- package/src/worker/catalog_workflow/catalog_publisher.spec.js +0 -317
- package/src/worker/catalog_workflow/index.spec.js +0 -222
- package/src/worker/configs.spec.js +0 -213
- package/src/worker/configs_tuner_schema.spec.js +0 -127
- package/src/worker/connection_monitor.spec.js +0 -328
- package/src/worker/global_functions.spec.js +0 -55
- package/src/worker/index.spec.js +0 -526
- package/src/worker/interceptors/activity.spec.js +0 -369
- package/src/worker/interceptors/headers.spec.js +0 -73
- package/src/worker/interceptors/workflow.spec.js +0 -357
- package/src/worker/interruption.spec.js +0 -177
- package/src/worker/loader/activities.spec.js +0 -212
- package/src/worker/loader/hooks.spec.js +0 -64
- package/src/worker/loader/matchers.spec.js +0 -140
- package/src/worker/loader/tools.spec.js +0 -595
- package/src/worker/loader/workflows.spec.js +0 -287
- package/src/worker/log_hooks.spec.js +0 -303
- package/src/worker/shutdown.spec.js +0 -197
- package/src/worker/sinks.spec.js +0 -220
- package/src/worker/telemetry.spec.js +0 -80
- package/src/worker/temporal_logger.spec.js +0 -90
- package/src/worker/webpack_loaders/tools.spec.js +0 -314
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +0 -134
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +0 -154
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +0 -165
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +0 -202
- package/src/worker/webpack_loaders/workflow_validator/npm_workflow_export_resolve.spec.js +0 -374
- package/src/worker/worker_runner.spec.js +0 -220
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { ApplicationFailure, CompleteAsyncError } from '@temporalio/common';
|
|
3
|
-
import { ActivitySpecialOutput, BusEventType, METADATA_ACCESS_SYMBOL } from '#consts';
|
|
4
|
-
import { TransparentFatalError } from '#errors';
|
|
5
|
-
|
|
6
|
-
const heartbeatMock = vi.hoisted( () => vi.fn() );
|
|
7
|
-
const runWithContextMock = vi.hoisted( () => vi.fn().mockImplementation( async fn => fn() ) );
|
|
8
|
-
const activityInfoMock = vi.hoisted( () => ( {
|
|
9
|
-
workflowExecution: { workflowId: 'wf-1', runId: 'run-1' },
|
|
10
|
-
activityId: 'act-1',
|
|
11
|
-
attempt: 1,
|
|
12
|
-
activityType: 'myWorkflow#myStep',
|
|
13
|
-
workflowType: 'myWorkflow'
|
|
14
|
-
} ) );
|
|
15
|
-
const traceInfoMock = vi.hoisted( () => ( {
|
|
16
|
-
workflowId: 'wf-1',
|
|
17
|
-
runId: 'run-1',
|
|
18
|
-
workflowType: 'myWorkflow',
|
|
19
|
-
startTime: 1710000000000
|
|
20
|
-
} ) );
|
|
21
|
-
const workflowDetailsMock = vi.hoisted( () => ( {
|
|
22
|
-
workflowId: 'wf-1',
|
|
23
|
-
runId: 'run-1',
|
|
24
|
-
workflowType: 'myWorkflow',
|
|
25
|
-
firstExecutionRunId: 'run-1',
|
|
26
|
-
startTime: 1710000000000,
|
|
27
|
-
runStartTime: 1710000000000,
|
|
28
|
-
attempt: 1
|
|
29
|
-
} ) );
|
|
30
|
-
|
|
31
|
-
vi.mock( '@temporalio/activity', async importOriginal => {
|
|
32
|
-
const actual = await importOriginal();
|
|
33
|
-
return {
|
|
34
|
-
...actual,
|
|
35
|
-
activityInfo: () => activityInfoMock,
|
|
36
|
-
Context: {
|
|
37
|
-
current: () => ( {
|
|
38
|
-
info: activityInfoMock,
|
|
39
|
-
heartbeat: heartbeatMock
|
|
40
|
-
} )
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
} );
|
|
44
|
-
|
|
45
|
-
vi.mock( '#async_storage', () => ( {
|
|
46
|
-
Storage: {
|
|
47
|
-
runWithContext: runWithContextMock
|
|
48
|
-
}
|
|
49
|
-
} ) );
|
|
50
|
-
|
|
51
|
-
const addEventStartMock = vi.fn();
|
|
52
|
-
const addEventEndMock = vi.fn();
|
|
53
|
-
const addEventErrorMock = vi.fn();
|
|
54
|
-
vi.mock( '#tracing', () => ( {
|
|
55
|
-
addEventStart: addEventStartMock,
|
|
56
|
-
addEventEnd: addEventEndMock,
|
|
57
|
-
addEventError: addEventErrorMock
|
|
58
|
-
} ) );
|
|
59
|
-
|
|
60
|
-
vi.mock( './headers.js', () => ( {
|
|
61
|
-
headersToObject: () => ( { traceInfo: traceInfoMock, workflowDetails: workflowDetailsMock } )
|
|
62
|
-
} ) );
|
|
63
|
-
|
|
64
|
-
const mainEventBusEmitMock = vi.fn();
|
|
65
|
-
vi.mock( '#bus', () => ( { mainEventBus: { emit: mainEventBusEmitMock } } ) );
|
|
66
|
-
|
|
67
|
-
vi.mock( '../configs.js', () => ( {
|
|
68
|
-
get activityHeartbeatEnabled() {
|
|
69
|
-
return process.env.OUTPUT_ACTIVITY_HEARTBEAT_ENABLED !== 'false';
|
|
70
|
-
},
|
|
71
|
-
get activityHeartbeatIntervalMs() {
|
|
72
|
-
return parseInt( process.env.OUTPUT_ACTIVITY_HEARTBEAT_INTERVAL_MS || '120000', 10 );
|
|
73
|
-
}
|
|
74
|
-
} ) );
|
|
75
|
-
|
|
76
|
-
const makeActivities = () => ( {
|
|
77
|
-
'myWorkflow#myStep': { [METADATA_ACCESS_SYMBOL]: { type: 'step' } }
|
|
78
|
-
} );
|
|
79
|
-
|
|
80
|
-
const makeWorkflows = () => [ { name: 'myWorkflow', path: '/workflows/myWorkflow.js' } ];
|
|
81
|
-
|
|
82
|
-
const makeInput = () => ( {
|
|
83
|
-
args: [ { someInput: 'data' } ],
|
|
84
|
-
headers: {}
|
|
85
|
-
} );
|
|
86
|
-
|
|
87
|
-
describe( 'ActivityExecutionInterceptor', () => {
|
|
88
|
-
beforeEach( () => {
|
|
89
|
-
vi.clearAllMocks();
|
|
90
|
-
activityInfoMock.workflowType = 'myWorkflow';
|
|
91
|
-
activityInfoMock.workflowExecution.runId = 'run-1';
|
|
92
|
-
activityInfoMock.attempt = 1;
|
|
93
|
-
delete activityInfoMock.retryPolicy;
|
|
94
|
-
vi.useFakeTimers();
|
|
95
|
-
// Default: heartbeat enabled with 50ms interval for fast tests
|
|
96
|
-
vi.stubEnv( 'OUTPUT_ACTIVITY_HEARTBEAT_ENABLED', 'true' );
|
|
97
|
-
vi.stubEnv( 'OUTPUT_ACTIVITY_HEARTBEAT_INTERVAL_MS', '50' );
|
|
98
|
-
} );
|
|
99
|
-
|
|
100
|
-
afterEach( () => {
|
|
101
|
-
vi.useRealTimers();
|
|
102
|
-
vi.unstubAllEnvs();
|
|
103
|
-
} );
|
|
104
|
-
|
|
105
|
-
it( 'records trace start and end events on successful execution', async () => {
|
|
106
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
107
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
108
|
-
const next = vi.fn().mockResolvedValue( { result: 'ok' } );
|
|
109
|
-
|
|
110
|
-
await expect( interceptor.execute( makeInput(), next ) ).resolves.toEqual( { result: 'ok' } );
|
|
111
|
-
expect( mainEventBusEmitMock ).toHaveBeenCalledWith(
|
|
112
|
-
BusEventType.ACTIVITY_START,
|
|
113
|
-
{ activityInfo: activityInfoMock, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
114
|
-
);
|
|
115
|
-
expect( mainEventBusEmitMock ).toHaveBeenCalledWith(
|
|
116
|
-
BusEventType.ACTIVITY_END,
|
|
117
|
-
{ activityInfo: activityInfoMock, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
118
|
-
);
|
|
119
|
-
expect( addEventStartMock ).toHaveBeenCalledWith( {
|
|
120
|
-
id: 'run-1:act-1:1',
|
|
121
|
-
name: 'myWorkflow#myStep',
|
|
122
|
-
kind: 'step',
|
|
123
|
-
parentId: 'run-1',
|
|
124
|
-
details: { someInput: 'data' },
|
|
125
|
-
traceInfo: traceInfoMock
|
|
126
|
-
} );
|
|
127
|
-
expect( addEventEndMock ).toHaveBeenCalledWith( { id: 'run-1:act-1:1', details: { result: 'ok' }, traceInfo: traceInfoMock } );
|
|
128
|
-
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
129
|
-
expect( runWithContextMock ).toHaveBeenCalledWith(
|
|
130
|
-
expect.any( Function ),
|
|
131
|
-
expect.objectContaining( {
|
|
132
|
-
parentId: 'run-1:act-1:1',
|
|
133
|
-
activityInfo: activityInfoMock,
|
|
134
|
-
workflowDetails: workflowDetailsMock,
|
|
135
|
-
outputActivityKind: 'step',
|
|
136
|
-
workflowFilename: '/workflows/myWorkflow.js',
|
|
137
|
-
traceInfo: traceInfoMock
|
|
138
|
-
} )
|
|
139
|
-
);
|
|
140
|
-
} );
|
|
141
|
-
|
|
142
|
-
it( 'scopes trace ids by attempt so retries do not reuse the same id', async () => {
|
|
143
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
144
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
145
|
-
activityInfoMock.attempt = 3;
|
|
146
|
-
const next = vi.fn().mockResolvedValue( { result: 'ok' } );
|
|
147
|
-
|
|
148
|
-
await expect( interceptor.execute( makeInput(), next ) ).resolves.toEqual( { result: 'ok' } );
|
|
149
|
-
expect( addEventStartMock ).toHaveBeenCalledWith( expect.objectContaining( { id: 'run-1:act-1:3', parentId: 'run-1' } ) );
|
|
150
|
-
expect( addEventEndMock ).toHaveBeenCalledWith( expect.objectContaining( { id: 'run-1:act-1:3' } ) );
|
|
151
|
-
expect( runWithContextMock ).toHaveBeenCalledWith(
|
|
152
|
-
expect.any( Function ),
|
|
153
|
-
expect.objectContaining( { parentId: 'run-1:act-1:3' } )
|
|
154
|
-
);
|
|
155
|
-
} );
|
|
156
|
-
|
|
157
|
-
it( 'scopes trace ids by run so a child workflow reusing an activityId gets a distinct id', async () => {
|
|
158
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
159
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
160
|
-
const next = vi.fn().mockResolvedValue( { result: 'ok' } );
|
|
161
|
-
|
|
162
|
-
// Temporal numbers activityIds per run, so a parent and its child both reach the same 'act-1' on attempt 1.
|
|
163
|
-
await interceptor.execute( makeInput(), next );
|
|
164
|
-
activityInfoMock.workflowExecution.runId = 'run-2';
|
|
165
|
-
await interceptor.execute( makeInput(), next );
|
|
166
|
-
|
|
167
|
-
expect( addEventStartMock.mock.calls.map( ( [ { id, parentId } ] ) => ( { id, parentId } ) ) ).toEqual( [
|
|
168
|
-
{ id: 'run-1:act-1:1', parentId: 'run-1' },
|
|
169
|
-
{ id: 'run-2:act-1:1', parentId: 'run-2' }
|
|
170
|
-
] );
|
|
171
|
-
} );
|
|
172
|
-
|
|
173
|
-
it( 'wraps native errors with serialized details', async () => {
|
|
174
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
175
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
176
|
-
const error = new Error( 'step failed' );
|
|
177
|
-
error.code = 'ESTEP';
|
|
178
|
-
const next = vi.fn().mockRejectedValue( error );
|
|
179
|
-
|
|
180
|
-
const thrown = await interceptor.execute( makeInput(), next ).catch( e => e );
|
|
181
|
-
|
|
182
|
-
expect( thrown ).toBeInstanceOf( ApplicationFailure );
|
|
183
|
-
expect( thrown ).toMatchObject( {
|
|
184
|
-
message: 'step failed',
|
|
185
|
-
type: 'Error',
|
|
186
|
-
details: [ {
|
|
187
|
-
error: {
|
|
188
|
-
name: 'Error',
|
|
189
|
-
message: 'step failed',
|
|
190
|
-
code: 'ESTEP'
|
|
191
|
-
}
|
|
192
|
-
} ],
|
|
193
|
-
cause: error,
|
|
194
|
-
nonRetryable: false
|
|
195
|
-
} );
|
|
196
|
-
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
197
|
-
activityInfo: activityInfoMock,
|
|
198
|
-
workflowDetails: workflowDetailsMock,
|
|
199
|
-
outputActivityKind: 'step',
|
|
200
|
-
error
|
|
201
|
-
} );
|
|
202
|
-
expect( addEventErrorMock ).toHaveBeenCalledOnce();
|
|
203
|
-
expect( addEventEndMock ).not.toHaveBeenCalled();
|
|
204
|
-
} );
|
|
205
|
-
|
|
206
|
-
it( 'marks inherited configured error types as non-retryable', async () => {
|
|
207
|
-
class ValidationError extends Error {}
|
|
208
|
-
class SpecificValidationError extends ValidationError {}
|
|
209
|
-
activityInfoMock.retryPolicy = { nonRetryableErrorTypes: [ 'ValidationError' ] };
|
|
210
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
211
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
212
|
-
const error = new SpecificValidationError( 'invalid input' );
|
|
213
|
-
const next = vi.fn().mockRejectedValue( error );
|
|
214
|
-
|
|
215
|
-
const thrown = await interceptor.execute( makeInput(), next ).catch( e => e );
|
|
216
|
-
|
|
217
|
-
expect( thrown ).toMatchObject( {
|
|
218
|
-
type: 'SpecificValidationError',
|
|
219
|
-
nonRetryable: true,
|
|
220
|
-
cause: error
|
|
221
|
-
} );
|
|
222
|
-
} );
|
|
223
|
-
|
|
224
|
-
it( 'uses a transparent error cause while remaining non-retryable without retry policy metadata', async () => {
|
|
225
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
226
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
227
|
-
const cause = new TypeError( 'provider rejected request' );
|
|
228
|
-
cause.code = 'EAUTH';
|
|
229
|
-
const error = new TransparentFatalError( cause );
|
|
230
|
-
const next = vi.fn().mockRejectedValue( error );
|
|
231
|
-
|
|
232
|
-
const thrown = await interceptor.execute( makeInput(), next ).catch( e => e );
|
|
233
|
-
|
|
234
|
-
expect( thrown ).toMatchObject( {
|
|
235
|
-
message: 'provider rejected request',
|
|
236
|
-
type: 'TypeError',
|
|
237
|
-
nonRetryable: true,
|
|
238
|
-
cause,
|
|
239
|
-
details: [ {
|
|
240
|
-
error: {
|
|
241
|
-
name: 'TypeError',
|
|
242
|
-
message: 'provider rejected request',
|
|
243
|
-
code: 'EAUTH'
|
|
244
|
-
}
|
|
245
|
-
} ]
|
|
246
|
-
} );
|
|
247
|
-
expect( addEventErrorMock ).toHaveBeenCalledWith( { id: 'run-1:act-1:1', details: cause, traceInfo: traceInfoMock } );
|
|
248
|
-
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
249
|
-
activityInfo: activityInfoMock,
|
|
250
|
-
workflowDetails: workflowDetailsMock,
|
|
251
|
-
outputActivityKind: 'step',
|
|
252
|
-
error: cause
|
|
253
|
-
} );
|
|
254
|
-
} );
|
|
255
|
-
|
|
256
|
-
it( 'rethrows existing Temporal failures unchanged', async () => {
|
|
257
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
258
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
259
|
-
const error = ApplicationFailure.nonRetryable( 'domain failed', 'DomainFailure', { reason: 'invalid' } );
|
|
260
|
-
const next = vi.fn().mockRejectedValue( error );
|
|
261
|
-
|
|
262
|
-
await expect( interceptor.execute( makeInput(), next ) ).rejects.toBe( error );
|
|
263
|
-
expect( addEventErrorMock ).toHaveBeenCalledWith( { id: 'run-1:act-1:1', details: error, traceInfo: traceInfoMock } );
|
|
264
|
-
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
265
|
-
activityInfo: activityInfoMock,
|
|
266
|
-
workflowDetails: workflowDetailsMock,
|
|
267
|
-
outputActivityKind: 'step',
|
|
268
|
-
error
|
|
269
|
-
} );
|
|
270
|
-
} );
|
|
271
|
-
|
|
272
|
-
it( 'records async completion handoff as a trace end only', async () => {
|
|
273
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
274
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
275
|
-
const error = new CompleteAsyncError();
|
|
276
|
-
const next = vi.fn().mockRejectedValue( error );
|
|
277
|
-
|
|
278
|
-
await expect( interceptor.execute( makeInput(), next ) ).rejects.toBe( error );
|
|
279
|
-
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_START, expect.any( Object ) );
|
|
280
|
-
expect( mainEventBusEmitMock ).toHaveBeenCalledTimes( 1 );
|
|
281
|
-
expect( addEventEndMock ).toHaveBeenCalledWith( {
|
|
282
|
-
id: 'run-1:act-1:1',
|
|
283
|
-
details: ActivitySpecialOutput.ASYNC_HANDOFF,
|
|
284
|
-
traceInfo: traceInfoMock
|
|
285
|
-
} );
|
|
286
|
-
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
287
|
-
} );
|
|
288
|
-
|
|
289
|
-
it( 'sends periodic heartbeats and stops after successful execution', async () => {
|
|
290
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
291
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
292
|
-
|
|
293
|
-
const deferred = { resolve: null };
|
|
294
|
-
const next = vi.fn().mockImplementation( () => new Promise( r => {
|
|
295
|
-
deferred.resolve = r;
|
|
296
|
-
} ) );
|
|
297
|
-
|
|
298
|
-
const promise = interceptor.execute( makeInput(), next );
|
|
299
|
-
|
|
300
|
-
expect( heartbeatMock ).not.toHaveBeenCalled();
|
|
301
|
-
|
|
302
|
-
vi.advanceTimersByTime( 50 );
|
|
303
|
-
expect( heartbeatMock ).toHaveBeenCalledTimes( 1 );
|
|
304
|
-
|
|
305
|
-
vi.advanceTimersByTime( 50 );
|
|
306
|
-
expect( heartbeatMock ).toHaveBeenCalledTimes( 2 );
|
|
307
|
-
|
|
308
|
-
vi.advanceTimersByTime( 50 );
|
|
309
|
-
expect( heartbeatMock ).toHaveBeenCalledTimes( 3 );
|
|
310
|
-
|
|
311
|
-
deferred.resolve( { result: 'done' } );
|
|
312
|
-
await promise;
|
|
313
|
-
|
|
314
|
-
heartbeatMock.mockClear();
|
|
315
|
-
vi.advanceTimersByTime( 500 );
|
|
316
|
-
expect( heartbeatMock ).not.toHaveBeenCalled();
|
|
317
|
-
} );
|
|
318
|
-
|
|
319
|
-
it( 'clears heartbeat interval after activity fails', async () => {
|
|
320
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
321
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
322
|
-
const next = vi.fn().mockRejectedValue( new Error( 'boom' ) );
|
|
323
|
-
|
|
324
|
-
const promise = interceptor.execute( makeInput(), next );
|
|
325
|
-
vi.advanceTimersByTime( 0 );
|
|
326
|
-
await expect( promise ).rejects.toBeInstanceOf( ApplicationFailure );
|
|
327
|
-
|
|
328
|
-
heartbeatMock.mockClear();
|
|
329
|
-
vi.advanceTimersByTime( 500 );
|
|
330
|
-
expect( heartbeatMock ).not.toHaveBeenCalled();
|
|
331
|
-
} );
|
|
332
|
-
|
|
333
|
-
it( 'resolves workflow alias in workflowsMap', async () => {
|
|
334
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
335
|
-
const workflows = [ { name: 'myWorkflow', path: '/workflows/myWorkflow.js', aliases: [ 'myWorkflowOld' ] } ];
|
|
336
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows } );
|
|
337
|
-
|
|
338
|
-
activityInfoMock.workflowType = 'myWorkflowOld';
|
|
339
|
-
const next = vi.fn().mockResolvedValue( { result: 'ok' } );
|
|
340
|
-
|
|
341
|
-
const promise = interceptor.execute( makeInput(), next );
|
|
342
|
-
vi.advanceTimersByTime( 0 );
|
|
343
|
-
await promise;
|
|
344
|
-
|
|
345
|
-
expect( runWithContextMock ).toHaveBeenCalledWith(
|
|
346
|
-
expect.any( Function ),
|
|
347
|
-
expect.objectContaining( { workflowFilename: '/workflows/myWorkflow.js' } )
|
|
348
|
-
);
|
|
349
|
-
} );
|
|
350
|
-
|
|
351
|
-
it( 'does not heartbeat when OUTPUT_ACTIVITY_HEARTBEAT_ENABLED is false', async () => {
|
|
352
|
-
vi.stubEnv( 'OUTPUT_ACTIVITY_HEARTBEAT_ENABLED', 'false' );
|
|
353
|
-
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
354
|
-
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
355
|
-
|
|
356
|
-
const deferred = { resolve: null };
|
|
357
|
-
const next = vi.fn().mockImplementation( () => new Promise( r => {
|
|
358
|
-
deferred.resolve = r;
|
|
359
|
-
} ) );
|
|
360
|
-
|
|
361
|
-
const promise = interceptor.execute( makeInput(), next );
|
|
362
|
-
|
|
363
|
-
vi.advanceTimersByTime( 200 );
|
|
364
|
-
expect( heartbeatMock ).not.toHaveBeenCalled();
|
|
365
|
-
|
|
366
|
-
deferred.resolve( { result: 'done' } );
|
|
367
|
-
await promise;
|
|
368
|
-
} );
|
|
369
|
-
} );
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
const toPayloadMock = vi.hoisted( () => vi.fn( value => ( { encoded: value } ) ) );
|
|
4
|
-
const fromPayloadMock = vi.hoisted( () => vi.fn( payload => payload.encoded ) );
|
|
5
|
-
|
|
6
|
-
vi.mock( '@temporalio/common', () => ( {
|
|
7
|
-
defaultPayloadConverter: {
|
|
8
|
-
toPayload: toPayloadMock,
|
|
9
|
-
fromPayload: fromPayloadMock
|
|
10
|
-
}
|
|
11
|
-
} ) );
|
|
12
|
-
|
|
13
|
-
describe( 'headers utils', () => {
|
|
14
|
-
beforeEach( () => {
|
|
15
|
-
vi.clearAllMocks();
|
|
16
|
-
} );
|
|
17
|
-
|
|
18
|
-
describe( 'memoToHeaders', () => {
|
|
19
|
-
it( 'converts memo entries into Temporal payload headers', async () => {
|
|
20
|
-
const { memoToHeaders } = await import( './headers.js' );
|
|
21
|
-
const memo = {
|
|
22
|
-
traceInfo: { runId: 'run-1' },
|
|
23
|
-
workflowDetails: { workflowId: 'workflow-1' }
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const headers = memoToHeaders( memo );
|
|
27
|
-
|
|
28
|
-
expect( headers ).toEqual( {
|
|
29
|
-
traceInfo: { encoded: memo.traceInfo },
|
|
30
|
-
workflowDetails: { encoded: memo.workflowDetails }
|
|
31
|
-
} );
|
|
32
|
-
expect( toPayloadMock ).toHaveBeenCalledTimes( 2 );
|
|
33
|
-
expect( toPayloadMock ).toHaveBeenCalledWith( memo.traceInfo );
|
|
34
|
-
expect( toPayloadMock ).toHaveBeenCalledWith( memo.workflowDetails );
|
|
35
|
-
} );
|
|
36
|
-
|
|
37
|
-
it( 'returns an empty object for nullish memo', async () => {
|
|
38
|
-
const { memoToHeaders } = await import( './headers.js' );
|
|
39
|
-
|
|
40
|
-
expect( memoToHeaders() ).toEqual( {} );
|
|
41
|
-
expect( memoToHeaders( null ) ).toEqual( {} );
|
|
42
|
-
expect( toPayloadMock ).not.toHaveBeenCalled();
|
|
43
|
-
} );
|
|
44
|
-
} );
|
|
45
|
-
|
|
46
|
-
describe( 'headersToObject', () => {
|
|
47
|
-
it( 'converts Temporal payload headers into plain object values', async () => {
|
|
48
|
-
const { headersToObject } = await import( './headers.js' );
|
|
49
|
-
const headers = {
|
|
50
|
-
traceInfo: { encoded: { runId: 'run-1' } },
|
|
51
|
-
workflowDetails: { encoded: { workflowId: 'workflow-1' } }
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const object = headersToObject( headers );
|
|
55
|
-
|
|
56
|
-
expect( object ).toEqual( {
|
|
57
|
-
traceInfo: headers.traceInfo.encoded,
|
|
58
|
-
workflowDetails: headers.workflowDetails.encoded
|
|
59
|
-
} );
|
|
60
|
-
expect( fromPayloadMock ).toHaveBeenCalledTimes( 2 );
|
|
61
|
-
expect( fromPayloadMock ).toHaveBeenCalledWith( headers.traceInfo );
|
|
62
|
-
expect( fromPayloadMock ).toHaveBeenCalledWith( headers.workflowDetails );
|
|
63
|
-
} );
|
|
64
|
-
|
|
65
|
-
it( 'returns an empty object for nullish headers', async () => {
|
|
66
|
-
const { headersToObject } = await import( './headers.js' );
|
|
67
|
-
|
|
68
|
-
expect( headersToObject() ).toEqual( {} );
|
|
69
|
-
expect( headersToObject( null ) ).toEqual( {} );
|
|
70
|
-
expect( fromPayloadMock ).not.toHaveBeenCalled();
|
|
71
|
-
} );
|
|
72
|
-
} );
|
|
73
|
-
} );
|