@temporalio/testing 0.23.3 → 1.0.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/LICENSE.md +1 -1
- package/README.md +9 -0
- package/lib/child-process.d.ts +2 -0
- package/lib/child-process.js +12 -1
- package/lib/child-process.js.map +1 -1
- package/lib/index-for-docs.d.ts +1 -0
- package/lib/index-for-docs.js +29 -0
- package/lib/index-for-docs.js.map +1 -0
- package/lib/index.d.ts +15 -3
- package/lib/index.js +24 -9
- package/lib/index.js.map +1 -1
- package/lib/test-service-client.d.ts +9 -1
- package/lib/test-service-client.js +21 -2
- package/lib/test-service-client.js.map +1 -1
- package/lib/utils.js +5 -2
- package/lib/utils.js.map +1 -1
- package/package.json +12 -11
- package/scripts/compile-proto.mjs +10 -17
- package/src/assert-to-failure-interceptor.ts +38 -0
- package/src/child-process.ts +60 -0
- package/src/index-for-docs.ts +2 -0
- package/src/index.ts +358 -0
- package/src/test-service-client.ts +47 -0
- package/src/utils.ts +52 -0
package/LICENSE.md
CHANGED
|
@@ -2,7 +2,7 @@ Temporal TypeScript SDK
|
|
|
2
2
|
|
|
3
3
|
MIT License
|
|
4
4
|
|
|
5
|
-
Copyright (c) 2021 Temporal Technologies
|
|
5
|
+
Copyright (c) 2021 Temporal Technologies Inc. All Rights Reserved
|
|
6
6
|
|
|
7
7
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
8
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# `@temporalio/testing`
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@temporalio/testing)
|
|
4
|
+
|
|
5
|
+
Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/).
|
|
6
|
+
|
|
7
|
+
- [Testing docs](https://docs.temporal.io/typescript/testing)
|
|
8
|
+
- [API reference](https://typescript.temporal.io/api/namespaces/testing)
|
|
9
|
+
- [Sample projects](https://github.com/temporalio/samples-typescript)
|
package/lib/child-process.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
2
3
|
import { ChildProcess } from 'child_process';
|
|
3
4
|
export declare class ChildProcessError extends Error {
|
|
4
5
|
readonly code: number | null;
|
|
@@ -11,4 +12,5 @@ export interface WaitOptions {
|
|
|
11
12
|
}
|
|
12
13
|
export declare function waitOnChild(child: ChildProcess, opts?: WaitOptions): Promise<void>;
|
|
13
14
|
export declare function kill(child: ChildProcess, signal?: NodeJS.Signals, opts?: WaitOptions): Promise<void>;
|
|
15
|
+
export declare function killIfExists(child: ChildProcess, signal?: NodeJS.Signals, opts?: WaitOptions): Promise<void>;
|
|
14
16
|
export declare const shell: boolean;
|
package/lib/child-process.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.shell = exports.kill = exports.waitOnChild = exports.ChildProcessError = void 0;
|
|
3
|
+
exports.shell = exports.killIfExists = exports.kill = exports.waitOnChild = exports.ChildProcessError = void 0;
|
|
4
4
|
class ChildProcessError extends Error {
|
|
5
5
|
constructor(message, code, signal) {
|
|
6
6
|
super(message);
|
|
@@ -42,5 +42,16 @@ async function kill(child, signal = 'SIGINT', opts) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
exports.kill = kill;
|
|
45
|
+
async function killIfExists(child, signal = 'SIGINT', opts) {
|
|
46
|
+
try {
|
|
47
|
+
await kill(child, signal, opts);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err.code !== 'ESRCH') {
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.killIfExists = killIfExists;
|
|
45
56
|
exports.shell = process.platform === 'win32';
|
|
46
57
|
//# sourceMappingURL=child-process.js.map
|
package/lib/child-process.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"child-process.js","sourceRoot":"","sources":["../src/child-process.ts"],"names":[],"mappings":";;;AAGA,MAAa,iBAAkB,SAAQ,KAAK;IAG1C,YAAY,OAAe,EAAkB,IAAmB,EAAkB,MAA6B;QAC7G,KAAK,CAAC,OAAO,CAAC,CAAC;QAD4B,SAAI,GAAJ,IAAI,CAAe;QAAkB,WAAM,GAAN,MAAM,CAAuB;QAF/F,SAAI,GAAG,mBAAmB,CAAC;IAI3C,CAAC;CACF;AAND,8CAMC;AAMM,KAAK,UAAU,WAAW,CAAC,KAAmB,EAAE,IAAkB;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAChC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,gBAAgB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;gBACnE,OAAO,EAAE,CAAC;aACX;iBAAM;gBACL,MAAM,CAAC,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;aAC/D;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAXD,kCAWC;AAEM,KAAK,UAAU,IAAI,CAAC,KAAmB,EAAE,SAAyB,QAAQ,EAAE,IAAkB;IACnG,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE;QAC3B,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;KAChD;IACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI;QACF,MAAM,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;KAChC;IAAC,OAAO,GAAG,EAAE;QACZ,0EAA0E;QAC1E,oEAAoE;QACpE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,YAAY,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QACnH,IAAI,WAAW,EAAE;YACf,MAAM,GAAG,CAAC;SACX;KACF;AACH,CAAC;AAfD,oBAeC;AAEY,QAAA,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC"}
|
|
1
|
+
{"version":3,"file":"child-process.js","sourceRoot":"","sources":["../src/child-process.ts"],"names":[],"mappings":";;;AAGA,MAAa,iBAAkB,SAAQ,KAAK;IAG1C,YAAY,OAAe,EAAkB,IAAmB,EAAkB,MAA6B;QAC7G,KAAK,CAAC,OAAO,CAAC,CAAC;QAD4B,SAAI,GAAJ,IAAI,CAAe;QAAkB,WAAM,GAAN,MAAM,CAAuB;QAF/F,SAAI,GAAG,mBAAmB,CAAC;IAI3C,CAAC;CACF;AAND,8CAMC;AAMM,KAAK,UAAU,WAAW,CAAC,KAAmB,EAAE,IAAkB;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAChC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,gBAAgB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;gBACnE,OAAO,EAAE,CAAC;aACX;iBAAM;gBACL,MAAM,CAAC,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;aAC/D;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAXD,kCAWC;AAEM,KAAK,UAAU,IAAI,CAAC,KAAmB,EAAE,SAAyB,QAAQ,EAAE,IAAkB;IACnG,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE;QAC3B,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;KAChD;IACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI;QACF,MAAM,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;KAChC;IAAC,OAAO,GAAG,EAAE;QACZ,0EAA0E;QAC1E,oEAAoE;QACpE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,YAAY,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QACnH,IAAI,WAAW,EAAE;YACf,MAAM,GAAG,CAAC;SACX;KACF;AACH,CAAC;AAfD,oBAeC;AAEM,KAAK,UAAU,YAAY,CAChC,KAAmB,EACnB,SAAyB,QAAQ,EACjC,IAAkB;IAElB,IAAI;QACF,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;KACjC;IAAC,OAAO,GAAQ,EAAE;QACjB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE;YACxB,MAAM,GAAG,CAAC;SACX;KACF;AACH,CAAC;AAZD,oCAYC;AAEY,QAAA,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as testing from './index';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.testing = void 0;
|
|
27
|
+
// Reexports the contents of this package under the "testing" namespace for better docs generation
|
|
28
|
+
exports.testing = __importStar(require("./index"));
|
|
29
|
+
//# sourceMappingURL=index-for-docs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-for-docs.js","sourceRoot":"","sources":["../src/index-for-docs.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kGAAkG;AAClG,mDAAmC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npm i @temporalio/testing`
|
|
3
|
+
*
|
|
4
|
+
* Testing library for the SDK.
|
|
5
|
+
*
|
|
6
|
+
* [Documentation](https://docs.temporal.io/typescript/testing)
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/// <reference types="node" />
|
|
1
11
|
/// <reference types="node" />
|
|
2
12
|
import * as activity from '@temporalio/activity';
|
|
3
|
-
import { AsyncCompletionClient, WorkflowClient as BaseWorkflowClient, WorkflowClientOptions, WorkflowResultOptions as BaseWorkflowResultOptions, WorkflowStartOptions as BaseWorkflowStartOptions } from '@temporalio/client';
|
|
13
|
+
import { AsyncCompletionClient, WorkflowClient as BaseWorkflowClient, WorkflowClientOptions as BaseWorkflowClientOptions, WorkflowResultOptions as BaseWorkflowResultOptions, WorkflowStartOptions as BaseWorkflowStartOptions } from '@temporalio/client';
|
|
4
14
|
import { ActivityFunction, Workflow, WorkflowResultType } from '@temporalio/common';
|
|
5
15
|
import { NativeConnection, Logger } from '@temporalio/worker';
|
|
6
16
|
import { ChildProcess, StdioOptions } from 'child_process';
|
|
7
17
|
import events from 'events';
|
|
8
18
|
import { Connection, TestService } from './test-service-client';
|
|
9
|
-
import { temporal } from '@temporalio/proto';
|
|
10
19
|
export declare const DEFAULT_TEST_SERVER_PATH: string;
|
|
11
20
|
/**
|
|
12
21
|
* Options passed to {@link WorkflowClient.result}, these are the same as the
|
|
@@ -32,6 +41,9 @@ export declare type WorkflowStartOptions<T extends Workflow> = BaseWorkflowStart
|
|
|
32
41
|
*/
|
|
33
42
|
runInNormalTime?: boolean;
|
|
34
43
|
};
|
|
44
|
+
export interface WorkflowClientOptions extends BaseWorkflowClientOptions {
|
|
45
|
+
connection: Connection;
|
|
46
|
+
}
|
|
35
47
|
/**
|
|
36
48
|
* A client with the exact same API as the "normal" client with 1 exception,
|
|
37
49
|
* When this client waits on a Workflow's result, it will enable time skipping
|
|
@@ -39,7 +51,7 @@ export declare type WorkflowStartOptions<T extends Workflow> = BaseWorkflowStart
|
|
|
39
51
|
*/
|
|
40
52
|
export declare class WorkflowClient extends BaseWorkflowClient {
|
|
41
53
|
protected readonly testService: TestService;
|
|
42
|
-
constructor(
|
|
54
|
+
constructor(options: WorkflowClientOptions);
|
|
43
55
|
/**
|
|
44
56
|
* Execute a Workflow and wait for completion.
|
|
45
57
|
*
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `npm i @temporalio/testing`
|
|
4
|
+
*
|
|
5
|
+
* Testing library for the SDK.
|
|
6
|
+
*
|
|
7
|
+
* [Documentation](https://docs.temporal.io/typescript/testing)
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
2
11
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
12
|
if (k2 === undefined) k2 = k;
|
|
4
13
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -46,9 +55,9 @@ exports.DEFAULT_TEST_SERVER_PATH = path_1.default.join(__dirname, `../${TEST_SER
|
|
|
46
55
|
* in the test server.
|
|
47
56
|
*/
|
|
48
57
|
class WorkflowClient extends client_1.WorkflowClient {
|
|
49
|
-
constructor(
|
|
50
|
-
super(
|
|
51
|
-
this.testService = testService;
|
|
58
|
+
constructor(options) {
|
|
59
|
+
super(options);
|
|
60
|
+
this.testService = options.connection.testService;
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
54
63
|
* Execute a Workflow and wait for completion.
|
|
@@ -151,8 +160,8 @@ class TestWorkflowEnvironment {
|
|
|
151
160
|
};
|
|
152
161
|
this.connection = connection;
|
|
153
162
|
this.nativeConnection = nativeConnection;
|
|
154
|
-
this.workflowClient = new WorkflowClient(
|
|
155
|
-
this.asyncCompletionClient = new client_1.AsyncCompletionClient(
|
|
163
|
+
this.workflowClient = new WorkflowClient({ connection });
|
|
164
|
+
this.asyncCompletionClient = new client_1.AsyncCompletionClient({ connection });
|
|
156
165
|
}
|
|
157
166
|
/**
|
|
158
167
|
* Create a new test environment
|
|
@@ -164,10 +173,10 @@ class TestWorkflowEnvironment {
|
|
|
164
173
|
const { testServerSpawner, logger } = addDefaults(opts ?? {});
|
|
165
174
|
const child = testServerSpawner(port);
|
|
166
175
|
const address = `127.0.0.1:${port}`;
|
|
167
|
-
const
|
|
176
|
+
const connPromise = test_service_client_1.Connection.connect({ address });
|
|
168
177
|
try {
|
|
169
178
|
await Promise.race([
|
|
170
|
-
|
|
179
|
+
connPromise,
|
|
171
180
|
(0, child_process_2.waitOnChild)(child).then(() => {
|
|
172
181
|
throw new Error('Test server child process exited prematurely');
|
|
173
182
|
}),
|
|
@@ -182,14 +191,16 @@ class TestWorkflowEnvironment {
|
|
|
182
191
|
}
|
|
183
192
|
throw err;
|
|
184
193
|
}
|
|
185
|
-
const
|
|
194
|
+
const conn = await connPromise;
|
|
195
|
+
const nativeConnection = await worker_1.NativeConnection.connect({ address });
|
|
186
196
|
return new this(child, conn, nativeConnection);
|
|
187
197
|
}
|
|
188
198
|
/**
|
|
189
199
|
* Kill the test server process and close the connection to it
|
|
190
200
|
*/
|
|
191
201
|
async teardown() {
|
|
192
|
-
this.connection.
|
|
202
|
+
await this.connection.close();
|
|
203
|
+
await this.nativeConnection.close();
|
|
193
204
|
// TODO: the server should return exit code 0
|
|
194
205
|
await (0, child_process_2.kill)(this.serverProc, 'SIGINT', { validReturnCodes: [0, 130] });
|
|
195
206
|
}
|
|
@@ -200,6 +211,7 @@ exports.TestWorkflowEnvironment = TestWorkflowEnvironment;
|
|
|
200
211
|
*/
|
|
201
212
|
exports.defaultActivityInfo = {
|
|
202
213
|
attempt: 1,
|
|
214
|
+
taskQueue: 'test',
|
|
203
215
|
isLocal: false,
|
|
204
216
|
taskToken: Buffer.from('test'),
|
|
205
217
|
activityId: 'test',
|
|
@@ -233,6 +245,9 @@ class MockActivityEnvironment extends events_1.default.EventEmitter {
|
|
|
233
245
|
});
|
|
234
246
|
const heartbeatCallback = (details) => this.emit('heartbeat', details);
|
|
235
247
|
this.context = new activity.Context({ ...exports.defaultActivityInfo, ...info }, promise, abortController.signal, heartbeatCallback);
|
|
248
|
+
promise.catch(() => {
|
|
249
|
+
/* avoid unhandled rejection */
|
|
250
|
+
});
|
|
236
251
|
}
|
|
237
252
|
/**
|
|
238
253
|
* Run a function in Activity Context
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,+DAAiD;AACjD,+CAM4B;AAC5B,+CAA8G;AAC9G,+CAA6E;AAC7E,gDAAwB;AACxB,4CAAoB;AACpB,uDAAmD;AACnD,iDAAkE;AAClE,oDAA4B;AAC5B,mDAAoD;AAEpD,+DAAgE;AAEhE,MAAM,2BAA2B,GAAG,YAAE,CAAC,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC;AAErF,QAAA,wBAAwB,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,2BAA2B,EAAE,CAAC,CAAC;AAgClG;;;;GAIG;AACH,MAAa,cAAe,SAAQ,uBAAkB;IAGpD,YAAY,OAA8B;QACxC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,OAAO,CAClB,kBAA8B,EAC9B,OAAgC;QAEhC,OAAO,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACM,KAAK,CAAC,MAAM,CACnB,UAAkB,EAClB,KAA0B,EAC1B,IAAwC;QAExC,IAAI,IAAI,EAAE,eAAe,EAAE;YACzB,OAAO,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;SACpD;QACD,MAAM,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QAC9C,IAAI;YACF,OAAO,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;SACpD;gBAAS;YACR,MAAM,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;SAC7C;IACH,CAAC;CACF;AAxCD,wCAwCC;AAED;;;;;GAKG;AACU,QAAA,0BAA0B,GAAG,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,+BAA+B,CAAC,CAAC,CAAC;AA+BlG,SAAS,WAAW,CAAC,EACnB,UAAU,EACV,MAAM,GACyB;IAC/B,OAAO;QACL,iBAAiB,EACf,OAAO,UAAU,KAAK,UAAU;YAC9B,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,CAAC,IAAY,EAAE,EAAE,CACf,IAAA,qBAAK,EAAC,UAAU,EAAE,IAAI,IAAI,gCAAwB,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE;gBAC/D,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,QAAQ;aACrC,CAAC;QACV,MAAM,EAAE,MAAM,IAAI,IAAI,sBAAa,CAAC,MAAM,CAAC;KAC5C,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,sCAAsC;AACtC,MAAM,cAAc,GAAG,IAAI,QAAQ,CAAC,YAAY,EAAE,2BAA2B,CAAC,CAAC;AAE/E;;;;;GAKG;AACH,MAAa,uBAAuB;IAuBlC,YACqB,UAAwB,EAC3C,UAAsB,EACtB,gBAAkC;QAFf,eAAU,GAAV,UAAU,CAAc;QAyD7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAuCG;QACH,UAAK,GAAG,KAAK,EAAE,UAA2B,EAAiB,EAAE;YAC3D,MAAM,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,2BAA2B,CAAC,EAAE,QAAQ,EAAE,IAAA,eAAM,EAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAClG,CAAC,CAAC;QA/FA,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,qBAAqB,GAAG,IAAI,8BAAqB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IACzE,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAqC;QACvD,gEAAgE;QAChE,MAAM,OAAO,GAAG,CAAC,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,OAA6B,CAAC;QACjF,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAC;QAE7B,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAE9D,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAEtC,MAAM,OAAO,GAAG,aAAa,IAAI,EAAE,CAAC;QACpC,MAAM,WAAW,GAAG,gCAAU,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAEpD,IAAI;YACF,MAAM,OAAO,CAAC,IAAI,CAAC;gBACjB,WAAW;gBACX,IAAA,2BAAW,EAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;oBAC3B,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;gBAClE,CAAC,CAAC;aACH,CAAC,CAAC;SACJ;QAAC,OAAO,GAAG,EAAE;YACZ,IAAI;gBACF,MAAM,IAAA,oBAAI,EAAC,KAAK,CAAC,CAAC;aACnB;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,KAAK,CAAC,0CAA0C,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;aACrE;YACD,MAAM,GAAG,CAAC;SACX;QAED,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC;QAC/B,MAAM,gBAAgB,GAAG,MAAM,yBAAgB,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAErE,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACjD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QACpC,6CAA6C;QAC7C,MAAM,IAAA,oBAAI,EAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACxE,CAAC;CA6CF;AA5HD,0DA4HC;AAED;;GAEG;AACU,QAAA,mBAAmB,GAAkB;IAChD,OAAO,EAAE,CAAC;IACV,SAAS,EAAE,MAAM;IACjB,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;IAC9B,UAAU,EAAE,MAAM;IAClB,YAAY,EAAE,SAAS;IACvB,YAAY,EAAE,MAAM;IACpB,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACvD,gBAAgB,EAAE,SAAS;IAC3B,iBAAiB,EAAE,SAAS;IAC5B,iBAAiB,EAAE,SAAS;IAC5B,iBAAiB,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE;IAC7D,oBAAoB,EAAE,CAAC;IACvB,qBAAqB,EAAE,IAAI;IAC3B,wBAAwB,EAAE,IAAI;CAC/B,CAAC;AAEF;;;;;GAKG;AACH,MAAa,uBAAwB,SAAQ,gBAAM,CAAC,YAAY;IAI9D,YAAY,IAA6B;QACvC,KAAK,EAAE,CAAC;QAJH,WAAM,GAA2B,GAAG,EAAE,CAAC,SAAS,CAAC;QAKtD,MAAM,eAAe,GAAG,IAAI,kCAAe,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;YAC/C,IAAI,CAAC,MAAM,GAAG,CAAC,MAAY,EAAE,EAAE;gBAC7B,eAAe,CAAC,KAAK,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,yBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;YACvC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,MAAM,iBAAiB,GAAG,CAAC,OAAiB,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACjF,IAAI,CAAC,OAAO,GAAG,IAAI,QAAQ,CAAC,OAAO,CACjC,EAAE,GAAG,2BAAmB,EAAE,GAAG,IAAI,EAAE,EACnC,OAAO,EACP,eAAe,CAAC,MAAM,EACtB,iBAAiB,CAClB,CAAC;QACF,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;YACjB,+BAA+B;QACjC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACI,GAAG,CAAuD,EAAK,EAAE,GAAG,IAAO;QAChF,OAAO,QAAQ,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;IACnE,CAAC;CACF;AA/BD,0DA+BC"}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import * as grpc from '@grpc/grpc-js';
|
|
2
2
|
import { Connection as BaseConnection, ConnectionOptions } from '@temporalio/client';
|
|
3
|
+
import { ConnectionCtorOptions as BaseConnectionCtorOptions } from '@temporalio/client/lib/connection';
|
|
3
4
|
import { temporal } from '../generated-protos';
|
|
4
5
|
export declare type TestService = temporal.api.testservice.v1.TestService;
|
|
5
6
|
export declare const TestService: typeof temporal.api.testservice.v1.TestService;
|
|
7
|
+
interface ConnectionCtorOptions extends BaseConnectionCtorOptions {
|
|
8
|
+
testService: TestService;
|
|
9
|
+
}
|
|
6
10
|
/**
|
|
7
11
|
* A Connection class that can be used to interact with both the test server's TestService and WorkflowService
|
|
8
12
|
*/
|
|
9
13
|
export declare class Connection extends BaseConnection {
|
|
10
14
|
static readonly TestServiceClient: grpc.ServiceClientConstructor;
|
|
11
15
|
readonly testService: TestService;
|
|
12
|
-
|
|
16
|
+
protected static createCtorOptions(options?: ConnectionOptions): ConnectionCtorOptions;
|
|
17
|
+
static lazy(options?: ConnectionOptions): Connection;
|
|
18
|
+
static connect(options?: ConnectionOptions): Promise<Connection>;
|
|
19
|
+
protected constructor(options: ConnectionCtorOptions);
|
|
13
20
|
}
|
|
21
|
+
export {};
|
|
@@ -34,8 +34,27 @@ exports.TestService = generated_protos_1.temporal.api.testservice.v1.TestService
|
|
|
34
34
|
class Connection extends client_1.Connection {
|
|
35
35
|
constructor(options) {
|
|
36
36
|
super(options);
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
this.testService = options.testService;
|
|
38
|
+
}
|
|
39
|
+
static createCtorOptions(options) {
|
|
40
|
+
const ctorOptions = client_1.Connection.createCtorOptions(options);
|
|
41
|
+
const rpcImpl = this.generateRPCImplementation({
|
|
42
|
+
serviceName: 'temporal.api.testservice.v1.TestService',
|
|
43
|
+
client: ctorOptions.client,
|
|
44
|
+
callContextStorage: ctorOptions.callContextStorage,
|
|
45
|
+
interceptors: ctorOptions.options.interceptors,
|
|
46
|
+
});
|
|
47
|
+
const testService = exports.TestService.create(rpcImpl, false, false);
|
|
48
|
+
return { ...ctorOptions, testService };
|
|
49
|
+
}
|
|
50
|
+
static lazy(options) {
|
|
51
|
+
const ctorOptions = this.createCtorOptions(options);
|
|
52
|
+
return new this(ctorOptions);
|
|
53
|
+
}
|
|
54
|
+
static async connect(options) {
|
|
55
|
+
const ret = this.lazy(options);
|
|
56
|
+
await ret.ensureConnected();
|
|
57
|
+
return ret;
|
|
39
58
|
}
|
|
40
59
|
}
|
|
41
60
|
exports.Connection = Connection;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-service-client.js","sourceRoot":"","sources":["../src/test-service-client.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAsC;AACtC,+CAAqF;
|
|
1
|
+
{"version":3,"file":"test-service-client.js","sourceRoot":"","sources":["../src/test-service-client.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAsC;AACtC,+CAAqF;AAErF,0DAA+C;AAGhC,mBAAW,GAAK,2BAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,aAAC;AAM3D;;GAEG;AACH,MAAa,UAAW,SAAQ,mBAAc;IA2B5C,YAAsB,OAA8B;QAClD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;IA1BS,MAAM,CAAC,iBAAiB,CAAC,OAA2B;QAC5D,MAAM,WAAW,GAAG,mBAAc,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,yBAAyB,CAAC;YAC7C,WAAW,EAAE,yCAAyC;YACtD,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,kBAAkB,EAAE,WAAW,CAAC,kBAAkB;YAClD,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,YAAY;SAC/C,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,mBAAW,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAC9D,OAAO,EAAE,GAAG,WAAW,EAAE,WAAW,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAA2B;QACrC,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACpD,OAAO,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAA2B;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC,eAAe,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACb,CAAC;;AAzBH,gCA+BC;AA9BwB,4BAAiB,GAAG,IAAI,CAAC,4BAA4B,CAAC,EAAE,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC"}
|
package/lib/utils.js
CHANGED
|
@@ -6,7 +6,7 @@ async function waitOnNamespace(connection, namespace, maxAttempts = 100, retryIn
|
|
|
6
6
|
const runId = '12345678-dead-beef-1234-1234567890ab';
|
|
7
7
|
for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
|
|
8
8
|
try {
|
|
9
|
-
await connection.
|
|
9
|
+
await connection.workflowService.getWorkflowExecutionHistory({
|
|
10
10
|
namespace,
|
|
11
11
|
execution: { workflowId: 'fake', runId },
|
|
12
12
|
});
|
|
@@ -26,7 +26,10 @@ exports.waitOnNamespace = waitOnNamespace;
|
|
|
26
26
|
async function createNamespace(connection, namespace, maxAttempts = 100, retryIntervalSecs = 1) {
|
|
27
27
|
for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
|
|
28
28
|
try {
|
|
29
|
-
await connection.
|
|
29
|
+
await connection.workflowService.registerNamespace({
|
|
30
|
+
namespace,
|
|
31
|
+
workflowExecutionRetentionPeriod: (0, internal_workflow_common_1.msToTs)('1 day'),
|
|
32
|
+
});
|
|
30
33
|
break;
|
|
31
34
|
}
|
|
32
35
|
catch (err) {
|
package/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AACA,mFAA8D;AAEvD,KAAK,UAAU,eAAe,CACnC,UAAsB,EACtB,SAAiB,EACjB,WAAW,GAAG,GAAG,EACjB,iBAAiB,GAAG,CAAC;IAErB,MAAM,KAAK,GAAG,sCAAsC,CAAC;IACrD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE;QACvD,IAAI;YACF,MAAM,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AACA,mFAA8D;AAEvD,KAAK,UAAU,eAAe,CACnC,UAAsB,EACtB,SAAiB,EACjB,WAAW,GAAG,GAAG,EACjB,iBAAiB,GAAG,CAAC;IAErB,MAAM,KAAK,GAAG,sCAAsC,CAAC;IACrD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE;QACvD,IAAI;YACF,MAAM,UAAU,CAAC,eAAe,CAAC,2BAA2B,CAAC;gBAC3D,SAAS;gBACT,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE;aACzC,CAAC,CAAC;SACJ;QAAC,OAAO,GAAQ,EAAE;YACjB,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;gBACrF,MAAM;aACP;YACD,IAAI,OAAO,KAAK,WAAW,EAAE;gBAC3B,MAAM,GAAG,CAAC;aACX;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC,CAAC,CAAC;SAC/E;KACF;AACH,CAAC;AAvBD,0CAuBC;AAEM,KAAK,UAAU,eAAe,CACnC,UAAsB,EACtB,SAAiB,EACjB,WAAW,GAAG,GAAG,EACjB,iBAAiB,GAAG,CAAC;IAErB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE;QACvD,IAAI;YACF,MAAM,UAAU,CAAC,eAAe,CAAC,iBAAiB,CAAC;gBACjD,SAAS;gBACT,gCAAgC,EAAE,IAAA,iCAAM,EAAC,OAAO,CAAC;aAClD,CAAC,CAAC;YACH,MAAM;SACP;QAAC,OAAO,GAAQ,EAAE;YACjB,IAAI,GAAG,CAAC,OAAO,KAAK,2BAA2B,EAAE;gBAC/C,MAAM;aACP;YACD,IAAI,OAAO,KAAK,WAAW,EAAE;gBAC3B,MAAM,GAAG,CAAC;aACX;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC,CAAC,CAAC;SAC/E;KACF;AACH,CAAC;AAvBD,0CAuBC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporalio/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Temporal.io SDK Testing sub-package",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"author": "Temporal Technologies Inc. <sdk@temporal.io>",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@grpc/grpc-js": "^1.
|
|
21
|
-
"@temporalio/activity": "^0.
|
|
22
|
-
"@temporalio/client": "^0.
|
|
23
|
-
"@temporalio/common": "^0.
|
|
24
|
-
"@temporalio/worker": "^0.
|
|
25
|
-
"@types/long": "^4.0.
|
|
20
|
+
"@grpc/grpc-js": "^1.6.7",
|
|
21
|
+
"@temporalio/activity": "^1.0.0",
|
|
22
|
+
"@temporalio/client": "^1.0.0",
|
|
23
|
+
"@temporalio/common": "^1.0.0",
|
|
24
|
+
"@temporalio/worker": "^1.0.0",
|
|
25
|
+
"@types/long": "^4.0.2",
|
|
26
26
|
"abort-controller": "^3.0.0",
|
|
27
27
|
"get-port": "^6.1.2",
|
|
28
|
-
"got": "^12.0
|
|
29
|
-
"long": "^
|
|
30
|
-
"protobufjs": "
|
|
28
|
+
"got": "^12.1.0",
|
|
29
|
+
"long": "^5.2.0",
|
|
30
|
+
"protobufjs": "6.11.2",
|
|
31
31
|
"tar-stream": "^2.2.0",
|
|
32
32
|
"unzipper": "^0.10.11"
|
|
33
33
|
},
|
|
@@ -37,11 +37,12 @@
|
|
|
37
37
|
"homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/testing",
|
|
38
38
|
"files": [
|
|
39
39
|
"lib",
|
|
40
|
+
"src",
|
|
40
41
|
"generated-protos",
|
|
41
42
|
"scripts"
|
|
42
43
|
],
|
|
43
44
|
"publishConfig": {
|
|
44
45
|
"access": "public"
|
|
45
46
|
},
|
|
46
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "c4fc4dc608bf58701c11b6ae02d1d63b4457718d"
|
|
47
48
|
}
|
|
@@ -58,24 +58,17 @@ async function compileProtos(protoPath, jsOutputFile, dtsOutputFile, ...args) {
|
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
mkdirSync(outputDir, { recursive: true });
|
|
61
|
+
mkdirSync(outputDir, { recursive: true });
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const protoFiles = glob.sync(resolve(protoBaseDir, '**/*.proto'));
|
|
64
|
+
const protosMTime = Math.max(...protoFiles.map(mtime));
|
|
65
|
+
const genMTime = mtime(outputFile);
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
await compileProtos(serviceProtoPath, outputFile, resolve(outputDir, 'index.d.ts'), '--path', resolve(protoBaseDir));
|
|
74
|
-
|
|
75
|
-
console.log('Done');
|
|
67
|
+
if (protosMTime < genMTime) {
|
|
68
|
+
console.log('Assuming protos are up to date');
|
|
69
|
+
process.exit(0);
|
|
76
70
|
}
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
72
|
+
await compileProtos(serviceProtoPath, outputFile, resolve(outputDir, 'index.d.ts'), '--path', resolve(protoBaseDir));
|
|
73
|
+
|
|
74
|
+
console.log('Done');
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import { ApplicationFailure, WorkflowInterceptors } from '@temporalio/workflow';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Simple interceptor that transforms {@link assert.AssertionError} into non retryable failures.
|
|
6
|
+
*
|
|
7
|
+
* This allows conveniently using `assert` directly from Workflows.
|
|
8
|
+
*/
|
|
9
|
+
export function interceptors(): WorkflowInterceptors {
|
|
10
|
+
return {
|
|
11
|
+
inbound: [
|
|
12
|
+
{
|
|
13
|
+
async handleSignal(input, next) {
|
|
14
|
+
try {
|
|
15
|
+
return await next(input);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
if (err instanceof assert.AssertionError) {
|
|
18
|
+
const appErr = ApplicationFailure.nonRetryable(err.message);
|
|
19
|
+
appErr.stack = err.stack;
|
|
20
|
+
throw appErr;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
async execute(input, next) {
|
|
25
|
+
try {
|
|
26
|
+
return await next(input);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (err instanceof assert.AssertionError) {
|
|
29
|
+
const appErr = ApplicationFailure.nonRetryable(err.message);
|
|
30
|
+
appErr.stack = err.stack;
|
|
31
|
+
throw appErr;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// TODO: this code is duplicated in the scripts directory, consider moving to external dependency
|
|
2
|
+
import { ChildProcess } from 'child_process';
|
|
3
|
+
|
|
4
|
+
export class ChildProcessError extends Error {
|
|
5
|
+
public readonly name = 'ChildProcessError';
|
|
6
|
+
|
|
7
|
+
constructor(message: string, public readonly code: number | null, public readonly signal: NodeJS.Signals | null) {
|
|
8
|
+
super(message);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WaitOptions {
|
|
13
|
+
validReturnCodes: number[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function waitOnChild(child: ChildProcess, opts?: WaitOptions): Promise<void> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
child.on('exit', (code, signal) => {
|
|
19
|
+
if (code !== null && (opts?.validReturnCodes ?? [0]).includes(code)) {
|
|
20
|
+
resolve();
|
|
21
|
+
} else {
|
|
22
|
+
reject(new ChildProcessError('Process failed', code, signal));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
child.on('error', reject);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function kill(child: ChildProcess, signal: NodeJS.Signals = 'SIGINT', opts?: WaitOptions): Promise<void> {
|
|
30
|
+
if (child.pid === undefined) {
|
|
31
|
+
throw new TypeError('Expected child with pid');
|
|
32
|
+
}
|
|
33
|
+
process.kill(child.pid, signal);
|
|
34
|
+
try {
|
|
35
|
+
await waitOnChild(child, opts);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
// Should error if the error is not a child process error or it is a child
|
|
38
|
+
// process and either the platform is Windows or the signal matches.
|
|
39
|
+
const shouldError = !(err instanceof ChildProcessError) || (process.platform !== 'win32' && err.signal !== signal);
|
|
40
|
+
if (shouldError) {
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function killIfExists(
|
|
47
|
+
child: ChildProcess,
|
|
48
|
+
signal: NodeJS.Signals = 'SIGINT',
|
|
49
|
+
opts?: WaitOptions
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
try {
|
|
52
|
+
await kill(child, signal, opts);
|
|
53
|
+
} catch (err: any) {
|
|
54
|
+
if (err.code !== 'ESRCH') {
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const shell = process.platform === 'win32';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npm i @temporalio/testing`
|
|
3
|
+
*
|
|
4
|
+
* Testing library for the SDK.
|
|
5
|
+
*
|
|
6
|
+
* [Documentation](https://docs.temporal.io/typescript/testing)
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as activity from '@temporalio/activity';
|
|
12
|
+
import {
|
|
13
|
+
AsyncCompletionClient,
|
|
14
|
+
WorkflowClient as BaseWorkflowClient,
|
|
15
|
+
WorkflowClientOptions as BaseWorkflowClientOptions,
|
|
16
|
+
WorkflowResultOptions as BaseWorkflowResultOptions,
|
|
17
|
+
WorkflowStartOptions as BaseWorkflowStartOptions,
|
|
18
|
+
} from '@temporalio/client';
|
|
19
|
+
import { ActivityFunction, CancelledFailure, msToTs, Workflow, WorkflowResultType } from '@temporalio/common';
|
|
20
|
+
import { NativeConnection, Logger, DefaultLogger } from '@temporalio/worker';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import os from 'os';
|
|
23
|
+
import { AbortController } from 'abort-controller';
|
|
24
|
+
import { ChildProcess, spawn, StdioOptions } from 'child_process';
|
|
25
|
+
import events from 'events';
|
|
26
|
+
import { kill, waitOnChild } from './child-process';
|
|
27
|
+
import type getPortType from 'get-port';
|
|
28
|
+
import { Connection, TestService } from './test-service-client';
|
|
29
|
+
|
|
30
|
+
const TEST_SERVER_EXECUTABLE_NAME = os.platform() === 'win32' ? 'test-server.exe' : 'test-server';
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_TEST_SERVER_PATH = path.join(__dirname, `../${TEST_SERVER_EXECUTABLE_NAME}`);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Options passed to {@link WorkflowClient.result}, these are the same as the
|
|
36
|
+
* {@link BaseWorkflowResultOptions} with an additional option that controls
|
|
37
|
+
* whether to toggle time skipping in the Test server while waiting on a
|
|
38
|
+
* Workflow's result.
|
|
39
|
+
*/
|
|
40
|
+
export interface WorkflowResultOptions extends BaseWorkflowResultOptions {
|
|
41
|
+
/**
|
|
42
|
+
* If set to `true`, waiting for the result does not enable time skipping
|
|
43
|
+
*/
|
|
44
|
+
runInNormalTime?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Options passed to {@link WorkflowClient.execute}, these are the same as the
|
|
49
|
+
* {@link BaseWorkflowStartOptions} with an additional option that controls
|
|
50
|
+
* whether to toggle time skipping in the Test server while waiting on a
|
|
51
|
+
* Workflow's result.
|
|
52
|
+
*/
|
|
53
|
+
export type WorkflowStartOptions<T extends Workflow> = BaseWorkflowStartOptions<T> & {
|
|
54
|
+
/**
|
|
55
|
+
* If set to `true`, waiting for the result does not enable time skipping
|
|
56
|
+
*/
|
|
57
|
+
runInNormalTime?: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export interface WorkflowClientOptions extends BaseWorkflowClientOptions {
|
|
61
|
+
connection: Connection;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A client with the exact same API as the "normal" client with 1 exception,
|
|
66
|
+
* When this client waits on a Workflow's result, it will enable time skipping
|
|
67
|
+
* in the test server.
|
|
68
|
+
*/
|
|
69
|
+
export class WorkflowClient extends BaseWorkflowClient {
|
|
70
|
+
protected readonly testService: TestService;
|
|
71
|
+
|
|
72
|
+
constructor(options: WorkflowClientOptions) {
|
|
73
|
+
super(options);
|
|
74
|
+
this.testService = options.connection.testService;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Execute a Workflow and wait for completion.
|
|
79
|
+
*
|
|
80
|
+
* @see {@link BaseWorkflowClient.execute}
|
|
81
|
+
*/
|
|
82
|
+
public async execute<T extends Workflow>(
|
|
83
|
+
workflowTypeOrFunc: string | T,
|
|
84
|
+
options: WorkflowStartOptions<T>
|
|
85
|
+
): Promise<WorkflowResultType<T>> {
|
|
86
|
+
return super.execute(workflowTypeOrFunc, options);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Gets the result of a Workflow execution.
|
|
91
|
+
*
|
|
92
|
+
* @see {@link BaseWorkflowClient.result}
|
|
93
|
+
*/
|
|
94
|
+
override async result<T>(
|
|
95
|
+
workflowId: string,
|
|
96
|
+
runId?: string | undefined,
|
|
97
|
+
opts?: WorkflowResultOptions | undefined
|
|
98
|
+
): Promise<T> {
|
|
99
|
+
if (opts?.runInNormalTime) {
|
|
100
|
+
return await super.result(workflowId, runId, opts);
|
|
101
|
+
}
|
|
102
|
+
await this.testService.unlockTimeSkipping({});
|
|
103
|
+
try {
|
|
104
|
+
return await super.result(workflowId, runId, opts);
|
|
105
|
+
} finally {
|
|
106
|
+
await this.testService.lockTimeSkipping({});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convenience workflow interceptors
|
|
113
|
+
*
|
|
114
|
+
* Contains a single interceptor for transforming `AssertionError`s into non
|
|
115
|
+
* retryable `ApplicationFailure`s.
|
|
116
|
+
*/
|
|
117
|
+
export const workflowInterceptorModules = [path.join(__dirname, 'assert-to-failure-interceptor')];
|
|
118
|
+
|
|
119
|
+
export interface TestServerSpawnerOptions {
|
|
120
|
+
/**
|
|
121
|
+
* @default {@link DEFAULT_TEST_SERVER_PATH}
|
|
122
|
+
*/
|
|
123
|
+
path?: string;
|
|
124
|
+
/**
|
|
125
|
+
* @default ignore
|
|
126
|
+
*/
|
|
127
|
+
stdio?: StdioOptions;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A generic callback that returns a child process
|
|
132
|
+
*/
|
|
133
|
+
export type TestServerSpawner = (port: number) => ChildProcess;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Options for {@link TestWorkflowEnvironment.create}
|
|
137
|
+
*/
|
|
138
|
+
export interface TestWorkflowEnvironmentOptions {
|
|
139
|
+
testServer?: TestServerSpawner | TestServerSpawnerOptions;
|
|
140
|
+
logger?: Logger;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface TestWorkflowEnvironmentOptionsWithDefaults {
|
|
144
|
+
testServerSpawner: TestServerSpawner;
|
|
145
|
+
logger: Logger;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function addDefaults({
|
|
149
|
+
testServer,
|
|
150
|
+
logger,
|
|
151
|
+
}: TestWorkflowEnvironmentOptions): TestWorkflowEnvironmentOptionsWithDefaults {
|
|
152
|
+
return {
|
|
153
|
+
testServerSpawner:
|
|
154
|
+
typeof testServer === 'function'
|
|
155
|
+
? testServer
|
|
156
|
+
: (port: number) =>
|
|
157
|
+
spawn(testServer?.path || DEFAULT_TEST_SERVER_PATH, [`${port}`], {
|
|
158
|
+
stdio: testServer?.stdio || 'ignore',
|
|
159
|
+
}),
|
|
160
|
+
logger: logger ?? new DefaultLogger('INFO'),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// TS transforms `import` statements into `require`s, this is a workaround until
|
|
165
|
+
// tsconfig module nodenext is stable.
|
|
166
|
+
const _importDynamic = new Function('modulePath', 'return import(modulePath)');
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* An execution environment for running Workflow integration tests.
|
|
170
|
+
*
|
|
171
|
+
* Runs an external server.
|
|
172
|
+
* By default, the Java test server is used which supports time skipping.
|
|
173
|
+
*/
|
|
174
|
+
export class TestWorkflowEnvironment {
|
|
175
|
+
/**
|
|
176
|
+
* Get an extablished {@link Connection} to the test server
|
|
177
|
+
*/
|
|
178
|
+
public readonly connection: Connection;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* An {@link AsyncCompletionClient} for interacting with the test server
|
|
182
|
+
*/
|
|
183
|
+
public readonly asyncCompletionClient: AsyncCompletionClient;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A {@link WorkflowClient} for interacting with the test server
|
|
187
|
+
*/
|
|
188
|
+
public readonly workflowClient: WorkflowClient;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A {@link NativeConnection} for interacting with the test server.
|
|
192
|
+
*
|
|
193
|
+
* Use this connection when creating Workers for testing.
|
|
194
|
+
*/
|
|
195
|
+
public readonly nativeConnection: NativeConnection;
|
|
196
|
+
|
|
197
|
+
protected constructor(
|
|
198
|
+
protected readonly serverProc: ChildProcess,
|
|
199
|
+
connection: Connection,
|
|
200
|
+
nativeConnection: NativeConnection
|
|
201
|
+
) {
|
|
202
|
+
this.connection = connection;
|
|
203
|
+
this.nativeConnection = nativeConnection;
|
|
204
|
+
this.workflowClient = new WorkflowClient({ connection });
|
|
205
|
+
this.asyncCompletionClient = new AsyncCompletionClient({ connection });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Create a new test environment
|
|
210
|
+
*/
|
|
211
|
+
static async create(opts?: TestWorkflowEnvironmentOptions): Promise<TestWorkflowEnvironment> {
|
|
212
|
+
// No, we're not going to compile this to ESM for one dependency
|
|
213
|
+
const getPort = (await _importDynamic('get-port')).default as typeof getPortType;
|
|
214
|
+
const port = await getPort();
|
|
215
|
+
|
|
216
|
+
const { testServerSpawner, logger } = addDefaults(opts ?? {});
|
|
217
|
+
|
|
218
|
+
const child = testServerSpawner(port);
|
|
219
|
+
|
|
220
|
+
const address = `127.0.0.1:${port}`;
|
|
221
|
+
const connPromise = Connection.connect({ address });
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await Promise.race([
|
|
225
|
+
connPromise,
|
|
226
|
+
waitOnChild(child).then(() => {
|
|
227
|
+
throw new Error('Test server child process exited prematurely');
|
|
228
|
+
}),
|
|
229
|
+
]);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
try {
|
|
232
|
+
await kill(child);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
logger.error('Failed to kill test server child process', { error });
|
|
235
|
+
}
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const conn = await connPromise;
|
|
240
|
+
const nativeConnection = await NativeConnection.connect({ address });
|
|
241
|
+
|
|
242
|
+
return new this(child, conn, nativeConnection);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Kill the test server process and close the connection to it
|
|
247
|
+
*/
|
|
248
|
+
async teardown(): Promise<void> {
|
|
249
|
+
await this.connection.close();
|
|
250
|
+
await this.nativeConnection.close();
|
|
251
|
+
// TODO: the server should return exit code 0
|
|
252
|
+
await kill(this.serverProc, 'SIGINT', { validReturnCodes: [0, 130] });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Wait for `durationMs` in "test server time".
|
|
257
|
+
*
|
|
258
|
+
* The test server toggles between skipped time and normal time depending on what it needs to execute.
|
|
259
|
+
*
|
|
260
|
+
* This method is likely to resolve in less than `durationMs` of "real time".
|
|
261
|
+
*
|
|
262
|
+
* Useful for simulating events far into the future like completion of long running activities.
|
|
263
|
+
*
|
|
264
|
+
* @param durationMs {@link https://www.npmjs.com/package/ms | ms} formatted string or number of milliseconds
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
*
|
|
268
|
+
* `workflow.ts`
|
|
269
|
+
*
|
|
270
|
+
* ```ts
|
|
271
|
+
* const activities = proxyActivities({ startToCloseTimeout: 2_000_000 });
|
|
272
|
+
*
|
|
273
|
+
* export async function raceActivityAndTimer(): Promise<string> {
|
|
274
|
+
* return await Promise.race([
|
|
275
|
+
* wf.sleep(500_000).then(() => 'timer'),
|
|
276
|
+
* activities.longRunning().then(() => 'activity'),
|
|
277
|
+
* ]);
|
|
278
|
+
* }
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* `test.ts`
|
|
282
|
+
*
|
|
283
|
+
* ```ts
|
|
284
|
+
* const worker = await Worker.create({
|
|
285
|
+
* connection: testEnv.nativeConnection,
|
|
286
|
+
* activities: {
|
|
287
|
+
* async longRunning() {
|
|
288
|
+
* await testEnv.sleep(1_000_000); // <-- sleep called here
|
|
289
|
+
* },
|
|
290
|
+
* },
|
|
291
|
+
* // ...
|
|
292
|
+
* });
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
sleep = async (durationMs: number | string): Promise<void> => {
|
|
296
|
+
await this.connection.testService.unlockTimeSkippingWithSleep({ duration: msToTs(durationMs) });
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Used as the default activity info for Activities executed in the {@link MockActivityEnvironment}
|
|
302
|
+
*/
|
|
303
|
+
export const defaultActivityInfo: activity.Info = {
|
|
304
|
+
attempt: 1,
|
|
305
|
+
taskQueue: 'test',
|
|
306
|
+
isLocal: false,
|
|
307
|
+
taskToken: Buffer.from('test'),
|
|
308
|
+
activityId: 'test',
|
|
309
|
+
activityType: 'unknown',
|
|
310
|
+
workflowType: 'test',
|
|
311
|
+
base64TaskToken: Buffer.from('test').toString('base64'),
|
|
312
|
+
heartbeatDetails: undefined,
|
|
313
|
+
activityNamespace: 'default',
|
|
314
|
+
workflowNamespace: 'default',
|
|
315
|
+
workflowExecution: { workflowId: 'test', runId: 'dead-beef' },
|
|
316
|
+
scheduledTimestampMs: 1,
|
|
317
|
+
startToCloseTimeoutMs: 1000,
|
|
318
|
+
scheduleToCloseTimeoutMs: 1000,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* An execution environment for testing Activities.
|
|
323
|
+
*
|
|
324
|
+
* Mocks Activity {@link Context | activity.Context} and exposes hooks for
|
|
325
|
+
* cancellation and heartbeats.
|
|
326
|
+
*/
|
|
327
|
+
export class MockActivityEnvironment extends events.EventEmitter {
|
|
328
|
+
public cancel: (reason?: any) => void = () => undefined;
|
|
329
|
+
public readonly context: activity.Context;
|
|
330
|
+
|
|
331
|
+
constructor(info?: Partial<activity.Info>) {
|
|
332
|
+
super();
|
|
333
|
+
const abortController = new AbortController();
|
|
334
|
+
const promise = new Promise<never>((_, reject) => {
|
|
335
|
+
this.cancel = (reason?: any) => {
|
|
336
|
+
abortController.abort();
|
|
337
|
+
reject(new CancelledFailure(reason));
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
const heartbeatCallback = (details?: unknown) => this.emit('heartbeat', details);
|
|
341
|
+
this.context = new activity.Context(
|
|
342
|
+
{ ...defaultActivityInfo, ...info },
|
|
343
|
+
promise,
|
|
344
|
+
abortController.signal,
|
|
345
|
+
heartbeatCallback
|
|
346
|
+
);
|
|
347
|
+
promise.catch(() => {
|
|
348
|
+
/* avoid unhandled rejection */
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Run a function in Activity Context
|
|
354
|
+
*/
|
|
355
|
+
public run<P extends any[], R, F extends ActivityFunction<P, R>>(fn: F, ...args: P): Promise<R> {
|
|
356
|
+
return activity.asyncLocalStorage.run(this.context, fn, ...args);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as grpc from '@grpc/grpc-js';
|
|
2
|
+
import { Connection as BaseConnection, ConnectionOptions } from '@temporalio/client';
|
|
3
|
+
import { ConnectionCtorOptions as BaseConnectionCtorOptions } from '@temporalio/client/lib/connection';
|
|
4
|
+
import { temporal } from '../generated-protos';
|
|
5
|
+
|
|
6
|
+
export type TestService = temporal.api.testservice.v1.TestService;
|
|
7
|
+
export const { TestService } = temporal.api.testservice.v1;
|
|
8
|
+
|
|
9
|
+
interface ConnectionCtorOptions extends BaseConnectionCtorOptions {
|
|
10
|
+
testService: TestService;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A Connection class that can be used to interact with both the test server's TestService and WorkflowService
|
|
15
|
+
*/
|
|
16
|
+
export class Connection extends BaseConnection {
|
|
17
|
+
public static readonly TestServiceClient = grpc.makeGenericClientConstructor({}, 'TestService', {});
|
|
18
|
+
public readonly testService: TestService;
|
|
19
|
+
|
|
20
|
+
protected static createCtorOptions(options?: ConnectionOptions): ConnectionCtorOptions {
|
|
21
|
+
const ctorOptions = BaseConnection.createCtorOptions(options);
|
|
22
|
+
const rpcImpl = this.generateRPCImplementation({
|
|
23
|
+
serviceName: 'temporal.api.testservice.v1.TestService',
|
|
24
|
+
client: ctorOptions.client,
|
|
25
|
+
callContextStorage: ctorOptions.callContextStorage,
|
|
26
|
+
interceptors: ctorOptions.options.interceptors,
|
|
27
|
+
});
|
|
28
|
+
const testService = TestService.create(rpcImpl, false, false);
|
|
29
|
+
return { ...ctorOptions, testService };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static lazy(options?: ConnectionOptions): Connection {
|
|
33
|
+
const ctorOptions = this.createCtorOptions(options);
|
|
34
|
+
return new this(ctorOptions);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static async connect(options?: ConnectionOptions): Promise<Connection> {
|
|
38
|
+
const ret = this.lazy(options);
|
|
39
|
+
await ret.ensureConnected();
|
|
40
|
+
return ret;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected constructor(options: ConnectionCtorOptions) {
|
|
44
|
+
super(options);
|
|
45
|
+
this.testService = options.testService;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Connection } from '@temporalio/client';
|
|
2
|
+
import { msToTs } from '@temporalio/internal-workflow-common';
|
|
3
|
+
|
|
4
|
+
export async function waitOnNamespace(
|
|
5
|
+
connection: Connection,
|
|
6
|
+
namespace: string,
|
|
7
|
+
maxAttempts = 100,
|
|
8
|
+
retryIntervalSecs = 1
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const runId = '12345678-dead-beef-1234-1234567890ab';
|
|
11
|
+
for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
|
|
12
|
+
try {
|
|
13
|
+
await connection.workflowService.getWorkflowExecutionHistory({
|
|
14
|
+
namespace,
|
|
15
|
+
execution: { workflowId: 'fake', runId },
|
|
16
|
+
});
|
|
17
|
+
} catch (err: any) {
|
|
18
|
+
if (err.details.includes('workflow history not found') || err.details.includes(runId)) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
if (attempt === maxAttempts) {
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
await new Promise((resolve) => setTimeout(resolve, retryIntervalSecs * 1000));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function createNamespace(
|
|
30
|
+
connection: Connection,
|
|
31
|
+
namespace: string,
|
|
32
|
+
maxAttempts = 100,
|
|
33
|
+
retryIntervalSecs = 1
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
|
|
36
|
+
try {
|
|
37
|
+
await connection.workflowService.registerNamespace({
|
|
38
|
+
namespace,
|
|
39
|
+
workflowExecutionRetentionPeriod: msToTs('1 day'),
|
|
40
|
+
});
|
|
41
|
+
break;
|
|
42
|
+
} catch (err: any) {
|
|
43
|
+
if (err.details === 'Namespace already exists.') {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
if (attempt === maxAttempts) {
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, retryIntervalSecs * 1000));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|