codify-plugin-lib 1.0.55 → 1.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entities/plan-types.d.ts +1 -2
- package/dist/entities/plan.js +2 -2
- package/dist/entities/plugin.d.ts +2 -1
- package/dist/entities/plugin.js +3 -1
- package/dist/entities/resource-types.d.ts +1 -2
- package/dist/entities/resource.d.ts +2 -1
- package/dist/entities/resource.js +10 -13
- package/dist/index.d.ts +0 -1
- package/dist/messages/handlers.d.ts +1 -0
- package/dist/messages/handlers.js +34 -25
- package/package.json +1 -1
- package/src/entities/plan-types.ts +2 -4
- package/src/entities/plan.ts +2 -2
- package/src/entities/plugin.ts +1 -0
- package/src/entities/resource-parameters.test.ts +1 -1
- package/src/entities/resource-types.ts +3 -5
- package/src/entities/resource.test.ts +4 -4
- package/src/entities/resource.ts +12 -16
- package/src/entities/transform-parameter.ts +6 -0
- package/src/index.ts +0 -1
- package/src/messages/handlers.test.ts +142 -9
- package/src/messages/handlers.ts +42 -27
- package/src/utils/test-utils.test.ts +2 -2
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ResourceOperation } from 'codify-schemas';
|
|
2
1
|
export interface ParameterOptions {
|
|
3
|
-
|
|
2
|
+
canModify?: boolean;
|
|
4
3
|
isEqual?: (desired: any, current: any) => boolean;
|
|
5
4
|
isElementEqual?: (desired: any, current: any) => boolean;
|
|
6
5
|
default?: unknown;
|
package/dist/entities/plan.js
CHANGED
|
@@ -31,8 +31,8 @@ export class Plan {
|
|
|
31
31
|
if (statefulParameterNames.has(curr.name)) {
|
|
32
32
|
newOperation = ResourceOperation.MODIFY;
|
|
33
33
|
}
|
|
34
|
-
else if (parameterOptions[curr.name]?.
|
|
35
|
-
newOperation = parameterOptions[curr.name].
|
|
34
|
+
else if (parameterOptions[curr.name]?.canModify) {
|
|
35
|
+
newOperation = parameterOptions[curr.name].canModify ? ResourceOperation.MODIFY : ResourceOperation.RECREATE;
|
|
36
36
|
}
|
|
37
37
|
else {
|
|
38
38
|
newOperation = ResourceOperation.RECREATE;
|
|
@@ -2,9 +2,10 @@ import { Resource } from './resource.js';
|
|
|
2
2
|
import { ApplyRequestData, InitializeResponseData, PlanRequestData, PlanResponseData, ResourceConfig, ValidateRequestData, ValidateResponseData } from 'codify-schemas';
|
|
3
3
|
import { Plan } from './plan.js';
|
|
4
4
|
export declare class Plugin {
|
|
5
|
+
name: string;
|
|
5
6
|
resources: Map<string, Resource<ResourceConfig>>;
|
|
6
7
|
planStorage: Map<string, Plan<ResourceConfig>>;
|
|
7
|
-
constructor(resources: Map<string, Resource<ResourceConfig>>);
|
|
8
|
+
constructor(name: string, resources: Map<string, Resource<ResourceConfig>>);
|
|
8
9
|
initialize(): Promise<InitializeResponseData>;
|
|
9
10
|
validate(data: ValidateRequestData): Promise<ValidateResponseData>;
|
|
10
11
|
plan(data: PlanRequestData): Promise<PlanResponseData>;
|
package/dist/entities/plugin.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Plan } from './plan.js';
|
|
2
2
|
import { splitUserConfig } from '../utils/utils.js';
|
|
3
3
|
export class Plugin {
|
|
4
|
+
name;
|
|
4
5
|
resources;
|
|
5
6
|
planStorage;
|
|
6
|
-
constructor(resources) {
|
|
7
|
+
constructor(name, resources) {
|
|
8
|
+
this.name = name;
|
|
7
9
|
this.resources = resources;
|
|
8
10
|
this.planStorage = new Map();
|
|
9
11
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { ResourceOperation } from 'codify-schemas';
|
|
2
1
|
export type ErrorMessage = string;
|
|
3
2
|
export interface ResourceParameterOptions {
|
|
4
|
-
|
|
3
|
+
canModify?: boolean;
|
|
5
4
|
isEqual?: (desired: any, current: any) => boolean;
|
|
6
5
|
default?: unknown;
|
|
7
6
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
|
|
2
|
+
import { ParameterChange } from './change-set.js';
|
|
2
3
|
import { Plan } from './plan.js';
|
|
3
4
|
import { StatefulParameter } from './stateful-parameter.js';
|
|
4
5
|
import { ResourceParameterOptions, ValidationResult } from './resource-types.js';
|
|
@@ -36,6 +37,6 @@ export declare abstract class Resource<T extends StringIndexedObject> {
|
|
|
36
37
|
validate(parameters: unknown): Promise<ValidationResult>;
|
|
37
38
|
abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
|
|
38
39
|
abstract applyCreate(plan: Plan<T>): Promise<void>;
|
|
39
|
-
applyModify(
|
|
40
|
+
applyModify(pc: ParameterChange<T>, plan: Plan<T>): Promise<void>;
|
|
40
41
|
abstract applyDestroy(plan: Plan<T>): Promise<void>;
|
|
41
42
|
}
|
|
@@ -60,7 +60,7 @@ export class Resource {
|
|
|
60
60
|
await this.applyTransformParameters(transformParameters, resourceParameters);
|
|
61
61
|
const currentParameters = await this.refreshResourceParameters(resourceParameters);
|
|
62
62
|
if (currentParameters == null) {
|
|
63
|
-
return Plan.create(
|
|
63
|
+
return Plan.create(resourceParameters, null, resourceMetadata, planOptions);
|
|
64
64
|
}
|
|
65
65
|
const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
|
|
66
66
|
return Plan.create({ ...resourceParameters, ...statefulParameters }, { ...currentParameters, ...statefulCurrentParameters }, resourceMetadata, planOptions);
|
|
@@ -103,7 +103,7 @@ export class Resource {
|
|
|
103
103
|
const statelessParameterChanges = parameterChanges
|
|
104
104
|
.filter((pc) => !this.statefulParameters.has(pc.name));
|
|
105
105
|
for (const pc of statelessParameterChanges) {
|
|
106
|
-
await this.applyModify(pc
|
|
106
|
+
await this.applyModify(pc, plan);
|
|
107
107
|
}
|
|
108
108
|
const statefulParameterChanges = parameterChanges
|
|
109
109
|
.filter((pc) => this.statefulParameters.has(pc.name))
|
|
@@ -154,17 +154,14 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
|
|
|
154
154
|
async applyTransformParameters(transformParameters, desired) {
|
|
155
155
|
const orderedEntries = [...Object.entries(transformParameters)]
|
|
156
156
|
.sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA) - this.transformParameterOrder.get(keyB));
|
|
157
|
-
for (const [key] of orderedEntries) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
throw new Error(`Transform parameter ${key} is attempting to override existing value ${desired[key]}`);
|
|
162
|
-
}
|
|
163
|
-
Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
|
|
164
|
-
desired[tvKey] = tvValue;
|
|
165
|
-
});
|
|
166
|
-
delete desired[key];
|
|
157
|
+
for (const [key, value] of orderedEntries) {
|
|
158
|
+
const transformedValue = await this.transformParameters.get(key).transform(value);
|
|
159
|
+
if (Object.keys(transformedValue).some((k) => desired[k] !== undefined)) {
|
|
160
|
+
throw new Error(`Transform parameter ${key} is attempting to override existing values ${JSON.stringify(transformedValue, null, 2)}`);
|
|
167
161
|
}
|
|
162
|
+
Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
|
|
163
|
+
desired[tvKey] = tvValue;
|
|
164
|
+
});
|
|
168
165
|
}
|
|
169
166
|
}
|
|
170
167
|
addDefaultValues(desired) {
|
|
@@ -213,7 +210,7 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
|
|
|
213
210
|
};
|
|
214
211
|
}
|
|
215
212
|
;
|
|
216
|
-
async applyModify(
|
|
213
|
+
async applyModify(pc, plan) { }
|
|
217
214
|
;
|
|
218
215
|
}
|
|
219
216
|
class ConfigParser {
|
package/dist/index.d.ts
CHANGED
|
@@ -44,39 +44,48 @@ export class MessageHandler {
|
|
|
44
44
|
.map(([k, v]) => [k, this.ajv.compile(v.responseValidator)]));
|
|
45
45
|
}
|
|
46
46
|
async onMessage(message) {
|
|
47
|
-
if (!this.validateMessage(message)) {
|
|
48
|
-
throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.messageSchemaValidator.errors, null, 2)}`);
|
|
49
|
-
}
|
|
50
|
-
if (!this.requestValidators.has(message.cmd)) {
|
|
51
|
-
throw new Error(`Plugin: ${this.plugin}. Unsupported message: ${message.cmd}`);
|
|
52
|
-
}
|
|
53
|
-
const requestValidator = this.requestValidators.get(message.cmd);
|
|
54
|
-
if (!requestValidator(message.data)) {
|
|
55
|
-
throw new Error(`Plugin: ${this.plugin}. cmd: ${message.cmd}. Malformed message data: ${JSON.stringify(requestValidator.errors, null, 2)}`);
|
|
56
|
-
}
|
|
57
|
-
let result;
|
|
58
47
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
if (!this.validateMessage(message)) {
|
|
49
|
+
throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.messageSchemaValidator.errors, null, 2)}`);
|
|
50
|
+
}
|
|
51
|
+
if (!this.requestValidators.has(message.cmd)) {
|
|
52
|
+
throw new Error(`Plugin: ${this.plugin}. Unsupported message: ${message.cmd}`);
|
|
53
|
+
}
|
|
54
|
+
const requestValidator = this.requestValidators.get(message.cmd);
|
|
55
|
+
if (!requestValidator(message.data)) {
|
|
56
|
+
throw new Error(`Plugin: ${this.plugin}. cmd: ${message.cmd}. Malformed message data: ${JSON.stringify(requestValidator.errors, null, 2)}`);
|
|
57
|
+
}
|
|
58
|
+
const result = await SupportedRequests[message.cmd].handler(this.plugin, message.data);
|
|
59
|
+
const responseValidator = this.responseValidators.get(message.cmd);
|
|
60
|
+
if (responseValidator && !responseValidator(result)) {
|
|
61
|
+
throw new Error(`Plugin: ${this.plugin}. Malformed response data: ${JSON.stringify(responseValidator.errors, null, 2)}`);
|
|
62
|
+
}
|
|
62
63
|
process.send({
|
|
63
64
|
cmd: message.cmd + '_Response',
|
|
64
|
-
status: MessageStatus.
|
|
65
|
-
data:
|
|
65
|
+
status: MessageStatus.SUCCESS,
|
|
66
|
+
data: result,
|
|
66
67
|
});
|
|
67
|
-
return;
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
throw new Error(`Plugin: ${this.plugin}. Malformed response data: ${JSON.stringify(responseValidator.errors, null, 2)}`);
|
|
69
|
+
catch (e) {
|
|
70
|
+
this.handleErrors(message, e);
|
|
72
71
|
}
|
|
73
|
-
process.send({
|
|
74
|
-
cmd: message.cmd + '_Response',
|
|
75
|
-
status: MessageStatus.SUCCESS,
|
|
76
|
-
data: result,
|
|
77
|
-
});
|
|
78
72
|
}
|
|
79
73
|
validateMessage(message) {
|
|
80
74
|
return this.messageSchemaValidator(message);
|
|
81
75
|
}
|
|
76
|
+
handleErrors(message, e) {
|
|
77
|
+
if (!message) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!message.hasOwnProperty('cmd')) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const cmd = message.cmd + '_Response';
|
|
84
|
+
const isDebug = process.env.DEBUG?.includes('*') ?? false;
|
|
85
|
+
process.send?.({
|
|
86
|
+
cmd,
|
|
87
|
+
status: MessageStatus.ERROR,
|
|
88
|
+
data: isDebug ? e.stack : e.message,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
82
91
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { ResourceOperation } from 'codify-schemas';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Customize properties for specific parameters. This will alter the way the library process changes to the parameter.
|
|
5
3
|
*/
|
|
6
4
|
export interface ParameterOptions {
|
|
7
5
|
/**
|
|
8
|
-
* Chose if the resource should be re-created or modified if this parameter is changed. Defaults to re-
|
|
6
|
+
* Chose if the resource should be re-created or modified if this parameter is changed. Defaults to false (re-creates resource on change).
|
|
9
7
|
*/
|
|
10
|
-
|
|
8
|
+
canModify?: boolean;
|
|
11
9
|
/**
|
|
12
10
|
* Customize the equality comparison for a parameter.
|
|
13
11
|
* @param a
|
package/src/entities/plan.ts
CHANGED
|
@@ -55,8 +55,8 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
55
55
|
let newOperation: ResourceOperation;
|
|
56
56
|
if (statefulParameterNames.has(curr.name)) {
|
|
57
57
|
newOperation = ResourceOperation.MODIFY // All stateful parameters are modify only
|
|
58
|
-
} else if (parameterOptions[curr.name]?.
|
|
59
|
-
newOperation = parameterOptions[curr.name].
|
|
58
|
+
} else if (parameterOptions[curr.name]?.canModify) {
|
|
59
|
+
newOperation = parameterOptions[curr.name].canModify ? ResourceOperation.MODIFY : ResourceOperation.RECREATE;
|
|
60
60
|
} else {
|
|
61
61
|
newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
|
|
62
62
|
}
|
package/src/entities/plugin.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { ResourceOperation } from 'codify-schemas';
|
|
2
|
-
|
|
3
1
|
export type ErrorMessage = string;
|
|
4
2
|
|
|
5
3
|
/**
|
|
@@ -7,9 +5,9 @@ export type ErrorMessage = string;
|
|
|
7
5
|
*/
|
|
8
6
|
export interface ResourceParameterOptions {
|
|
9
7
|
/**
|
|
10
|
-
* Chose if the resource should be re-created or modified if this parameter is changed. Defaults to re-create.
|
|
8
|
+
* Chose if the resource should be re-created or modified if this parameter is changed. Defaults to false (re-create).
|
|
11
9
|
*/
|
|
12
|
-
|
|
10
|
+
canModify?: boolean;
|
|
13
11
|
/**
|
|
14
12
|
* Customize the equality comparison for a parameter.
|
|
15
13
|
* @param desired
|
|
@@ -19,7 +17,7 @@ export interface ResourceParameterOptions {
|
|
|
19
17
|
/**
|
|
20
18
|
* Default value for the parameter. If a value is not provided in the config, the library will use this value.
|
|
21
19
|
*/
|
|
22
|
-
default?: unknown
|
|
20
|
+
default?: unknown;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
/**
|
|
@@ -195,8 +195,8 @@ describe('Resource tests', () => {
|
|
|
195
195
|
super({
|
|
196
196
|
type: 'resource',
|
|
197
197
|
parameterOptions: {
|
|
198
|
-
propA: {
|
|
199
|
-
propB: {
|
|
198
|
+
propA: { canModify: true },
|
|
199
|
+
propB: { canModify: true },
|
|
200
200
|
}
|
|
201
201
|
});
|
|
202
202
|
}
|
|
@@ -244,7 +244,7 @@ describe('Resource tests', () => {
|
|
|
244
244
|
type: 'type',
|
|
245
245
|
dependencies: ['homebrew', 'python'],
|
|
246
246
|
parameterOptions: {
|
|
247
|
-
propA: {
|
|
247
|
+
propA: { canModify: true },
|
|
248
248
|
propB: { statefulParameter },
|
|
249
249
|
propC: { isEqual: (a, b) => true },
|
|
250
250
|
}
|
|
@@ -281,7 +281,7 @@ describe('Resource tests', () => {
|
|
|
281
281
|
type: 'type',
|
|
282
282
|
dependencies: ['homebrew', 'python'],
|
|
283
283
|
parameterOptions: {
|
|
284
|
-
propA: {
|
|
284
|
+
propA: { canModify: true },
|
|
285
285
|
propB: { statefulParameter },
|
|
286
286
|
propC: { isEqual: (a, b) => true },
|
|
287
287
|
}
|
package/src/entities/resource.ts
CHANGED
|
@@ -101,7 +101,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
101
101
|
|
|
102
102
|
// Short circuit here. If the resource is non-existent, there's no point checking stateful parameters
|
|
103
103
|
if (currentParameters == null) {
|
|
104
|
-
return Plan.create(
|
|
104
|
+
return Plan.create(resourceParameters, null, resourceMetadata, planOptions);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// Refresh stateful parameters. These parameters have state external to the resource
|
|
@@ -161,7 +161,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
161
161
|
|
|
162
162
|
for (const pc of statelessParameterChanges) {
|
|
163
163
|
// TODO: When stateful mode is added in the future. Dynamically choose if deletes are allowed
|
|
164
|
-
await this.applyModify(pc
|
|
164
|
+
await this.applyModify(pc, plan);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
const statefulParameterChanges = parameterChanges
|
|
@@ -228,21 +228,17 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
228
228
|
const orderedEntries = [...Object.entries(transformParameters)]
|
|
229
229
|
.sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA)! - this.transformParameterOrder.get(keyB)!)
|
|
230
230
|
|
|
231
|
-
for (const [key] of orderedEntries) {
|
|
232
|
-
|
|
233
|
-
const transformedValue = await this.transformParameters.get(key)!.transform(desired[key]);
|
|
231
|
+
for (const [key, value] of orderedEntries) {
|
|
232
|
+
const transformedValue = await this.transformParameters.get(key)!.transform(value);
|
|
234
233
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
|
|
240
|
-
// @ts-ignore
|
|
241
|
-
desired[tvKey] = tvValue;
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
delete desired[key];
|
|
234
|
+
if (Object.keys(transformedValue).some((k) => desired[k] !== undefined)) {
|
|
235
|
+
throw new Error(`Transform parameter ${key as string} is attempting to override existing values ${JSON.stringify(transformedValue, null, 2)}`);
|
|
245
236
|
}
|
|
237
|
+
|
|
238
|
+
Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
|
|
239
|
+
// @ts-ignore
|
|
240
|
+
desired[tvKey] = tvValue;
|
|
241
|
+
})
|
|
246
242
|
}
|
|
247
243
|
}
|
|
248
244
|
|
|
@@ -314,7 +310,7 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`
|
|
|
314
310
|
|
|
315
311
|
abstract applyCreate(plan: Plan<T>): Promise<void>;
|
|
316
312
|
|
|
317
|
-
async applyModify(
|
|
313
|
+
async applyModify(pc: ParameterChange<T>, plan: Plan<T>): Promise<void> {};
|
|
318
314
|
|
|
319
315
|
abstract applyDestroy(plan: Plan<T>): Promise<void>;
|
|
320
316
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { StringIndexedObject } from 'codify-schemas';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Transform parameters convert the provided value into
|
|
5
|
+
* other parameters. Transform parameters will not show up
|
|
6
|
+
* in the refresh or the plan. Transform parameters get processed after
|
|
7
|
+
* default values.
|
|
8
|
+
*/
|
|
3
9
|
export abstract class TransformParameter<T extends StringIndexedObject> {
|
|
4
10
|
|
|
5
11
|
abstract transform(value: any): Promise<Partial<T>>
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { MessageHandler } from './handlers.js';
|
|
2
2
|
import { Plugin } from '../entities/plugin.js';
|
|
3
|
-
import { describe,
|
|
4
|
-
import { vi } from 'vitest'
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
5
4
|
import { mock } from 'vitest-mock-extended'
|
|
5
|
+
import { Resource } from '../entities/resource.js';
|
|
6
|
+
import { Plan } from '../entities/plan.js';
|
|
7
|
+
import { MessageStatus, ResourceOperation } from 'codify-schemas';
|
|
6
8
|
|
|
7
9
|
describe('Message handler tests', () => {
|
|
8
10
|
it('handles plan requests', async () => {
|
|
9
11
|
const plugin = mock<Plugin>();
|
|
10
12
|
const handler = new MessageHandler(plugin);
|
|
11
13
|
|
|
14
|
+
process.send = (message) => {
|
|
15
|
+
expect(message).toMatchObject({
|
|
16
|
+
cmd: 'plan_Response',
|
|
17
|
+
status: MessageStatus.SUCCESS,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
// Message handler also validates the response. That part does not need to be tested
|
|
13
24
|
try {
|
|
14
25
|
await handler.onMessage({
|
|
@@ -23,12 +34,23 @@ describe('Message handler tests', () => {
|
|
|
23
34
|
} catch (e) {}
|
|
24
35
|
|
|
25
36
|
expect(plugin.plan.mock.calls.length).to.eq(1);
|
|
37
|
+
process.send = undefined;
|
|
26
38
|
})
|
|
27
39
|
|
|
28
40
|
it('rejects bad plan requests', async () => {
|
|
29
41
|
const plugin = mock<Plugin>();
|
|
30
42
|
const handler = new MessageHandler(plugin);
|
|
31
43
|
|
|
44
|
+
process.send = (message) => {
|
|
45
|
+
console.log(message);
|
|
46
|
+
expect(message).toMatchObject({
|
|
47
|
+
cmd: 'plan_Response',
|
|
48
|
+
status: MessageStatus.ERROR,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
// Message handler also validates the response. That part does not need to be tested
|
|
33
55
|
try {
|
|
34
56
|
await handler.onMessage({
|
|
@@ -39,9 +61,12 @@ describe('Message handler tests', () => {
|
|
|
39
61
|
prop2: 'B',
|
|
40
62
|
}
|
|
41
63
|
})
|
|
42
|
-
} catch (e) {
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.log(e);
|
|
66
|
+
}
|
|
43
67
|
|
|
44
68
|
expect(plugin.plan.mock.calls.length).to.eq(0);
|
|
69
|
+
process.send = undefined;
|
|
45
70
|
})
|
|
46
71
|
|
|
47
72
|
it('handles apply requests', async () => {
|
|
@@ -110,15 +135,123 @@ describe('Message handler tests', () => {
|
|
|
110
135
|
const plugin = mock<Plugin>();
|
|
111
136
|
const handler = new MessageHandler(plugin);
|
|
112
137
|
|
|
138
|
+
process.send = () => true;
|
|
139
|
+
|
|
113
140
|
// Message handler also validates the response. That part does not need to be tested
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
} catch (e) {}
|
|
141
|
+
// This should not throw
|
|
142
|
+
expect(await handler.onMessage({
|
|
143
|
+
cmd: 'validate',
|
|
144
|
+
data: {}
|
|
145
|
+
})).to.eq(undefined);
|
|
120
146
|
|
|
121
147
|
expect(plugin.apply.mock.calls.length).to.be.eq(0);
|
|
122
148
|
})
|
|
123
149
|
|
|
150
|
+
it('handles errors for plan', async () => {
|
|
151
|
+
const resource= testResource();
|
|
152
|
+
const plugin = testPlugin(resource);
|
|
153
|
+
|
|
154
|
+
const handler = new MessageHandler(plugin);
|
|
155
|
+
|
|
156
|
+
process.send = (message) => {
|
|
157
|
+
expect(message).toMatchObject({
|
|
158
|
+
cmd: 'plan_Response',
|
|
159
|
+
status: MessageStatus.ERROR,
|
|
160
|
+
data: 'Refresh error',
|
|
161
|
+
})
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
expect(async () => await handler.onMessage({
|
|
166
|
+
cmd: 'plan',
|
|
167
|
+
data: {
|
|
168
|
+
type: 'resourceA'
|
|
169
|
+
}
|
|
170
|
+
})).rejects.to.not.throw;
|
|
171
|
+
|
|
172
|
+
process.send = undefined;
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('handles errors for apply (create)', async () => {
|
|
176
|
+
const resource= testResource();
|
|
177
|
+
const plugin = testPlugin(resource);
|
|
178
|
+
|
|
179
|
+
const handler = new MessageHandler(plugin);
|
|
180
|
+
|
|
181
|
+
process.send = (message) => {
|
|
182
|
+
expect(message).toMatchObject({
|
|
183
|
+
cmd: 'apply_Response',
|
|
184
|
+
status: MessageStatus.ERROR,
|
|
185
|
+
data: 'Create error',
|
|
186
|
+
})
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
expect(async () => await handler.onMessage({
|
|
191
|
+
cmd: 'apply',
|
|
192
|
+
data: {
|
|
193
|
+
plan: {
|
|
194
|
+
resourceType: 'resourceA',
|
|
195
|
+
operation: ResourceOperation.CREATE,
|
|
196
|
+
parameters: []
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
})).rejects.to.not.throw;
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('handles errors for apply (destroy)', async () => {
|
|
203
|
+
const resource= testResource();
|
|
204
|
+
const plugin = testPlugin(resource);
|
|
205
|
+
|
|
206
|
+
const handler = new MessageHandler(plugin);
|
|
207
|
+
|
|
208
|
+
process.send = (message) => {
|
|
209
|
+
expect(message).toMatchObject({
|
|
210
|
+
cmd: 'apply_Response',
|
|
211
|
+
status: MessageStatus.ERROR,
|
|
212
|
+
data: 'Destroy error',
|
|
213
|
+
})
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
expect(async () => await handler.onMessage({
|
|
218
|
+
cmd: 'apply',
|
|
219
|
+
data: {
|
|
220
|
+
plan: {
|
|
221
|
+
resourceType: 'resourceA',
|
|
222
|
+
operation: ResourceOperation.DESTROY,
|
|
223
|
+
parameters: []
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
})).rejects.to.not.throw;
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
const testResource = () => new class extends Resource<any> {
|
|
231
|
+
constructor() {
|
|
232
|
+
super({ type: 'resourceA' });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async refresh(keys: Map<keyof any, any>): Promise<Partial<any> | null> {
|
|
236
|
+
throw new Error('Refresh error');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
applyCreate(plan: Plan<any>): Promise<void> {
|
|
240
|
+
throw new Error('Create error');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
applyDestroy(plan: Plan<any>): Promise<void> {
|
|
244
|
+
throw new Error('Destroy error');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const testPlugin = (resource: Resource<any>) => new class extends Plugin {
|
|
249
|
+
constructor() {
|
|
250
|
+
const map = new Map();
|
|
251
|
+
map.set('resourceA', resource);
|
|
252
|
+
|
|
253
|
+
super('name', map);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
124
257
|
});
|
package/src/messages/handlers.ts
CHANGED
|
@@ -67,45 +67,60 @@ export class MessageHandler {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async onMessage(message: unknown): Promise<void> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
try {
|
|
71
|
+
if (!this.validateMessage(message)) {
|
|
72
|
+
throw new Error(`Plugin: ${this.plugin}. Message is malformed: ${JSON.stringify(this.messageSchemaValidator.errors, null, 2)}`);
|
|
73
|
+
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
if (!this.requestValidators.has(message.cmd)) {
|
|
76
|
+
throw new Error(`Plugin: ${this.plugin}. Unsupported message: ${message.cmd}`);
|
|
77
|
+
}
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const requestValidator = this.requestValidators.get(message.cmd)!;
|
|
80
|
+
if (!requestValidator(message.data)) {
|
|
81
|
+
throw new Error(`Plugin: ${this.plugin}. cmd: ${message.cmd}. Malformed message data: ${JSON.stringify(requestValidator.errors, null, 2)}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = await SupportedRequests[message.cmd].handler(this.plugin, message.data);
|
|
85
|
+
|
|
86
|
+
const responseValidator = this.responseValidators.get(message.cmd);
|
|
87
|
+
if (responseValidator && !responseValidator(result)) {
|
|
88
|
+
throw new Error(`Plugin: ${this.plugin}. Malformed response data: ${JSON.stringify(responseValidator.errors, null, 2)}`)
|
|
89
|
+
}
|
|
82
90
|
|
|
83
|
-
let result: unknown;
|
|
84
|
-
try {
|
|
85
|
-
result = await SupportedRequests[message.cmd].handler(this.plugin, message.data);
|
|
86
|
-
} catch(e: any) {
|
|
87
91
|
process.send!({
|
|
88
92
|
cmd: message.cmd + '_Response',
|
|
89
|
-
status: MessageStatus.
|
|
90
|
-
data:
|
|
93
|
+
status: MessageStatus.SUCCESS,
|
|
94
|
+
data: result,
|
|
91
95
|
})
|
|
92
96
|
|
|
97
|
+
} catch (e: unknown) {
|
|
98
|
+
this.handleErrors(message, e as Error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private validateMessage(message: unknown): message is IpcMessage {
|
|
103
|
+
return this.messageSchemaValidator(message);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private handleErrors(message: unknown, e: Error) {
|
|
107
|
+
if (!message) {
|
|
93
108
|
return;
|
|
94
109
|
}
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
throw new Error(`Plugin: ${this.plugin}. Malformed response data: ${JSON.stringify(responseValidator.errors, null, 2)}`)
|
|
111
|
+
if (!message.hasOwnProperty('cmd')) {
|
|
112
|
+
return;
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
status: MessageStatus.SUCCESS,
|
|
104
|
-
data: result,
|
|
105
|
-
})
|
|
106
|
-
}
|
|
115
|
+
// @ts-ignore
|
|
116
|
+
const cmd = message.cmd + '_Response';
|
|
107
117
|
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
const isDebug = process.env.DEBUG?.includes('*') ?? false;
|
|
119
|
+
|
|
120
|
+
process.send?.({
|
|
121
|
+
cmd,
|
|
122
|
+
status: MessageStatus.ERROR,
|
|
123
|
+
data: isDebug ? e.stack : e.message,
|
|
124
|
+
})
|
|
110
125
|
}
|
|
111
126
|
}
|
|
@@ -3,8 +3,8 @@ import { ChildProcess } from 'node:child_process';
|
|
|
3
3
|
import { Readable } from 'stream';
|
|
4
4
|
import { mock } from 'node:test';
|
|
5
5
|
import { AssertionError } from 'chai';
|
|
6
|
-
import { CodifyTestUtils } from './test-utils';
|
|
7
|
-
import { describe,
|
|
6
|
+
import { CodifyTestUtils } from './test-utils.js';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
9
|
describe('Test Utils tests', async () => {
|
|
10
10
|
|