codify-plugin-lib 1.0.65 → 1.0.67

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,247 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { TestConfig, TestResource } from './resource.test.js';
3
+ import { ParameterOperation, ResourceOperation } from 'codify-schemas';
4
+ import { TestParameter } from './resource-parameters.test.js';
5
+ import { StatefulParameter } from './stateful-parameter.js';
6
+
7
+
8
+ describe('Resource tests for stateful plans', () => {
9
+ it('Supports delete operations ', async () => {
10
+ const resource = new class extends TestResource {
11
+ constructor() {
12
+ super({ type: 'resource' });
13
+ }
14
+
15
+ async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
16
+ return {
17
+ propA: 'propADifferent',
18
+ propB: undefined,
19
+ propC: 'propCDifferent',
20
+ }
21
+ }
22
+ }
23
+
24
+ const plan = await resource.plan(
25
+ null,
26
+ {
27
+ type: 'resource',
28
+ propA: 'propA',
29
+ propB: 10,
30
+ propC: 'propC',
31
+ }, true
32
+ );
33
+
34
+ expect(plan).toMatchObject({
35
+ changeSet: {
36
+ operation: ResourceOperation.DESTROY,
37
+ parameterChanges: [
38
+ {
39
+ name: "propA",
40
+ previousValue: "propADifferent",
41
+ newValue: null,
42
+ operation: ParameterOperation.REMOVE
43
+ },
44
+ {
45
+ name: "propC",
46
+ previousValue: "propCDifferent",
47
+ newValue: null,
48
+ operation: ParameterOperation.REMOVE
49
+ },
50
+ ]
51
+ },
52
+ resourceMetadata: {
53
+ type: 'resource'
54
+ }
55
+ })
56
+ })
57
+
58
+ it('Supports create operations', async () => {
59
+ const resource = new class extends TestResource {
60
+ constructor() {
61
+ super({ type: 'resource' });
62
+ }
63
+
64
+ async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ const plan = await resource.plan(
70
+ {
71
+ type: 'resource',
72
+ propA: 'propA',
73
+ propB: 10,
74
+ propC: 'propC',
75
+ },
76
+ null,
77
+ true
78
+ );
79
+
80
+ expect(plan).toMatchObject({
81
+ changeSet: {
82
+ operation: ResourceOperation.CREATE,
83
+ parameterChanges: [
84
+ {
85
+ name: "propA",
86
+ newValue: "propA",
87
+ previousValue: null,
88
+ operation: ParameterOperation.ADD
89
+ },
90
+ {
91
+ name: "propB",
92
+ newValue: 10,
93
+ previousValue: null,
94
+ operation: ParameterOperation.ADD
95
+ },
96
+ {
97
+ name: "propC",
98
+ newValue: 'propC',
99
+ previousValue: null,
100
+ operation: ParameterOperation.ADD
101
+ },
102
+ ]
103
+ },
104
+ resourceMetadata: {
105
+ type: 'resource'
106
+ }
107
+ })
108
+ })
109
+
110
+ it('Supports re-create operations', async () => {
111
+ const resource = new class extends TestResource {
112
+ constructor() {
113
+ super({ type: 'resource' });
114
+ }
115
+
116
+ async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
117
+ return {
118
+ propA: 'propA',
119
+ propC: 'propC',
120
+ propB: undefined
121
+ };
122
+ }
123
+ }
124
+
125
+ const plan = await resource.plan(
126
+ {
127
+ type: 'resource',
128
+ propA: 'propA',
129
+ propB: 10,
130
+ propC: 'propC',
131
+ },
132
+ {
133
+ type: 'resource',
134
+ propA: 'propA',
135
+ propC: 'propC'
136
+ },
137
+ true
138
+ );
139
+
140
+ expect(plan).toMatchObject({
141
+ changeSet: {
142
+ operation: ResourceOperation.RECREATE,
143
+ parameterChanges: expect.arrayContaining([
144
+ {
145
+ name: "propA",
146
+ newValue: "propA",
147
+ previousValue: "propA",
148
+ operation: ParameterOperation.NOOP
149
+ },
150
+ {
151
+ name: "propB",
152
+ newValue: 10,
153
+ previousValue: null,
154
+ operation: ParameterOperation.ADD
155
+ },
156
+ {
157
+ name: "propC",
158
+ newValue: 'propC',
159
+ previousValue: 'propC',
160
+ operation: ParameterOperation.NOOP
161
+ },
162
+ ])
163
+ },
164
+ resourceMetadata: {
165
+ type: 'resource'
166
+ }
167
+ })
168
+ })
169
+
170
+ it('Supports stateful parameters', async () => {
171
+ const statefulParameter = new class extends TestParameter {
172
+ async refresh(): Promise<string | null> {
173
+ return null;
174
+ }
175
+ }
176
+
177
+ const resource = new class extends TestResource {
178
+ constructor() {
179
+ super({
180
+ type: 'resource',
181
+ parameterOptions: {
182
+ propD: { statefulParameter: statefulParameter as StatefulParameter<TestConfig, string> },
183
+ }
184
+ });
185
+ }
186
+
187
+ async refresh(keys: Map<string, unknown>): Promise<Partial<TestConfig> | null> {
188
+ return {
189
+ propA: 'propA',
190
+ propC: 'propC',
191
+ propB: undefined
192
+ };
193
+ }
194
+ }
195
+
196
+ const plan = await resource.plan(
197
+ {
198
+ type: 'resource',
199
+ propA: 'propA',
200
+ propB: 10,
201
+ propC: 'propC',
202
+ propD: 'propD'
203
+ },
204
+ {
205
+ type: 'resource',
206
+ propA: 'propA',
207
+ propC: 'propC'
208
+ },
209
+ true
210
+ );
211
+
212
+ expect(plan).toMatchObject({
213
+ changeSet: {
214
+ operation: ResourceOperation.RECREATE,
215
+ parameterChanges: expect.arrayContaining([
216
+ {
217
+ name: "propA",
218
+ newValue: "propA",
219
+ previousValue: "propA",
220
+ operation: ParameterOperation.NOOP
221
+ },
222
+ {
223
+ name: "propB",
224
+ newValue: 10,
225
+ previousValue: null,
226
+ operation: ParameterOperation.ADD
227
+ },
228
+ {
229
+ name: "propC",
230
+ newValue: 'propC',
231
+ previousValue: 'propC',
232
+ operation: ParameterOperation.NOOP
233
+ },
234
+ {
235
+ name: "propD",
236
+ newValue: 'propD',
237
+ previousValue: null,
238
+ operation: ParameterOperation.ADD
239
+ },
240
+ ])
241
+ },
242
+ resourceMetadata: {
243
+ type: 'resource'
244
+ }
245
+ })
246
+ })
247
+ })
@@ -301,8 +301,8 @@ describe('Resource tests', () => {
301
301
  }
302
302
 
303
303
  const plan = await resource.plan({ type: 'resource'})
304
- expect(plan.currentConfig.propA).to.eq('propAAfter');
305
- expect(plan.desiredConfig.propA).to.eq('propADefault');
304
+ expect(plan.currentConfig?.propA).to.eq('propAAfter');
305
+ expect(plan.desiredConfig?.propA).to.eq('propADefault');
306
306
  expect(plan.changeSet.operation).to.eq(ResourceOperation.RECREATE);
307
307
  })
308
308
 
@@ -327,8 +327,8 @@ describe('Resource tests', () => {
327
327
  }
328
328
 
329
329
  const plan = await resource.plan({ type: 'resource'})
330
- expect(plan.currentConfig.propE).to.eq('propEDefault');
331
- expect(plan.desiredConfig.propE).to.eq('propEDefault');
330
+ expect(plan.currentConfig?.propE).to.eq('propEDefault');
331
+ expect(plan.desiredConfig?.propE).to.eq('propEDefault');
332
332
  expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
333
333
  })
334
334
 
@@ -349,8 +349,8 @@ describe('Resource tests', () => {
349
349
  }
350
350
 
351
351
  const plan = await resource.plan({ type: 'resource'})
352
- expect(plan.currentConfig.propE).to.eq(null);
353
- expect(plan.desiredConfig.propE).to.eq('propEDefault');
352
+ expect(plan.currentConfig).to.be.null
353
+ expect(plan.desiredConfig!.propE).to.eq('propEDefault');
354
354
  expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
355
355
  })
356
356
 
@@ -377,8 +377,8 @@ describe('Resource tests', () => {
377
377
  }
378
378
 
379
379
  const plan = await resource.plan({ type: 'resource', propA: 'propA'})
380
- expect(plan.currentConfig.propA).to.eq('propAAfter');
381
- expect(plan.desiredConfig.propA).to.eq('propA');
380
+ expect(plan.currentConfig?.propA).to.eq('propAAfter');
381
+ expect(plan.desiredConfig?.propA).to.eq('propA');
382
382
  expect(plan.changeSet.operation).to.eq(ResourceOperation.RECREATE);
383
383
  });
384
384
 
@@ -4,7 +4,7 @@ import { Plan } from './plan.js';
4
4
  import { StatefulParameter } from './stateful-parameter.js';
5
5
  import { ResourceParameterOptions, ValidationResult } from './resource-types.js';
6
6
  import { setsEqual, splitUserConfig } from '../utils/utils.js';
7
- import { ParameterOptions, PlanOptions } from './plan-types.js';
7
+ import { CreatePlan, DestroyPlan, ModifyPlan, ParameterOptions, PlanOptions } from './plan-types.js';
8
8
  import { TransformParameter } from './transform-parameter.js';
9
9
  import { ResourceOptions, ResourceOptionsParser } from './resource-options.js';
10
10
  import Ajv from 'ajv';
@@ -74,38 +74,39 @@ export abstract class Resource<T extends StringIndexedObject> {
74
74
  return this.validate(parameters);
75
75
  }
76
76
 
77
- // TODO: Add state in later.
78
- // Currently only calculating how to add things to reach desired state. Can't delete resources.
79
- // Add previousConfig as a parameter for plan(desired, previous);
80
- async plan(desiredConfig: Partial<T> & ResourceConfig): Promise<Plan<T>> {
77
+ // TODO: Currently stateful mode expects that the currentConfig does not need any additional transformations (default and transform parameters)
78
+ // This may change in the future?
79
+ async plan(
80
+ desiredConfig: Partial<T> & ResourceConfig | null,
81
+ currentConfig: Partial<T> & ResourceConfig | null = null,
82
+ statefulMode = false,
83
+ ): Promise<Plan<T>> {
84
+ this.validatePlanInputs(desiredConfig, currentConfig, statefulMode);
85
+
81
86
  const planOptions: PlanOptions<T> = {
82
- statefulMode: false,
87
+ statefulMode,
83
88
  parameterOptions: this.parameterOptions,
84
89
  }
85
90
 
86
91
  this.addDefaultValues(desiredConfig);
92
+ await this.applyTransformParameters(desiredConfig);
87
93
 
88
94
  // Parse data from the user supplied config
89
- const parsedConfig = new ConfigParser(desiredConfig, this.statefulParameters, this.transformParameters)
95
+ const parsedConfig = new ConfigParser(desiredConfig, currentConfig, this.statefulParameters, this.transformParameters)
90
96
  const {
91
- parameters: desiredParameters,
97
+ desiredParameters,
92
98
  resourceMetadata,
93
- resourceParameters,
99
+ nonStatefulParameters,
94
100
  statefulParameters,
95
- transformParameters,
96
101
  } = parsedConfig;
97
102
 
98
- // Apply transform parameters. Transform parameters turn into other parameters.
99
- // Ex: csvFile: './location' => { password: 'pass', 'username': 'user' }
100
- await this.applyTransformParameters(transformParameters, resourceParameters);
101
-
102
103
  // Refresh resource parameters. This refreshes the parameters that configure the resource itself
103
- const currentParameters = await this.refreshResourceParameters(resourceParameters);
104
+ const currentParameters = await this.refreshNonStatefulParameters(nonStatefulParameters);
104
105
 
105
106
  // Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
106
107
  if (currentParameters == null) {
107
108
  return Plan.create(
108
- { ...resourceParameters, ...statefulParameters },
109
+ desiredParameters,
109
110
  null,
110
111
  resourceMetadata,
111
112
  planOptions,
@@ -116,7 +117,7 @@ export abstract class Resource<T extends StringIndexedObject> {
116
117
  const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
117
118
 
118
119
  return Plan.create(
119
- { ...resourceParameters, ...statefulParameters },
120
+ desiredParameters,
120
121
  { ...currentParameters, ...statefulCurrentParameters } as Partial<T>,
121
122
  resourceMetadata,
122
123
  planOptions,
@@ -232,17 +233,29 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
232
233
  }
233
234
  }
234
235
 
235
- private async applyTransformParameters(transformParameters: Partial<T>, desired: Partial<T>): Promise<void> {
236
- const orderedEntries = [...Object.entries(transformParameters)]
236
+ private async applyTransformParameters(desired: Partial<T> | null): Promise<void> {
237
+ if (!desired) {
238
+ return;
239
+ }
240
+
241
+ const transformParameters = [...this.transformParameters.entries()]
237
242
  .sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA)! - this.transformParameterOrder.get(keyB)!)
238
243
 
239
- for (const [key, value] of orderedEntries) {
240
- const transformedValue = await this.transformParameters.get(key)!.transform(value);
244
+ for (const [key, transformParameter] of transformParameters) {
245
+ if (desired[key] === undefined) {
246
+ continue;
247
+ }
248
+
249
+ const transformedValue = await transformParameter.transform(desired[key]);
241
250
 
242
251
  if (Object.keys(transformedValue).some((k) => desired[k] !== undefined)) {
243
252
  throw new Error(`Transform parameter ${key as string} is attempting to override existing values ${JSON.stringify(transformedValue, null, 2)}`);
244
253
  }
245
254
 
255
+ // Remove original transform parameter from the config
256
+ delete desired[key];
257
+
258
+ // Add the new transformed values
246
259
  Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
247
260
  // @ts-ignore
248
261
  desired[tvKey] = tvValue;
@@ -250,7 +263,11 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
250
263
  }
251
264
  }
252
265
 
253
- private addDefaultValues(desired: Partial<T>): void {
266
+ private addDefaultValues(desired: Partial<T> | null): void {
267
+ if (!desired) {
268
+ return;
269
+ }
270
+
254
271
  Object.entries(this.defaultValues)
255
272
  .forEach(([key, defaultValue]) => {
256
273
  if (defaultValue !== undefined && desired[key as any] === undefined) {
@@ -260,10 +277,11 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
260
277
  });
261
278
  }
262
279
 
263
- private async refreshResourceParameters(resourceParameters: Partial<T>): Promise<Partial<T> | null> {
264
- const entriesToRefresh = new Map(Object.entries(resourceParameters));
280
+ private async refreshNonStatefulParameters(resourceParameters: Partial<T>): Promise<Partial<T> | null> {
281
+ const entriesToRefresh = new Map<keyof T, T[keyof T]>(
282
+ Object.entries(resourceParameters)
283
+ )
265
284
  const currentParameters = await this.refresh(entriesToRefresh);
266
-
267
285
  this.validateRefreshResults(currentParameters, entriesToRefresh);
268
286
  return currentParameters;
269
287
  }
@@ -308,67 +326,106 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
308
326
  return currentParameters;
309
327
  }
310
328
 
329
+ private validatePlanInputs(
330
+ desired: Partial<T> & ResourceConfig | null,
331
+ current: Partial<T> & ResourceConfig | null,
332
+ statefulMode: boolean,
333
+ ) {
334
+ if (!desired && !current) {
335
+ throw new Error('Desired config and current config cannot both be missing')
336
+ }
337
+
338
+ if (!statefulMode && !desired) {
339
+ throw new Error('Desired config must be provided in non-stateful mode')
340
+ }
341
+ }
342
+
311
343
  async validate(parameters: unknown): Promise<ValidationResult> {
312
344
  return {
313
345
  isValid: true,
314
346
  }
315
347
  };
316
348
 
317
- abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
349
+ abstract refresh(values: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
318
350
 
319
- abstract applyCreate(plan: Plan<T>): Promise<void>;
351
+ abstract applyCreate(plan: CreatePlan<T>): Promise<void>;
320
352
 
321
- async applyModify(pc: ParameterChange<T>, plan: Plan<T>): Promise<void> {};
353
+ async applyModify(pc: ParameterChange<T>, plan: ModifyPlan<T>): Promise<void> {};
322
354
 
323
- abstract applyDestroy(plan: Plan<T>): Promise<void>;
355
+ abstract applyDestroy(plan: DestroyPlan<T>): Promise<void>;
324
356
  }
325
357
 
326
358
  class ConfigParser<T extends StringIndexedObject> {
327
- private config: Partial<T> & ResourceConfig;
359
+ private desiredConfig: Partial<T> & ResourceConfig | null;
360
+ private currentConfig: Partial<T> & ResourceConfig | null;
328
361
  private statefulParametersMap: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
329
362
  private transformParametersMap: Map<keyof T, TransformParameter<T>>;
330
363
 
331
364
  constructor(
332
- config: Partial<T> & ResourceConfig,
365
+ desiredConfig: Partial<T> & ResourceConfig | null,
366
+ currentConfig: Partial<T> & ResourceConfig | null,
333
367
  statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>,
334
- transformParameters: Map<keyof T, TransformParameter<T>>,
368
+ transformParameters: Map<keyof T, TransformParameter<T>>,
335
369
  ) {
336
- this.config = config;
370
+ this.desiredConfig = desiredConfig;
371
+ this.currentConfig = currentConfig
337
372
  this.statefulParametersMap = statefulParameters;
338
373
  this.transformParametersMap = transformParameters;
339
374
  }
340
375
 
341
376
  get resourceMetadata(): ResourceConfig {
342
- const { resourceMetadata } = splitUserConfig(this.config);
343
- return resourceMetadata;
377
+ const desiredMetadata = this.desiredConfig ? splitUserConfig(this.desiredConfig).resourceMetadata : undefined;
378
+ const currentMetadata = this.currentConfig ? splitUserConfig(this.currentConfig).resourceMetadata : undefined;
379
+
380
+ if (!desiredMetadata && !currentMetadata) {
381
+ throw new Error(`Unable to parse resource metadata from ${this.desiredConfig}, ${this.currentConfig}`)
382
+ }
383
+
384
+ if (currentMetadata && desiredMetadata && (
385
+ Object.keys(desiredMetadata).length !== Object.keys(currentMetadata).length
386
+ || Object.entries(desiredMetadata).some(([key, value]) => currentMetadata[key] !== value)
387
+ )) {
388
+ throw new Error(`The metadata for the current config does not match the desired config.
389
+ Desired metadata:
390
+ ${JSON.stringify(desiredMetadata, null, 2)}
391
+
392
+ Current metadata:
393
+ ${JSON.stringify(currentMetadata, null, 2)}`);
394
+ }
395
+
396
+ return desiredMetadata ?? currentMetadata!;
344
397
  }
345
398
 
346
- get parameters(): Partial<T> {
347
- const { parameters } = splitUserConfig(this.config);
399
+ get desiredParameters(): Partial<T> | null {
400
+ if (!this.desiredConfig) {
401
+ return null;
402
+ }
403
+
404
+ const { parameters } = splitUserConfig(this.desiredConfig);
348
405
  return parameters;
349
406
  }
350
407
 
351
- get resourceParameters(): Partial<T> {
352
- const parameters = this.parameters;
353
408
 
354
- return Object.fromEntries([
355
- ...Object.entries(parameters).filter(([key]) => !(this.statefulParametersMap.has(key) || this.transformParametersMap.has(key))),
356
- ]) as Partial<T>;
409
+ get parameters(): Partial<T> {
410
+ const desiredParameters = this.desiredConfig ? splitUserConfig(this.desiredConfig).parameters : undefined;
411
+ const currentParameters = this.currentConfig ? splitUserConfig(this.currentConfig).parameters : undefined;
412
+
413
+ return { ...(desiredParameters ?? {}), ...(currentParameters ?? {}) } as Partial<T>;
357
414
  }
358
415
 
359
- get statefulParameters(): Partial<T> {
416
+ get nonStatefulParameters(): Partial<T> {
360
417
  const parameters = this.parameters;
361
418
 
362
419
  return Object.fromEntries([
363
- ...Object.entries(parameters).filter(([key]) => this.statefulParametersMap.has(key)),
420
+ ...Object.entries(parameters).filter(([key]) => !(this.statefulParametersMap.has(key) || this.transformParametersMap.has(key))),
364
421
  ]) as Partial<T>;
365
422
  }
366
423
 
367
- get transformParameters(): Partial<T> {
424
+ get statefulParameters(): Partial<T> {
368
425
  const parameters = this.parameters;
369
426
 
370
427
  return Object.fromEntries([
371
- ...Object.entries(parameters).filter(([key]) => this.transformParametersMap.has(key)),
428
+ ...Object.entries(parameters).filter(([key]) => this.statefulParametersMap.has(key)),
372
429
  ]) as Partial<T>;
373
430
  }
374
431
  }
@@ -10,10 +10,8 @@ interface TestConfig {
10
10
  }
11
11
 
12
12
  class TestArrayParameter extends ArrayStatefulParameter<TestConfig, string> {
13
- constructor(options?: ArrayStatefulParameterOptions<TestConfig>) {
14
- super(options ?? {
15
- name: 'propA'
16
- })
13
+ constructor(options?: ArrayStatefulParameterOptions<string>) {
14
+ super(options)
17
15
  }
18
16
 
19
17
  async applyAddItem(item: string, plan: Plan<TestConfig>): Promise<void> {}
@@ -104,7 +102,6 @@ describe('Stateful parameter tests', () => {
104
102
  const testParameter = spy(new class extends TestArrayParameter {
105
103
  constructor() {
106
104
  super({
107
- name: 'propA',
108
105
  isElementEqual: (desired, current) => current.includes(desired),
109
106
  });
110
107
  }
package/src/index.ts CHANGED
@@ -11,7 +11,6 @@ export * from './entities/plan-types.js'
11
11
  export * from './entities/stateful-parameter.js'
12
12
  export * from './entities/errors.js'
13
13
 
14
- export * from './utils/test-utils.js'
15
14
  export * from './utils/utils.js'
16
15
 
17
16
  export async function runPlugin(plugin: Plugin) {
@@ -1,52 +0,0 @@
1
- import { EventEmitter } from 'node:events';
2
- import { ChildProcess } from 'node:child_process';
3
- import { Readable } from 'stream';
4
- import { mock } from 'node:test';
5
- import { AssertionError } from 'chai';
6
- import { CodifyTestUtils } from './test-utils.js';
7
- import { describe, expect, it } from 'vitest';
8
-
9
- describe('Test Utils tests', async () => {
10
-
11
- const mockChildProcess = () => {
12
- const process = new ChildProcess();
13
- process.stdout = new EventEmitter() as Readable;
14
- process.stderr = new EventEmitter() as Readable
15
- process.send = () => true;
16
-
17
- return process;
18
- }
19
-
20
- it('send a message', async () => {
21
- const process = mockChildProcess();
22
- const sendMock = mock.method(process, 'send');
23
-
24
- CodifyTestUtils.sendMessageToProcessAwaitResponse(process, { cmd: 'message', data: 'data' })
25
-
26
- expect(sendMock.mock.calls.length).to.eq(1);
27
- expect(sendMock.mock.calls[0].arguments[0]).to.deep.eq({ cmd: 'message', data: 'data' });
28
- })
29
-
30
- it('send a message and receives the response', async () => {
31
- const process = mockChildProcess();
32
-
33
- try {
34
- const result = await Promise.all([
35
- (async () => {
36
- await sleep(30);
37
- process.emit('message', { cmd: 'messageResult', data: 'data' })
38
- })(),
39
- CodifyTestUtils.sendMessageToProcessAwaitResponse(process, { cmd: 'message', data: 'data' }),
40
- ]);
41
-
42
- expect(result[1]).to.deep.eq({ cmd: 'messageResult', data: 'data' })
43
- } catch (e) {
44
- console.log(e);
45
- throw new AssertionError('Failed to receive message');
46
- }
47
- });
48
- });
49
-
50
- async function sleep(ms: number) {
51
- return new Promise((resolve, reject) => setTimeout(resolve, ms))
52
- }
@@ -1,20 +0,0 @@
1
- import { ChildProcess } from 'child_process';
2
-
3
- export class CodifyTestUtils {
4
- static sendMessageToProcessAwaitResponse(process: ChildProcess, message: any): Promise<any> {
5
- return new Promise((resolve, reject) => {
6
- process.on('message', (response) => {
7
- resolve(response)
8
- });
9
- process.on('error', (err) => reject(err))
10
- process.on('exit', (code) => {
11
- if (code != 0) {
12
- reject('Exit code is not 0');
13
- }
14
- resolve(code);
15
- })
16
- process.send(message);
17
- });
18
- }
19
-
20
- }