@output.ai/core 0.5.1 → 0.5.2

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.
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ const mockLog = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
4
+ vi.mock( '#logger', () => ( { createChildLogger: () => mockLog } ) );
5
+
6
+ vi.mock( '#consts', () => ( { WORKFLOW_CATALOG: 'catalog' } ) );
7
+
8
+ vi.mock( '#tracing', () => ( { init: vi.fn().mockResolvedValue( undefined ) } ) );
9
+
10
+ const configValues = {
11
+ address: 'localhost:7233',
12
+ apiKey: undefined,
13
+ namespace: 'default',
14
+ taskQueue: 'test-queue',
15
+ catalogId: 'test-catalog',
16
+ maxConcurrentWorkflowTaskExecutions: 200,
17
+ maxConcurrentActivityTaskExecutions: 40,
18
+ maxCachedWorkflows: 1000,
19
+ maxConcurrentActivityTaskPolls: 5,
20
+ maxConcurrentWorkflowTaskPolls: 5
21
+ };
22
+ vi.mock( './configs.js', () => configValues );
23
+
24
+ const loadWorkflowsMock = vi.fn().mockResolvedValue( [] );
25
+ const loadActivitiesMock = vi.fn().mockResolvedValue( {} );
26
+ const createWorkflowsEntryPointMock = vi.fn().mockReturnValue( '/fake/workflows/path.js' );
27
+ vi.mock( './loader.js', () => ( {
28
+ loadWorkflows: loadWorkflowsMock,
29
+ loadActivities: loadActivitiesMock,
30
+ createWorkflowsEntryPoint: createWorkflowsEntryPointMock
31
+ } ) );
32
+
33
+ vi.mock( './sinks.js', () => ( { sinks: {} } ) );
34
+
35
+ const createCatalogMock = vi.fn().mockReturnValue( { workflows: [], activities: {} } );
36
+ vi.mock( './catalog_workflow/index.js', () => ( { createCatalog: createCatalogMock } ) );
37
+
38
+ vi.mock( './bundler_options.js', () => ( { webpackConfigHook: vi.fn() } ) );
39
+
40
+ const initInterceptorsMock = vi.fn().mockReturnValue( [] );
41
+ vi.mock( './interceptors.js', () => ( { initInterceptors: initInterceptorsMock } ) );
42
+
43
+ const runState = { resolve: null };
44
+ const runPromise = new Promise( r => {
45
+ runState.resolve = r;
46
+ } );
47
+ const shutdownMock = vi.fn();
48
+ const mockConnection = { close: vi.fn().mockResolvedValue( undefined ) };
49
+ const mockWorker = { run: () => runPromise, shutdown: shutdownMock };
50
+
51
+ vi.mock( '@temporalio/worker', () => ( {
52
+ Worker: { create: vi.fn().mockResolvedValue( mockWorker ) },
53
+ NativeConnection: { connect: vi.fn().mockResolvedValue( mockConnection ) }
54
+ } ) );
55
+
56
+ const workflowStartMock = vi.fn().mockResolvedValue( undefined );
57
+ vi.mock( '@temporalio/client', () => ( {
58
+ Client: vi.fn().mockImplementation( () => ( {
59
+ workflow: { start: workflowStartMock }
60
+ } ) )
61
+ } ) );
62
+
63
+ vi.mock( '@temporalio/common', () => ( {
64
+ WorkflowIdConflictPolicy: { TERMINATE_EXISTING: 'TERMINATE_EXISTING' }
65
+ } ) );
66
+
67
+ describe( 'worker/index', () => {
68
+ const exitMock = vi.fn();
69
+ const originalArgv = process.argv;
70
+ const originalExit = process.exit;
71
+ const originalOn = process.on;
72
+
73
+ beforeEach( () => {
74
+ vi.clearAllMocks();
75
+ process.argv = [ ...originalArgv.slice( 0, 2 ), '/test/caller/dir' ];
76
+ process.exit = exitMock;
77
+ } );
78
+
79
+ afterEach( () => {
80
+ process.argv = originalArgv;
81
+ process.exit = originalExit;
82
+ process.on = originalOn;
83
+ configValues.apiKey = undefined;
84
+ } );
85
+
86
+ it( 'loads configs, workflows, activities and creates worker with correct options', async () => {
87
+ const { Worker, NativeConnection } = await import( '@temporalio/worker' );
88
+ const { Client } = await import( '@temporalio/client' );
89
+ const { init: initTracing } = await import( '#tracing' );
90
+
91
+ import( './index.js' );
92
+
93
+ await vi.waitFor( () => {
94
+ expect( loadWorkflowsMock ).toHaveBeenCalledWith( '/test/caller/dir' );
95
+ } );
96
+ expect( loadActivitiesMock ).toHaveBeenCalledWith( '/test/caller/dir', [] );
97
+ expect( createWorkflowsEntryPointMock ).toHaveBeenCalledWith( [] );
98
+ expect( initTracing ).toHaveBeenCalled();
99
+ expect( createCatalogMock ).toHaveBeenCalledWith( { workflows: [], activities: {} } );
100
+ expect( NativeConnection.connect ).toHaveBeenCalledWith( {
101
+ address: configValues.address,
102
+ tls: false,
103
+ apiKey: undefined
104
+ } );
105
+ expect( Worker.create ).toHaveBeenCalledWith( expect.objectContaining( {
106
+ namespace: configValues.namespace,
107
+ taskQueue: configValues.taskQueue,
108
+ workflowsPath: '/fake/workflows/path.js',
109
+ activities: {},
110
+ maxConcurrentWorkflowTaskExecutions: configValues.maxConcurrentWorkflowTaskExecutions,
111
+ maxConcurrentActivityTaskExecutions: configValues.maxConcurrentActivityTaskExecutions,
112
+ maxCachedWorkflows: configValues.maxCachedWorkflows,
113
+ maxConcurrentActivityTaskPolls: configValues.maxConcurrentActivityTaskPolls,
114
+ maxConcurrentWorkflowTaskPolls: configValues.maxConcurrentWorkflowTaskPolls
115
+ } ) );
116
+ expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {} } );
117
+ expect( Client ).toHaveBeenCalledWith( { connection: mockConnection, namespace: configValues.namespace } );
118
+ expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', {
119
+ taskQueue: configValues.taskQueue,
120
+ workflowId: configValues.catalogId,
121
+ workflowIdConflictPolicy: 'TERMINATE_EXISTING',
122
+ args: [ { workflows: [], activities: {} } ]
123
+ } );
124
+
125
+ runState.resolve();
126
+ await vi.waitFor( () => {
127
+ expect( mockConnection.close ).toHaveBeenCalled();
128
+ } );
129
+ expect( exitMock ).toHaveBeenCalledWith( 0 );
130
+ } );
131
+
132
+ it( 'enables TLS when apiKey is set', async () => {
133
+ configValues.apiKey = 'secret';
134
+ vi.resetModules();
135
+
136
+ const { NativeConnection } = await import( '@temporalio/worker' );
137
+ import( './index.js' );
138
+
139
+ await vi.waitFor( () => {
140
+ expect( NativeConnection.connect ).toHaveBeenCalledWith( expect.objectContaining( {
141
+ tls: true,
142
+ apiKey: 'secret'
143
+ } ) );
144
+ } );
145
+ await vi.waitFor( () => expect( exitMock ).toHaveBeenCalled() );
146
+ } );
147
+
148
+ it( 'registers SIGTERM and SIGINT handlers that shut down worker', async () => {
149
+ const onMock = vi.fn();
150
+ process.on = onMock;
151
+ vi.resetModules();
152
+
153
+ import( './index.js' );
154
+
155
+ await vi.waitFor( () => {
156
+ expect( onMock ).toHaveBeenCalledWith( 'SIGTERM', expect.any( Function ) );
157
+ expect( onMock ).toHaveBeenCalledWith( 'SIGINT', expect.any( Function ) );
158
+ } );
159
+
160
+ const sigtermHandler = onMock.mock.calls.find( c => c[0] === 'SIGTERM' )?.[1];
161
+ expect( sigtermHandler ).toBeDefined();
162
+ sigtermHandler();
163
+ expect( shutdownMock ).toHaveBeenCalled();
164
+
165
+ runState.resolve();
166
+ await vi.waitFor( () => expect( exitMock ).toHaveBeenCalled() );
167
+ } );
168
+
169
+ it( 'calls process.exit(1) on fatal error', async () => {
170
+ loadWorkflowsMock.mockRejectedValueOnce( new Error( 'load failed' ) );
171
+ vi.resetModules();
172
+
173
+ import( './index.js' );
174
+
175
+ await vi.waitFor( () => {
176
+ expect( mockLog.error ).toHaveBeenCalledWith( 'Fatal error', expect.any( Object ) );
177
+ } );
178
+ expect( exitMock ).toHaveBeenCalledWith( 1 );
179
+ } );
180
+ } );