@outputai/core 0.14.0 → 0.14.1-next.51e8690.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 +14 -3
- 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,124 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
const loadMock = vi.fn();
|
|
4
|
-
vi.mock( '#async_storage', () => ( {
|
|
5
|
-
Storage: { load: loadMock }
|
|
6
|
-
} ) );
|
|
7
|
-
|
|
8
|
-
const getHandleMock = vi.fn();
|
|
9
|
-
const clientConstructorMock = vi.fn( function () {
|
|
10
|
-
this.workflow = { getHandle: getHandleMock };
|
|
11
|
-
} );
|
|
12
|
-
vi.mock( '@temporalio/client', () => ( {
|
|
13
|
-
Client: clientConstructorMock
|
|
14
|
-
} ) );
|
|
15
|
-
|
|
16
|
-
const connection = {
|
|
17
|
-
fake: 'native-connection',
|
|
18
|
-
close: vi.fn().mockResolvedValue( undefined )
|
|
19
|
-
};
|
|
20
|
-
const activityContext = {
|
|
21
|
-
activityInfo: {
|
|
22
|
-
workflowExecution: {
|
|
23
|
-
workflowId: 'workflow-1',
|
|
24
|
-
runId: 'run-1'
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
describe( 'Temporal client', () => {
|
|
30
|
-
beforeEach( () => {
|
|
31
|
-
vi.clearAllMocks();
|
|
32
|
-
vi.resetModules();
|
|
33
|
-
} );
|
|
34
|
-
|
|
35
|
-
describe( 'createTemporalClient', () => {
|
|
36
|
-
it( 'throws a FatalError outside the worker runtime', async () => {
|
|
37
|
-
const { createTemporalClient } = await import( './client.js' );
|
|
38
|
-
const { FatalError } = await import( '#errors' );
|
|
39
|
-
|
|
40
|
-
expect( () => createTemporalClient() ).toThrow( FatalError );
|
|
41
|
-
} );
|
|
42
|
-
|
|
43
|
-
it( 'creates a client with the worker connection and namespace', async () => {
|
|
44
|
-
const { createTemporalClient, setupClientConfig } = await import( './client.js' );
|
|
45
|
-
setupClientConfig( { connection, namespace: 'namespace-1' } );
|
|
46
|
-
|
|
47
|
-
const client = createTemporalClient();
|
|
48
|
-
|
|
49
|
-
expect( clientConstructorMock ).toHaveBeenCalledWith( {
|
|
50
|
-
connection: expect.anything(),
|
|
51
|
-
namespace: 'namespace-1'
|
|
52
|
-
} );
|
|
53
|
-
expect( clientConstructorMock.mock.calls[0][0].connection ).not.toBe( connection );
|
|
54
|
-
expect( client ).toBeInstanceOf( clientConstructorMock );
|
|
55
|
-
} );
|
|
56
|
-
|
|
57
|
-
it( 'delegates connection access but prevents closing the worker connection', async () => {
|
|
58
|
-
const { createTemporalClient, setupClientConfig } = await import( './client.js' );
|
|
59
|
-
const { FatalError } = await import( '#errors' );
|
|
60
|
-
setupClientConfig( { connection, namespace: 'namespace-1' } );
|
|
61
|
-
|
|
62
|
-
createTemporalClient();
|
|
63
|
-
const clientConnection = clientConstructorMock.mock.calls[0][0].connection;
|
|
64
|
-
|
|
65
|
-
expect( clientConnection.fake ).toBe( 'native-connection' );
|
|
66
|
-
await expect( clientConnection.close() ).rejects.toThrow( FatalError );
|
|
67
|
-
expect( connection.close ).not.toHaveBeenCalled();
|
|
68
|
-
} );
|
|
69
|
-
|
|
70
|
-
it( 'returns a new client on each call', async () => {
|
|
71
|
-
const { createTemporalClient, setupClientConfig } = await import( './client.js' );
|
|
72
|
-
setupClientConfig( { connection, namespace: 'namespace-1' } );
|
|
73
|
-
|
|
74
|
-
expect( createTemporalClient() ).not.toBe( createTemporalClient() );
|
|
75
|
-
expect( clientConstructorMock ).toHaveBeenCalledTimes( 2 );
|
|
76
|
-
} );
|
|
77
|
-
} );
|
|
78
|
-
|
|
79
|
-
describe( 'getCurrentWorkflowHandle', () => {
|
|
80
|
-
it( 'throws a FatalError outside a Temporal activity context', async () => {
|
|
81
|
-
loadMock.mockReturnValue( undefined );
|
|
82
|
-
const { getCurrentWorkflowHandle, setupClientConfig } = await import( './client.js' );
|
|
83
|
-
const { FatalError } = await import( '#errors' );
|
|
84
|
-
setupClientConfig( { connection, namespace: 'namespace-1' } );
|
|
85
|
-
|
|
86
|
-
expect( () => getCurrentWorkflowHandle() ).toThrow( FatalError );
|
|
87
|
-
} );
|
|
88
|
-
|
|
89
|
-
it( 'throws a FatalError outside the worker runtime', async () => {
|
|
90
|
-
loadMock.mockReturnValue( activityContext );
|
|
91
|
-
const { getCurrentWorkflowHandle } = await import( './client.js' );
|
|
92
|
-
const { FatalError } = await import( '#errors' );
|
|
93
|
-
|
|
94
|
-
expect( () => getCurrentWorkflowHandle() ).toThrow( FatalError );
|
|
95
|
-
} );
|
|
96
|
-
|
|
97
|
-
it( 'returns the handle for the current workflow execution', async () => {
|
|
98
|
-
const handle = { signal: vi.fn() };
|
|
99
|
-
loadMock.mockReturnValue( activityContext );
|
|
100
|
-
getHandleMock.mockReturnValue( handle );
|
|
101
|
-
const { getCurrentWorkflowHandle, setupClientConfig } = await import( './client.js' );
|
|
102
|
-
setupClientConfig( { connection, namespace: 'namespace-1' } );
|
|
103
|
-
|
|
104
|
-
expect( getCurrentWorkflowHandle() ).toBe( handle );
|
|
105
|
-
expect( clientConstructorMock ).toHaveBeenCalledWith( {
|
|
106
|
-
connection: expect.anything(),
|
|
107
|
-
namespace: 'namespace-1'
|
|
108
|
-
} );
|
|
109
|
-
expect( getHandleMock ).toHaveBeenCalledWith( 'workflow-1', 'run-1' );
|
|
110
|
-
} );
|
|
111
|
-
|
|
112
|
-
it( 'reuses its client across calls', async () => {
|
|
113
|
-
loadMock.mockReturnValue( activityContext );
|
|
114
|
-
const { getCurrentWorkflowHandle, setupClientConfig } = await import( './client.js' );
|
|
115
|
-
setupClientConfig( { connection, namespace: 'namespace-1' } );
|
|
116
|
-
|
|
117
|
-
getCurrentWorkflowHandle();
|
|
118
|
-
getCurrentWorkflowHandle();
|
|
119
|
-
|
|
120
|
-
expect( clientConstructorMock ).toHaveBeenCalledTimes( 1 );
|
|
121
|
-
expect( getHandleMock ).toHaveBeenCalledTimes( 2 );
|
|
122
|
-
} );
|
|
123
|
-
} );
|
|
124
|
-
} );
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// In-memory fs mock store
|
|
4
|
-
const store = { files: new Map() };
|
|
5
|
-
const mkdirSyncMock = vi.fn();
|
|
6
|
-
const writeFileSyncMock = vi.fn();
|
|
7
|
-
const appendFileSyncMock = vi.fn( ( path, data ) => {
|
|
8
|
-
const prev = store.files.get( path ) ?? '';
|
|
9
|
-
store.files.set( path, prev + data );
|
|
10
|
-
} );
|
|
11
|
-
const readFileSyncMock = vi.fn( path => store.files.get( path ) ?? '' );
|
|
12
|
-
const readdirSyncMock = vi.fn( () => [] );
|
|
13
|
-
const rmSyncMock = vi.fn();
|
|
14
|
-
const createWriteStreamMock = vi.fn( path => ( { path } ) );
|
|
15
|
-
|
|
16
|
-
vi.mock( 'node:fs', () => ( {
|
|
17
|
-
mkdirSync: mkdirSyncMock,
|
|
18
|
-
writeFileSync: writeFileSyncMock,
|
|
19
|
-
appendFileSync: appendFileSyncMock,
|
|
20
|
-
readFileSync: readFileSyncMock,
|
|
21
|
-
readdirSync: readdirSyncMock,
|
|
22
|
-
rmSync: rmSyncMock,
|
|
23
|
-
createWriteStream: createWriteStreamMock
|
|
24
|
-
} ) );
|
|
25
|
-
|
|
26
|
-
const pipelineMock = vi.fn( async ( source, destination ) => {
|
|
27
|
-
const chunks = [];
|
|
28
|
-
for await ( const chunk of source ) {
|
|
29
|
-
chunks.push( Buffer.isBuffer( chunk ) ? chunk : Buffer.from( chunk ) );
|
|
30
|
-
}
|
|
31
|
-
store.files.set( destination.path, Buffer.concat( chunks ).toString( 'utf8' ) );
|
|
32
|
-
} );
|
|
33
|
-
vi.mock( 'node:stream/promises', () => ( { pipeline: pipelineMock } ) );
|
|
34
|
-
|
|
35
|
-
vi.mock( 'json-stream-stringify', async () => {
|
|
36
|
-
const { Readable } = await import( 'node:stream' );
|
|
37
|
-
return {
|
|
38
|
-
JsonStreamStringify: class extends Readable {
|
|
39
|
-
constructor( body ) {
|
|
40
|
-
super();
|
|
41
|
-
this.body = body;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
_read() {
|
|
45
|
-
this.push( JSON.stringify( this.body ) );
|
|
46
|
-
this.push( null );
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
} );
|
|
51
|
-
|
|
52
|
-
const buildTraceTreeMock = vi.fn( entries => ( { count: entries.length } ) );
|
|
53
|
-
vi.mock( '../../tools/build_trace_tree.js', () => ( { default: buildTraceTreeMock } ) );
|
|
54
|
-
|
|
55
|
-
/** Flush happens when the root id matches runId and action is not 'start', or when action is 'error'. */
|
|
56
|
-
const rootStart = ( runId, ts ) => ( { id: runId, action: 'start', timestamp: ts } );
|
|
57
|
-
const rootEnd = ( runId, ts ) => ( { id: runId, action: 'end', timestamp: ts } );
|
|
58
|
-
const childTick = ( id, ts ) => ( { id, action: 'tick', timestamp: ts } );
|
|
59
|
-
|
|
60
|
-
describe( 'tracing/processors/local', () => {
|
|
61
|
-
const startTime = Date.parse( '2020-01-02T03:04:05.678Z' );
|
|
62
|
-
const traceInfo = {
|
|
63
|
-
workflowId: 'id1',
|
|
64
|
-
runId: 'run-1',
|
|
65
|
-
workflowType: 'WF',
|
|
66
|
-
startTime
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
beforeEach( () => {
|
|
70
|
-
vi.clearAllMocks();
|
|
71
|
-
store.files.clear();
|
|
72
|
-
process.argv[2] = '/tmp/project';
|
|
73
|
-
delete process.env.OUTPUT_TRACE_HOST_PATH;
|
|
74
|
-
} );
|
|
75
|
-
|
|
76
|
-
it( 'init(): creates temp dir and cleans up old files', async () => {
|
|
77
|
-
const { init } = await import( './index.js' );
|
|
78
|
-
|
|
79
|
-
const now = Date.now();
|
|
80
|
-
readdirSyncMock.mockReturnValue( [ `${now - ( 8 * 24 * 60 * 60 * 1000 )}_old.trace`, `${now}_new.trace` ] );
|
|
81
|
-
|
|
82
|
-
init();
|
|
83
|
-
|
|
84
|
-
expect( mkdirSyncMock ).toHaveBeenCalledWith( expect.stringMatching( /temp\/traces$/ ), { recursive: true } );
|
|
85
|
-
expect( rmSyncMock ).toHaveBeenCalledTimes( 1 );
|
|
86
|
-
} );
|
|
87
|
-
|
|
88
|
-
it( 'exec(): appends each entry and writes aggregated tree once on root workflow end', async () => {
|
|
89
|
-
const { exec, init } = await import( './index.js' );
|
|
90
|
-
init();
|
|
91
|
-
|
|
92
|
-
await exec( { traceInfo, entry: rootStart( traceInfo.runId, startTime ) } );
|
|
93
|
-
await exec( { traceInfo, entry: childTick( 'child-1', startTime + 1 ) } );
|
|
94
|
-
await exec( { traceInfo, entry: rootEnd( traceInfo.runId, startTime + 2 ) } );
|
|
95
|
-
|
|
96
|
-
expect( buildTraceTreeMock ).toHaveBeenCalledTimes( 1 );
|
|
97
|
-
expect( buildTraceTreeMock.mock.calls[0][0] ).toHaveLength( 3 );
|
|
98
|
-
|
|
99
|
-
expect( createWriteStreamMock ).toHaveBeenCalledTimes( 1 );
|
|
100
|
-
expect( pipelineMock ).toHaveBeenCalledTimes( 1 );
|
|
101
|
-
const [ writtenPath ] = createWriteStreamMock.mock.calls[0];
|
|
102
|
-
expect( writtenPath ).toMatch( /\/tmp\/project\/logs\/runs\/WF\// );
|
|
103
|
-
expect( JSON.parse( store.files.get( writtenPath ) ).count ).toBe( 3 );
|
|
104
|
-
} );
|
|
105
|
-
|
|
106
|
-
it( 'exec(): does not build or write on non-flush entries', async () => {
|
|
107
|
-
const { exec, init } = await import( './index.js' );
|
|
108
|
-
init();
|
|
109
|
-
|
|
110
|
-
await exec( { traceInfo, entry: rootStart( traceInfo.runId, startTime ) } );
|
|
111
|
-
await exec( { traceInfo, entry: childTick( 'child-1', startTime + 1 ) } );
|
|
112
|
-
|
|
113
|
-
expect( buildTraceTreeMock ).not.toHaveBeenCalled();
|
|
114
|
-
expect( writeFileSyncMock ).not.toHaveBeenCalled();
|
|
115
|
-
expect( createWriteStreamMock ).not.toHaveBeenCalled();
|
|
116
|
-
expect( pipelineMock ).not.toHaveBeenCalled();
|
|
117
|
-
} );
|
|
118
|
-
|
|
119
|
-
it( 'exec(): flushes on error action before root end', async () => {
|
|
120
|
-
const { exec, init } = await import( './index.js' );
|
|
121
|
-
init();
|
|
122
|
-
|
|
123
|
-
await exec( { traceInfo, entry: rootStart( traceInfo.runId, startTime ) } );
|
|
124
|
-
await exec( { traceInfo, entry: { id: 'step-1', action: 'error', timestamp: startTime + 1 } } );
|
|
125
|
-
|
|
126
|
-
expect( buildTraceTreeMock ).toHaveBeenCalledTimes( 1 );
|
|
127
|
-
expect( buildTraceTreeMock.mock.calls[0][0] ).toHaveLength( 2 );
|
|
128
|
-
expect( createWriteStreamMock ).toHaveBeenCalledTimes( 1 );
|
|
129
|
-
expect( pipelineMock ).toHaveBeenCalledTimes( 1 );
|
|
130
|
-
} );
|
|
131
|
-
|
|
132
|
-
it( 'getDestination(): returns absolute path under callerDir logs', async () => {
|
|
133
|
-
const { getDestination } = await import( './index.js' );
|
|
134
|
-
|
|
135
|
-
const destination = getDestination( {
|
|
136
|
-
...traceInfo,
|
|
137
|
-
workflowId: 'workflow-id-123',
|
|
138
|
-
workflowType: 'test-workflow'
|
|
139
|
-
} );
|
|
140
|
-
|
|
141
|
-
expect( destination ).toMatch( /^\/|^[A-Z]:\\/i );
|
|
142
|
-
expect( destination ).toBe(
|
|
143
|
-
'/tmp/project/logs/runs/test-workflow/2020-01-02-03-04-05-678Z_workflow-id-123.json'
|
|
144
|
-
);
|
|
145
|
-
} );
|
|
146
|
-
|
|
147
|
-
it( 'exec(): writes under process.argv[2] logs even when OUTPUT_TRACE_HOST_PATH is set', async () => {
|
|
148
|
-
const { exec, init } = await import( './index.js' );
|
|
149
|
-
|
|
150
|
-
process.env.OUTPUT_TRACE_HOST_PATH = '/host/path/logs';
|
|
151
|
-
|
|
152
|
-
init();
|
|
153
|
-
|
|
154
|
-
await exec( { traceInfo, entry: rootStart( traceInfo.runId, startTime ) } );
|
|
155
|
-
await exec( { traceInfo, entry: rootEnd( traceInfo.runId, startTime + 1 ) } );
|
|
156
|
-
|
|
157
|
-
expect( createWriteStreamMock ).toHaveBeenCalledTimes( 1 );
|
|
158
|
-
const [ writtenPath ] = createWriteStreamMock.mock.calls[0];
|
|
159
|
-
|
|
160
|
-
expect( writtenPath ).not.toContain( '/host/path/logs' );
|
|
161
|
-
expect( writtenPath ).toMatch( /\/tmp\/project\/logs\/runs\/WF\// );
|
|
162
|
-
} );
|
|
163
|
-
|
|
164
|
-
it( 'getDestination(): returns OUTPUT_TRACE_HOST_PATH when set', async () => {
|
|
165
|
-
const { getDestination } = await import( './index.js' );
|
|
166
|
-
|
|
167
|
-
process.env.OUTPUT_TRACE_HOST_PATH = '/host/path/logs';
|
|
168
|
-
|
|
169
|
-
const destination = getDestination( {
|
|
170
|
-
...traceInfo,
|
|
171
|
-
workflowId: 'workflow-id-123',
|
|
172
|
-
workflowType: 'test-workflow'
|
|
173
|
-
} );
|
|
174
|
-
|
|
175
|
-
expect( destination ).toBe( '/host/path/logs/runs/test-workflow/2020-01-02-03-04-05-678Z_workflow-id-123.json' );
|
|
176
|
-
} );
|
|
177
|
-
|
|
178
|
-
it( 'separation of write and report paths works correctly', async () => {
|
|
179
|
-
const { exec, getDestination, init } = await import( './index.js' );
|
|
180
|
-
|
|
181
|
-
process.env.OUTPUT_TRACE_HOST_PATH = '/Users/ben/project/logs';
|
|
182
|
-
|
|
183
|
-
init();
|
|
184
|
-
|
|
185
|
-
const testTraceInfo = {
|
|
186
|
-
...traceInfo,
|
|
187
|
-
workflowId: 'workflow-id-123',
|
|
188
|
-
runId: 'run-123',
|
|
189
|
-
workflowType: 'test-workflow'
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
await exec( { traceInfo: testTraceInfo, entry: rootStart( testTraceInfo.runId, startTime ) } );
|
|
193
|
-
await exec( { traceInfo: testTraceInfo, entry: rootEnd( testTraceInfo.runId, startTime + 1 ) } );
|
|
194
|
-
|
|
195
|
-
const destination = getDestination( testTraceInfo );
|
|
196
|
-
|
|
197
|
-
const [ writtenPath ] = createWriteStreamMock.mock.calls[0];
|
|
198
|
-
expect( writtenPath ).not.toContain( '/Users/ben/project' );
|
|
199
|
-
expect( writtenPath ).toMatch( /\/tmp\/project\/logs\/runs\/test-workflow\// );
|
|
200
|
-
expect( JSON.parse( store.files.get( writtenPath ) ).count ).toBe( 2 );
|
|
201
|
-
|
|
202
|
-
expect( destination ).toBe( '/Users/ben/project/logs/runs/test-workflow/2020-01-02-03-04-05-678Z_workflow-id-123.json' );
|
|
203
|
-
} );
|
|
204
|
-
} );
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
async function loadModule() {
|
|
4
|
-
vi.resetModules();
|
|
5
|
-
return import( './configs.js' );
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
describe( 'tracing/processors/s3/configs', () => {
|
|
9
|
-
const required = {
|
|
10
|
-
OUTPUT_AWS_REGION: 'us-east-1',
|
|
11
|
-
OUTPUT_AWS_ACCESS_KEY_ID: 'id',
|
|
12
|
-
OUTPUT_AWS_SECRET_ACCESS_KEY: 'sek',
|
|
13
|
-
OUTPUT_TRACE_REMOTE_S3_BUCKET: 'bkt',
|
|
14
|
-
OUTPUT_REDIS_URL: 'redis://localhost:6379'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
beforeEach( () => {
|
|
18
|
-
vi.stubEnv( 'OUTPUT_AWS_REGION', required.OUTPUT_AWS_REGION );
|
|
19
|
-
vi.stubEnv( 'OUTPUT_AWS_ACCESS_KEY_ID', required.OUTPUT_AWS_ACCESS_KEY_ID );
|
|
20
|
-
vi.stubEnv( 'OUTPUT_AWS_SECRET_ACCESS_KEY', required.OUTPUT_AWS_SECRET_ACCESS_KEY );
|
|
21
|
-
vi.stubEnv( 'OUTPUT_TRACE_REMOTE_S3_BUCKET', required.OUTPUT_TRACE_REMOTE_S3_BUCKET );
|
|
22
|
-
vi.stubEnv( 'OUTPUT_REDIS_URL', required.OUTPUT_REDIS_URL );
|
|
23
|
-
} );
|
|
24
|
-
|
|
25
|
-
afterEach( () => {
|
|
26
|
-
vi.unstubAllEnvs();
|
|
27
|
-
} );
|
|
28
|
-
|
|
29
|
-
it( 'loadEnv() throws when required env vars are missing', async () => {
|
|
30
|
-
vi.stubEnv( 'OUTPUT_REDIS_URL', undefined );
|
|
31
|
-
const { loadEnv } = await loadModule();
|
|
32
|
-
expect( () => loadEnv() ).toThrow( /OUTPUT_REDIS_URL/ );
|
|
33
|
-
} );
|
|
34
|
-
|
|
35
|
-
it( 'loadEnv() populates getVars() with parsed env', async () => {
|
|
36
|
-
const { loadEnv, getVars } = await loadModule();
|
|
37
|
-
loadEnv();
|
|
38
|
-
const vars = getVars();
|
|
39
|
-
expect( vars.awsRegion ).toBe( required.OUTPUT_AWS_REGION );
|
|
40
|
-
expect( vars.awsAccessKeyId ).toBe( required.OUTPUT_AWS_ACCESS_KEY_ID );
|
|
41
|
-
expect( vars.awsSecretAccessKey ).toBe( required.OUTPUT_AWS_SECRET_ACCESS_KEY );
|
|
42
|
-
expect( vars.remoteS3Bucket ).toBe( required.OUTPUT_TRACE_REMOTE_S3_BUCKET );
|
|
43
|
-
expect( vars.redisUrl ).toBe( required.OUTPUT_REDIS_URL );
|
|
44
|
-
expect( vars.redisIncompleteWorkflowsTTL ).toBe( 60 * 60 * 24 * 7 );
|
|
45
|
-
} );
|
|
46
|
-
|
|
47
|
-
it( 'loadEnv() uses OUTPUT_REDIS_TRACE_TTL when set', async () => {
|
|
48
|
-
vi.stubEnv( 'OUTPUT_REDIS_TRACE_TTL', '3600' );
|
|
49
|
-
const { loadEnv, getVars } = await loadModule();
|
|
50
|
-
loadEnv();
|
|
51
|
-
expect( getVars().redisIncompleteWorkflowsTTL ).toBe( 3600 );
|
|
52
|
-
} );
|
|
53
|
-
|
|
54
|
-
it( 'getVars() throws when loadEnv() was not called', async () => {
|
|
55
|
-
const { getVars } = await loadModule();
|
|
56
|
-
expect( () => getVars() ).toThrow( 'Env vars not loaded. Use loadEnv() first.' );
|
|
57
|
-
} );
|
|
58
|
-
|
|
59
|
-
it( 'loadEnv() throws when OUTPUT_REDIS_TRACE_TTL is invalid', async () => {
|
|
60
|
-
vi.stubEnv( 'OUTPUT_REDIS_TRACE_TTL', 'not-a-number' );
|
|
61
|
-
const { loadEnv } = await loadModule();
|
|
62
|
-
expect( () => loadEnv() ).toThrow();
|
|
63
|
-
} );
|
|
64
|
-
} );
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
const loadEnvMock = vi.fn();
|
|
4
|
-
const getVarsMock = vi.fn( () => ( {
|
|
5
|
-
remoteS3Bucket: 'bkt',
|
|
6
|
-
redisIncompleteWorkflowsTTL: 3600
|
|
7
|
-
} ) );
|
|
8
|
-
vi.mock( './configs.js', () => ( { loadEnv: loadEnvMock, getVars: getVarsMock } ) );
|
|
9
|
-
|
|
10
|
-
const redisMulti = {
|
|
11
|
-
zAdd: vi.fn().mockReturnThis(),
|
|
12
|
-
expire: vi.fn().mockReturnThis(),
|
|
13
|
-
exec: vi.fn()
|
|
14
|
-
};
|
|
15
|
-
const zRangeMock = vi.fn();
|
|
16
|
-
const delMock = vi.fn().mockResolvedValue( undefined );
|
|
17
|
-
const getRedisClientMock = vi.fn( async () => ( {
|
|
18
|
-
multi: () => redisMulti,
|
|
19
|
-
zRange: zRangeMock,
|
|
20
|
-
del: delMock
|
|
21
|
-
} ) );
|
|
22
|
-
vi.mock( './redis_client.js', () => ( { getRedisClient: getRedisClientMock } ) );
|
|
23
|
-
|
|
24
|
-
const uploadMock = vi.fn();
|
|
25
|
-
vi.mock( './s3_client.js', () => ( { upload: uploadMock } ) );
|
|
26
|
-
|
|
27
|
-
const buildTraceTreeMock = vi.fn( entries => ( { count: entries.length } ) );
|
|
28
|
-
vi.mock( '../../tools/build_trace_tree.js', () => ( { default: buildTraceTreeMock } ) );
|
|
29
|
-
|
|
30
|
-
vi.mock( 'json-stream-stringify', async () => {
|
|
31
|
-
const { Readable } = await import( 'node:stream' );
|
|
32
|
-
return {
|
|
33
|
-
JsonStreamStringify: class extends Readable {
|
|
34
|
-
constructor( body ) {
|
|
35
|
-
super();
|
|
36
|
-
this.body = body;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
_read() {
|
|
40
|
-
this.push( JSON.stringify( this.body ) );
|
|
41
|
-
this.push( null );
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
} );
|
|
46
|
-
|
|
47
|
-
const streamToString = async stream => {
|
|
48
|
-
const chunks = [];
|
|
49
|
-
for await ( const chunk of stream ) {
|
|
50
|
-
chunks.push( Buffer.isBuffer( chunk ) ? chunk : Buffer.from( chunk ) );
|
|
51
|
-
}
|
|
52
|
-
return Buffer.concat( chunks ).toString( 'utf8' );
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
describe( 'tracing/processors/s3', () => {
|
|
56
|
-
const startTime = Date.parse( '2020-01-02T03:04:05.678Z' );
|
|
57
|
-
const traceInfo = {
|
|
58
|
-
workflowId: 'id1',
|
|
59
|
-
runId: 'run-1',
|
|
60
|
-
workflowType: 'WF',
|
|
61
|
-
startTime
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
beforeEach( () => {
|
|
65
|
-
vi.useFakeTimers();
|
|
66
|
-
vi.clearAllMocks();
|
|
67
|
-
getVarsMock.mockReturnValue( { remoteS3Bucket: 'bkt', redisIncompleteWorkflowsTTL: 3600, traceUploadDelayMs: 10_000 } );
|
|
68
|
-
} );
|
|
69
|
-
|
|
70
|
-
afterEach( () => {
|
|
71
|
-
vi.useRealTimers();
|
|
72
|
-
} );
|
|
73
|
-
|
|
74
|
-
it( 'init(): loads config and ensures redis client is created', async () => {
|
|
75
|
-
const { init } = await import( './index.js' );
|
|
76
|
-
await init();
|
|
77
|
-
expect( loadEnvMock ).toHaveBeenCalledTimes( 1 );
|
|
78
|
-
expect( getRedisClientMock ).toHaveBeenCalledTimes( 1 );
|
|
79
|
-
} );
|
|
80
|
-
|
|
81
|
-
it( 'exec(): accumulates via redis, uploads only on root workflow end', async () => {
|
|
82
|
-
const { exec } = await import( './index.js' );
|
|
83
|
-
|
|
84
|
-
redisMulti.exec.mockResolvedValue( [] );
|
|
85
|
-
|
|
86
|
-
const workflowStart = { id: 'run-1', name: 'WF', kind: 'workflow', action: 'start', details: {}, timestamp: startTime };
|
|
87
|
-
const activityStart = {
|
|
88
|
-
id: 'act-1',
|
|
89
|
-
name: 'DoSomething',
|
|
90
|
-
kind: 'step',
|
|
91
|
-
parentId: 'run-1',
|
|
92
|
-
action: 'start',
|
|
93
|
-
details: {},
|
|
94
|
-
timestamp: startTime + 1
|
|
95
|
-
};
|
|
96
|
-
const workflowEnd = { id: 'run-1', action: 'end', details: { ok: true }, timestamp: startTime + 2 };
|
|
97
|
-
zRangeMock.mockResolvedValue( [
|
|
98
|
-
JSON.stringify( workflowStart ),
|
|
99
|
-
JSON.stringify( activityStart ),
|
|
100
|
-
JSON.stringify( workflowEnd )
|
|
101
|
-
] );
|
|
102
|
-
|
|
103
|
-
await exec( { traceInfo, entry: workflowStart } );
|
|
104
|
-
await exec( { traceInfo, entry: activityStart } );
|
|
105
|
-
// Root end: id matches runId and not start — triggers the 10s delay before upload
|
|
106
|
-
const endPromise = exec( { traceInfo, entry: workflowEnd } );
|
|
107
|
-
await vi.advanceTimersByTimeAsync( 10_000 );
|
|
108
|
-
await endPromise;
|
|
109
|
-
|
|
110
|
-
expect( redisMulti.zAdd ).toHaveBeenCalledTimes( 3 );
|
|
111
|
-
expect( buildTraceTreeMock ).toHaveBeenCalledTimes( 1 );
|
|
112
|
-
expect( zRangeMock ).toHaveBeenCalledTimes( 1 );
|
|
113
|
-
expect( uploadMock ).toHaveBeenCalledTimes( 1 );
|
|
114
|
-
const { key, content } = uploadMock.mock.calls[0][0];
|
|
115
|
-
expect( key ).toMatch( /^WF\/2020\/01\/02\// );
|
|
116
|
-
expect( JSON.parse( await streamToString( content ) ).count ).toBe( 3 );
|
|
117
|
-
expect( delMock ).toHaveBeenCalledTimes( 1 );
|
|
118
|
-
expect( delMock ).toHaveBeenCalledWith( 'traces/run-1' );
|
|
119
|
-
} );
|
|
120
|
-
|
|
121
|
-
it( 'getDestination(): returns S3 URL using bucket and key from getVars', async () => {
|
|
122
|
-
getVarsMock.mockReturnValue( { remoteS3Bucket: 'my-bucket', redisIncompleteWorkflowsTTL: 3600, traceUploadDelayMs: 10_000 } );
|
|
123
|
-
const { getDestination } = await import( './index.js' );
|
|
124
|
-
const url = getDestination( traceInfo );
|
|
125
|
-
expect( getVarsMock ).toHaveBeenCalled();
|
|
126
|
-
expect( url ).toBe(
|
|
127
|
-
'https://my-bucket.s3.amazonaws.com/WF/2020/01/02/2020-01-02-03-04-05-678Z_id1.json'
|
|
128
|
-
);
|
|
129
|
-
} );
|
|
130
|
-
|
|
131
|
-
it( 'exec(): sets expiry on the redis key for each entry', async () => {
|
|
132
|
-
const { exec } = await import( './index.js' );
|
|
133
|
-
|
|
134
|
-
redisMulti.exec.mockResolvedValue( [] );
|
|
135
|
-
const workflowStart = {
|
|
136
|
-
kind: 'workflow', id: 'run-1', name: 'WF', parentId: undefined, action: 'start', details: {}, timestamp: startTime
|
|
137
|
-
};
|
|
138
|
-
zRangeMock.mockResolvedValue( [ JSON.stringify( workflowStart ) ] );
|
|
139
|
-
|
|
140
|
-
await exec( { traceInfo, entry: workflowStart } );
|
|
141
|
-
|
|
142
|
-
expect( redisMulti.expire ).toHaveBeenCalledTimes( 1 );
|
|
143
|
-
expect( redisMulti.expire ).toHaveBeenCalledWith( 'traces/run-1', 3600 );
|
|
144
|
-
} );
|
|
145
|
-
|
|
146
|
-
it( 'exec(): does not treat a non-root end (e.g. step without parentId) as root workflow end — regression for wrong root detection', async () => {
|
|
147
|
-
const { exec } = await import( './index.js' );
|
|
148
|
-
|
|
149
|
-
redisMulti.exec.mockResolvedValue( [] );
|
|
150
|
-
const workflowStart = { id: 'run-1', name: 'WF', kind: 'workflow', action: 'start', details: {}, timestamp: startTime };
|
|
151
|
-
const stepEndNoParent = { id: 'step-1', action: 'end', details: { done: true }, timestamp: startTime + 1 };
|
|
152
|
-
zRangeMock.mockResolvedValue( [
|
|
153
|
-
JSON.stringify( workflowStart ),
|
|
154
|
-
JSON.stringify( stepEndNoParent )
|
|
155
|
-
] );
|
|
156
|
-
|
|
157
|
-
await exec( { traceInfo, entry: workflowStart } );
|
|
158
|
-
await exec( { traceInfo, entry: stepEndNoParent } );
|
|
159
|
-
|
|
160
|
-
expect( redisMulti.zAdd ).toHaveBeenCalledTimes( 2 );
|
|
161
|
-
expect( buildTraceTreeMock ).not.toHaveBeenCalled();
|
|
162
|
-
expect( uploadMock ).not.toHaveBeenCalled();
|
|
163
|
-
expect( delMock ).not.toHaveBeenCalled();
|
|
164
|
-
} );
|
|
165
|
-
|
|
166
|
-
it( 'exec(): when buildTraceTree returns null (incomplete tree), does not upload or bust cache', async () => {
|
|
167
|
-
const { exec } = await import( './index.js' );
|
|
168
|
-
|
|
169
|
-
redisMulti.exec.mockResolvedValue( [] );
|
|
170
|
-
const workflowEnd = {
|
|
171
|
-
kind: 'workflow', id: 'run-1', name: 'WF', parentId: undefined, action: 'end', details: {}, timestamp: startTime
|
|
172
|
-
};
|
|
173
|
-
zRangeMock.mockResolvedValue( [ JSON.stringify( workflowEnd ) ] );
|
|
174
|
-
buildTraceTreeMock.mockReturnValueOnce( null );
|
|
175
|
-
|
|
176
|
-
const endPromise = exec( { traceInfo, entry: workflowEnd } );
|
|
177
|
-
await vi.advanceTimersByTimeAsync( 10_000 );
|
|
178
|
-
await endPromise;
|
|
179
|
-
|
|
180
|
-
expect( buildTraceTreeMock ).toHaveBeenCalledTimes( 1 );
|
|
181
|
-
expect( uploadMock ).not.toHaveBeenCalled();
|
|
182
|
-
expect( delMock ).not.toHaveBeenCalled();
|
|
183
|
-
} );
|
|
184
|
-
} );
|
|
185
|
-
|