readiness-manager 1.1.4 → 1.2.0-rc.xf50ab1c7f
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/CHANGELOG.md +6 -0
- package/README.md +33 -17
- package/dist/src/constants.d.ts +3 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +10 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/error.d.ts +16 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +23 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +120 -0
- package/dist/src/index.js.map +1 -0
- package/dist/tests/mock.d.ts +8 -0
- package/dist/tests/mock.d.ts.map +1 -0
- package/dist/tests/mock.js +39 -0
- package/dist/tests/mock.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/jest.config.ts +8 -0
- package/package.json +16 -27
- package/project.json +16 -0
- package/src/constants.ts +11 -0
- package/src/error.ts +38 -0
- package/src/index.ts +244 -0
- package/src/spec.ts +257 -0
- package/tests/mock.ts +56 -0
- package/tsconfig.json +10 -0
- package/LICENSE +0 -8
- package/index.js +0 -1
- package/src/constants.js +0 -14
- package/src/error.js +0 -42
- package/src/index.js +0 -208
- package/src/types.d.ts +0 -40
- package/tests/mock.d.ts +0 -9
- package/tests/mock.js +0 -36
package/jest.config.ts
ADDED
package/package.json
CHANGED
|
@@ -1,33 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "readiness-manager",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-rc.xf50ab1c7f",
|
|
4
4
|
"description": "👨💼 Define when your app is ready",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"handlers",
|
|
9
|
-
"process"
|
|
10
|
-
],
|
|
11
|
-
"author": "Fiverr Ants",
|
|
12
|
-
"license": "MIT",
|
|
13
|
-
"repository": {
|
|
14
|
-
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/fiverr/readiness-manager.git"
|
|
5
|
+
"main": "./dist/src/index.js",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@fiverr-private/obs": "^1.35.0"
|
|
16
8
|
},
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"test": "jest",
|
|
22
|
-
"lint": "eslint . --ext .js"
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/src/index.js",
|
|
11
|
+
"./tests/mock": "./dist/tests/mock.js",
|
|
12
|
+
"./package.json": "./package.json"
|
|
23
13
|
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"tag": "latest"
|
|
14
|
+
"types": "./dist/src/index.d.ts",
|
|
15
|
+
"nx": {
|
|
16
|
+
"tags": [
|
|
17
|
+
"scope:lib",
|
|
18
|
+
"package_core",
|
|
19
|
+
"package_core:lib"
|
|
20
|
+
]
|
|
32
21
|
}
|
|
33
|
-
}
|
|
22
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "readiness-manager",
|
|
3
|
+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"projectType": "library",
|
|
5
|
+
"sourceRoot": "packages/atoms/readiness-manager/src",
|
|
6
|
+
"targets": {
|
|
7
|
+
"build": {
|
|
8
|
+
"executor": "nx:run-commands",
|
|
9
|
+
"options": {
|
|
10
|
+
"commands": ["rm -rf ./dist", "tsc --build tsconfig.json"],
|
|
11
|
+
"parallel": false,
|
|
12
|
+
"cwd": "packages/atoms/readiness-manager"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ActionStatus = 'not_started' | 'pending' | 'resolved' | 'rejected';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The possible beacon status.
|
|
5
|
+
*/
|
|
6
|
+
export const BeaconStatus: Record<string, ActionStatus> = {
|
|
7
|
+
NOT_STARTED: 'not_started',
|
|
8
|
+
PENDING: 'pending',
|
|
9
|
+
RESOLVED: 'resolved',
|
|
10
|
+
REJECTED: 'rejected',
|
|
11
|
+
} as const;
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { logger } from '@fiverr-private/obs';
|
|
2
|
+
/**
|
|
3
|
+
* The possible errors that could be thrown.
|
|
4
|
+
*/
|
|
5
|
+
export const ERRORS = {
|
|
6
|
+
BEACON_ALREADY_EXISTS: 'Beacon is already exists, please ensure to use a unique name per beacon',
|
|
7
|
+
BEACON_DOES_NOT_EXISTS:
|
|
8
|
+
'Beacon does not exists, make sure to call `register` before trying to set beacon ready hook.',
|
|
9
|
+
BEACON_EXECUTION_FAILED: 'Beacon execution failed',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export type ActionRetry = () => Promise<unknown>;
|
|
13
|
+
export type ActionErrorHandler = (error: ActionExecutionError, retry: ActionRetry) => void;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The custom error to be thrown when beacon execution fails.
|
|
17
|
+
*/
|
|
18
|
+
export class ActionExecutionError extends Error {
|
|
19
|
+
override name: string;
|
|
20
|
+
override stack: string;
|
|
21
|
+
attempt: number;
|
|
22
|
+
failReason: string;
|
|
23
|
+
|
|
24
|
+
constructor(name: string, attempt: number, error: Error) {
|
|
25
|
+
super();
|
|
26
|
+
|
|
27
|
+
this.message = ERRORS.BEACON_EXECUTION_FAILED;
|
|
28
|
+
this.name = name;
|
|
29
|
+
this.attempt = attempt;
|
|
30
|
+
this.stack = error.stack || '';
|
|
31
|
+
this.failReason = error.message;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default error handler to be triggered upon beacon errors.
|
|
37
|
+
*/
|
|
38
|
+
export const defaultErrorHandler: ActionErrorHandler = (beaconError) => logger.error(beaconError);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { logger } from '@fiverr-private/obs';
|
|
2
|
+
import { ERRORS, ActionExecutionError, defaultErrorHandler, ActionErrorHandler } from './error';
|
|
3
|
+
import { BeaconStatus, ActionStatus } from './constants';
|
|
4
|
+
|
|
5
|
+
export type ReadyCallback = () => void;
|
|
6
|
+
export type ReadyAction = () => unknown | Promise<unknown>;
|
|
7
|
+
|
|
8
|
+
export interface ActionsStatus {
|
|
9
|
+
[status: string]: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Beacon {
|
|
13
|
+
name: string;
|
|
14
|
+
action: ReadyAction;
|
|
15
|
+
status: ActionStatus;
|
|
16
|
+
callbacks: ReadyCallback[];
|
|
17
|
+
debug?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface BeaconsMap {
|
|
21
|
+
[name: string]: Beacon;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sets of private attributes which should not be exposed / accessed by consumer.
|
|
26
|
+
*/
|
|
27
|
+
const beaconsSymbol = Symbol('beacons');
|
|
28
|
+
const callbacksSymbol = Symbol('callbacks');
|
|
29
|
+
const executeSymbol = Symbol('execute');
|
|
30
|
+
const trackBeaconUpdateSymbol = Symbol('trackBeaconUpdate');
|
|
31
|
+
const updateBeaconSymbol = Symbol('updateBeacon');
|
|
32
|
+
const errorHandlerSymbol = Symbol('errorHandler');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The internal "ready" state to determine process readiness.
|
|
36
|
+
*/
|
|
37
|
+
let readyState = false;
|
|
38
|
+
|
|
39
|
+
export interface ReadinessManager {
|
|
40
|
+
register: (name: string, action: ReadyAction, debug?: boolean) => void;
|
|
41
|
+
run: () => ReadinessManager;
|
|
42
|
+
onReady: (callback: ReadyCallback) => ReadinessManager;
|
|
43
|
+
onActionReady: (name: string, callback: ReadyCallback) => ReadinessManager;
|
|
44
|
+
onError: (errorHandler: ActionErrorHandler) => ReadinessManager;
|
|
45
|
+
status: () => ActionsStatus;
|
|
46
|
+
ready: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class ReadinessManagerImpl implements ReadinessManager {
|
|
50
|
+
private [beaconsSymbol]: BeaconsMap;
|
|
51
|
+
private [callbacksSymbol]: ReadyCallback[];
|
|
52
|
+
private [errorHandlerSymbol]: ActionErrorHandler;
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
this[beaconsSymbol] = {};
|
|
56
|
+
this[callbacksSymbol] = [];
|
|
57
|
+
this[errorHandlerSymbol] = defaultErrorHandler;
|
|
58
|
+
|
|
59
|
+
this[executeSymbol] = this[executeSymbol].bind(this);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns true if current process state is considered as "ready".
|
|
64
|
+
*/
|
|
65
|
+
get ready(): boolean {
|
|
66
|
+
return readyState;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns current registered actions status
|
|
71
|
+
*/
|
|
72
|
+
status(): ActionsStatus {
|
|
73
|
+
return Object.values(this[beaconsSymbol]).reduce((acc, { name, status }) => {
|
|
74
|
+
const current = acc[status] || [];
|
|
75
|
+
return Object.assign(acc, { [status]: [...current, name] });
|
|
76
|
+
}, {} as ActionsStatus);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Registers a given name and action as beacon under manager.
|
|
81
|
+
* @param name - The operation name to register with.
|
|
82
|
+
* @param action - The ready emitter to condition the process readiness with.
|
|
83
|
+
* @param debug - Optional flag to enable debug timing logs.
|
|
84
|
+
*/
|
|
85
|
+
register(name: string, action: ReadyAction, debug = false): void {
|
|
86
|
+
if (this.ready) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (this[beaconsSymbol][name]) {
|
|
91
|
+
throw new Error(ERRORS.BEACON_ALREADY_EXISTS);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const beacon: Beacon = {
|
|
95
|
+
name,
|
|
96
|
+
action,
|
|
97
|
+
callbacks: [],
|
|
98
|
+
status: BeaconStatus.NOT_STARTED,
|
|
99
|
+
debug,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
Object.assign(this[beaconsSymbol], { [name]: beacon });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Runs current registered beacons execution to determine process "ready" state.
|
|
107
|
+
*/
|
|
108
|
+
run(): ReadinessManager {
|
|
109
|
+
readyState = false;
|
|
110
|
+
|
|
111
|
+
Object.values(this[beaconsSymbol]).forEach((beacon) => this[executeSymbol](beacon));
|
|
112
|
+
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Registers a given callback to be triggered once process is marked as "ready".
|
|
118
|
+
* @param callback
|
|
119
|
+
*/
|
|
120
|
+
onReady(callback: ReadyCallback): ReadinessManager {
|
|
121
|
+
if (readyState) {
|
|
122
|
+
// Invokes immediately given callback if process is already ready.
|
|
123
|
+
callback();
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this[callbacksSymbol].push(callback);
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Registers a given callback on a specific action resolved.
|
|
133
|
+
* @param name - The name of the action the callback should be attached to.
|
|
134
|
+
* @param callback - The callback to run upon beacon resolved.
|
|
135
|
+
*/
|
|
136
|
+
onActionReady(name: string, callback: ReadyCallback): ReadinessManager {
|
|
137
|
+
const beacon = this[beaconsSymbol][name];
|
|
138
|
+
if (!beacon) {
|
|
139
|
+
throw new Error(ERRORS.BEACON_DOES_NOT_EXISTS);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (beacon.status === BeaconStatus.RESOLVED) {
|
|
143
|
+
// Invokes immediately given callback if beacon status is already resolved.
|
|
144
|
+
callback();
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this[beaconsSymbol][name].callbacks.push(callback);
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Registers a given error handler under manager errors.
|
|
154
|
+
* @param errorHandler - The handler to trigger upon action errors.
|
|
155
|
+
*/
|
|
156
|
+
onError(errorHandler: ActionErrorHandler): ReadinessManager {
|
|
157
|
+
this[errorHandlerSymbol] = errorHandler;
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Tracks a given beacon action.
|
|
163
|
+
* @param beacon - The beacon to execute.
|
|
164
|
+
* @param attempt - The attempt number of current beacon execution.
|
|
165
|
+
* @throws {ActionExecutionError}
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
private async [executeSymbol](beacon: Beacon, attempt = 1): Promise<void> {
|
|
169
|
+
const { name, action, debug } = beacon;
|
|
170
|
+
|
|
171
|
+
const update = (status: ActionStatus) => this[updateBeaconSymbol](name, status);
|
|
172
|
+
|
|
173
|
+
update(BeaconStatus.PENDING);
|
|
174
|
+
|
|
175
|
+
const startTime = debug ? Date.now() : null;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
// The actual beacon execution we want to keep track on.
|
|
179
|
+
const result = action();
|
|
180
|
+
|
|
181
|
+
if (result instanceof Promise) {
|
|
182
|
+
await result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (debug && startTime !== null) {
|
|
186
|
+
const duration = Date.now() - startTime;
|
|
187
|
+
logger.debug(`[ReadinessManager] Action "${name}" completed in ${duration}ms`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
update(BeaconStatus.RESOLVED);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (debug && startTime !== null) {
|
|
193
|
+
const duration = Date.now() - startTime;
|
|
194
|
+
logger.debug(`[ReadinessManager] Action "${name}" failed after ${duration}ms`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
update(BeaconStatus.REJECTED);
|
|
198
|
+
|
|
199
|
+
// Invokes consumer error hook with a retry method and the beacon error.
|
|
200
|
+
this[errorHandlerSymbol](new ActionExecutionError(name, attempt, error as Error), () =>
|
|
201
|
+
this[executeSymbol](beacon, attempt + 1)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Updates a given beacon name with a given status.
|
|
208
|
+
* @param name - The beacon name to update.
|
|
209
|
+
* @param status - The beacon status to set.
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
private [updateBeaconSymbol](name: string, status: ActionStatus): void {
|
|
213
|
+
Object.assign(this[beaconsSymbol][name], { status });
|
|
214
|
+
|
|
215
|
+
// Resolves specific beacon ready listeners.
|
|
216
|
+
if (status === BeaconStatus.RESOLVED) {
|
|
217
|
+
this[beaconsSymbol][name].callbacks.forEach((callback) => callback());
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this[trackBeaconUpdateSymbol]();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Tracks a beacon update event, checks whether process is "ready".
|
|
225
|
+
* If so, will trigger registered `onReady` callbacks.
|
|
226
|
+
* @private
|
|
227
|
+
*/
|
|
228
|
+
private [trackBeaconUpdateSymbol](): void {
|
|
229
|
+
if (readyState) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
readyState = Object.values(this[beaconsSymbol]).every((beacon) => beacon.status === BeaconStatus.RESOLVED);
|
|
234
|
+
|
|
235
|
+
if (readyState) {
|
|
236
|
+
this[callbacksSymbol].forEach((callback) => callback());
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const manager: ReadinessManager = new ReadinessManagerImpl();
|
|
242
|
+
|
|
243
|
+
export default manager;
|
|
244
|
+
export { ActionExecutionError, ActionErrorHandler };
|
package/src/spec.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { BeaconStatus } from './constants';
|
|
2
|
+
import { ERRORS, ActionExecutionError } from './error';
|
|
3
|
+
import { ReadinessManager } from './index';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Waits a given milliseconds before resolving.
|
|
7
|
+
* @param time - The time to wait
|
|
8
|
+
*/
|
|
9
|
+
const wait = (time = 0): Promise<unknown> => new Promise((resolve) => setTimeout(resolve, time));
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Delays a promise resolve with a given time.
|
|
13
|
+
* @param time - The time to wait.
|
|
14
|
+
*/
|
|
15
|
+
const delayResolve = (time = 100): Promise<unknown> => new Promise((resolve) => setTimeout(resolve, time));
|
|
16
|
+
|
|
17
|
+
describe('lib/readinessManager', () => {
|
|
18
|
+
let readyManager: ReadinessManager;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.isolateModules(() => {
|
|
22
|
+
readyManager = require('./index').default;
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(jest.clearAllMocks);
|
|
27
|
+
|
|
28
|
+
describe('API', () => {
|
|
29
|
+
it.each(['register', 'run', 'onReady', 'onActionReady', 'status', 'onError'])(
|
|
30
|
+
'Exposes public API (%p)',
|
|
31
|
+
(property) => {
|
|
32
|
+
expect(typeof (readyManager as any)[property]).toBe('function');
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
it.each(['beacons', 'callbacks', 'execute', 'updateBeacon'])(
|
|
37
|
+
'Encapsulates private properties (%p)',
|
|
38
|
+
(property) => {
|
|
39
|
+
expect((readyManager as any)[property]).toBeUndefined();
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('status', () => {
|
|
45
|
+
describe('When no actions are registered', () => {
|
|
46
|
+
it('Return an empty object', () => {
|
|
47
|
+
expect(readyManager.status()).toEqual({});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('When some actions exists', () => {
|
|
52
|
+
it('Group registered actions with same status', async () => {
|
|
53
|
+
readyManager.register('success', () => Promise.resolve());
|
|
54
|
+
readyManager.register('other', () => Promise.resolve());
|
|
55
|
+
readyManager.register('fail', () => Promise.reject(new Error()));
|
|
56
|
+
|
|
57
|
+
readyManager.run();
|
|
58
|
+
await wait(10);
|
|
59
|
+
|
|
60
|
+
const status = readyManager.status();
|
|
61
|
+
|
|
62
|
+
expect(status[BeaconStatus.RESOLVED]).toEqual(['success', 'other']);
|
|
63
|
+
expect(status[BeaconStatus.REJECTED]).toEqual(['fail']);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('register', () => {
|
|
69
|
+
describe('When given action name is unique', () => {
|
|
70
|
+
it('Registers action and name as beacon with not_started status', () => {
|
|
71
|
+
readyManager.register('action', () => true);
|
|
72
|
+
|
|
73
|
+
expect(readyManager.status()[BeaconStatus.NOT_STARTED]).toHaveLength(1);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('When action name was already registered', () => {
|
|
78
|
+
it('Throws upon registration', () => {
|
|
79
|
+
readyManager.register('once', () => true);
|
|
80
|
+
|
|
81
|
+
expect(() => readyManager.register('once', () => true)).toThrowError(ERRORS.BEACON_ALREADY_EXISTS);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('run', () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
readyManager.register('action1', delayResolve);
|
|
89
|
+
readyManager.register('action2', delayResolve);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('Executes all given actions', async () => {
|
|
93
|
+
expect(readyManager.status()[BeaconStatus.NOT_STARTED]).toHaveLength(2);
|
|
94
|
+
|
|
95
|
+
readyManager.run();
|
|
96
|
+
|
|
97
|
+
expect(readyManager.status()[BeaconStatus.NOT_STARTED]).toBeUndefined();
|
|
98
|
+
|
|
99
|
+
expect(readyManager.status()[BeaconStatus.PENDING]).toHaveLength(2);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('ready', () => {
|
|
104
|
+
it('Returns false by default', () => {
|
|
105
|
+
expect(readyManager.ready).toBeFalsy();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('When all actions resolves', () => {
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
readyManager.register('action1', () => true);
|
|
111
|
+
readyManager.register('action2', () => true);
|
|
112
|
+
|
|
113
|
+
readyManager.run();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('Is ready', () => expect(readyManager.ready).toBeTruthy());
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('When some action fails', () => {
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
readyManager.register('action1', () => true);
|
|
122
|
+
readyManager.register('action2', () => {
|
|
123
|
+
throw new Error('What?');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
readyManager.run();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('Is not ready', () => expect(readyManager.ready).toBeFalsy());
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('onReady', () => {
|
|
134
|
+
let callback: jest.Mock;
|
|
135
|
+
|
|
136
|
+
beforeEach(() => (callback = jest.fn()));
|
|
137
|
+
|
|
138
|
+
const ensureFresh = () => {
|
|
139
|
+
readyManager.register('action1', () => true);
|
|
140
|
+
expect(callback).toHaveBeenCalledTimes(0);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
describe('When process is not ready', () => {
|
|
144
|
+
it('Registers given callback', () => {
|
|
145
|
+
const callback = jest.fn();
|
|
146
|
+
ensureFresh();
|
|
147
|
+
|
|
148
|
+
readyManager.onReady(callback);
|
|
149
|
+
readyManager.run();
|
|
150
|
+
|
|
151
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('When process is already ready', () => {
|
|
156
|
+
it('Activates immediately given callback', () => {
|
|
157
|
+
ensureFresh();
|
|
158
|
+
|
|
159
|
+
readyManager.run();
|
|
160
|
+
readyManager.onReady(callback);
|
|
161
|
+
|
|
162
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('onActionReady', () => {
|
|
168
|
+
let callback: jest.Mock;
|
|
169
|
+
|
|
170
|
+
beforeEach(() => (callback = jest.fn()));
|
|
171
|
+
|
|
172
|
+
it('Throws when action does not exists', () => {
|
|
173
|
+
expect(() => readyManager.onActionReady('Hey', () => false)).toThrowError(ERRORS.BEACON_DOES_NOT_EXISTS);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('When action is not ready', () => {
|
|
177
|
+
it('Triggers callback once desired aciton is ready', async () => {
|
|
178
|
+
readyManager.register('some', () => true);
|
|
179
|
+
readyManager.register('thing', () => delayResolve(500));
|
|
180
|
+
readyManager.register('other', () => delayResolve(1000));
|
|
181
|
+
|
|
182
|
+
readyManager.onActionReady('thing', callback);
|
|
183
|
+
|
|
184
|
+
readyManager.run();
|
|
185
|
+
expect(callback).toHaveBeenCalledTimes(0);
|
|
186
|
+
|
|
187
|
+
await wait(500);
|
|
188
|
+
|
|
189
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('When action is already resolved', () => {
|
|
194
|
+
it('Triggers immediately given callback', () => {
|
|
195
|
+
readyManager.register('some', () => true);
|
|
196
|
+
|
|
197
|
+
readyManager.run();
|
|
198
|
+
readyManager.onActionReady('some', callback);
|
|
199
|
+
|
|
200
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('onError', () => {
|
|
206
|
+
let logger: jest.Mock;
|
|
207
|
+
beforeEach(() => (logger = jest.fn()));
|
|
208
|
+
|
|
209
|
+
describe('When no errors occur', () => {
|
|
210
|
+
beforeEach(() => {
|
|
211
|
+
readyManager.register('success', () => true);
|
|
212
|
+
readyManager.onError((error) => {
|
|
213
|
+
logger(error);
|
|
214
|
+
});
|
|
215
|
+
readyManager.run();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("Won't trigger error handler", () => {
|
|
219
|
+
expect(logger).toHaveBeenCalledTimes(0);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('When error occur', () => {
|
|
224
|
+
const error = new Error('Oh man, that did not worked!');
|
|
225
|
+
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
readyManager.register('success', () => {
|
|
228
|
+
throw error;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
readyManager.onError((error) => {
|
|
232
|
+
logger(error);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
readyManager.run();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('Triggers consumer handler with beacon execution error', () => {
|
|
239
|
+
expect(logger).toHaveBeenCalledWith(new ActionExecutionError('success', 1, error));
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('Chainable', () => {
|
|
245
|
+
it('Expose public chainable api', () => {
|
|
246
|
+
const ready = jest.fn();
|
|
247
|
+
const error = jest.fn();
|
|
248
|
+
|
|
249
|
+
readyManager.register('action', () => true);
|
|
250
|
+
|
|
251
|
+
readyManager.run().onReady(ready).onError(error);
|
|
252
|
+
|
|
253
|
+
expect(ready).toHaveBeenCalledTimes(1);
|
|
254
|
+
expect(error).toHaveBeenCalledTimes(0);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
package/tests/mock.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ReadinessManager, ReadyCallback, ReadyAction, ActionsStatus } from '../src/index';
|
|
2
|
+
import { ActionErrorHandler } from '../src/error';
|
|
3
|
+
|
|
4
|
+
export interface ReadinessManagerMock extends ReadinessManager {
|
|
5
|
+
setReady: (isReady: boolean) => void;
|
|
6
|
+
orchestrate: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Test mock helper class.
|
|
11
|
+
*/
|
|
12
|
+
class ReadinessManagerMockImpl implements ReadinessManagerMock {
|
|
13
|
+
private _ready = false;
|
|
14
|
+
|
|
15
|
+
get ready(): boolean {
|
|
16
|
+
return this._ready;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
status(): ActionsStatus {
|
|
20
|
+
return {} as ActionsStatus;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setReady(isReady: boolean): void {
|
|
24
|
+
this._ready = isReady;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
register(_name: string, _action: ReadyAction, _debug?: boolean): void {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
run(): ReadinessManager {
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
orchestrate(): void {
|
|
36
|
+
this._ready = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onReady(callback: ReadyCallback): ReadinessManager {
|
|
40
|
+
callback();
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onActionReady(_name: string, callback: ReadyCallback): ReadinessManager {
|
|
45
|
+
callback();
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onError(_errorHandler: ActionErrorHandler): ReadinessManager {
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const manager: ReadinessManagerMock = new ReadinessManagerMockImpl();
|
|
55
|
+
|
|
56
|
+
export default manager;
|
package/tsconfig.json
ADDED