codify-plugin-lib 1.0.76 → 1.0.78

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.
Files changed (74) hide show
  1. package/.eslintrc.json +11 -4
  2. package/.github/workflows/release.yaml +19 -0
  3. package/.github/workflows/unit-test-ci.yaml +19 -0
  4. package/dist/errors.d.ts +4 -0
  5. package/dist/errors.js +7 -0
  6. package/dist/index.d.ts +10 -10
  7. package/dist/index.js +9 -9
  8. package/dist/messages/handlers.d.ts +1 -1
  9. package/dist/messages/handlers.js +2 -1
  10. package/dist/plan/change-set.d.ts +47 -0
  11. package/dist/plan/change-set.js +156 -0
  12. package/dist/plan/plan-types.d.ts +23 -0
  13. package/dist/plan/plan-types.js +1 -0
  14. package/dist/plan/plan.d.ts +59 -0
  15. package/dist/plan/plan.js +228 -0
  16. package/dist/plugin/plugin.d.ts +17 -0
  17. package/dist/plugin/plugin.js +83 -0
  18. package/dist/resource/config-parser.d.ts +14 -0
  19. package/dist/resource/config-parser.js +48 -0
  20. package/dist/resource/parsed-resource-settings.d.ts +26 -0
  21. package/dist/resource/parsed-resource-settings.js +126 -0
  22. package/dist/resource/resource-controller.d.ts +30 -0
  23. package/dist/resource/resource-controller.js +249 -0
  24. package/dist/resource/resource-settings.d.ts +149 -0
  25. package/dist/resource/resource-settings.js +9 -0
  26. package/dist/resource/resource.d.ts +137 -0
  27. package/dist/resource/resource.js +44 -0
  28. package/dist/resource/stateful-parameter.d.ts +164 -0
  29. package/dist/resource/stateful-parameter.js +94 -0
  30. package/dist/utils/spawn-2.d.ts +5 -0
  31. package/dist/utils/spawn-2.js +7 -0
  32. package/dist/utils/spawn.d.ts +29 -0
  33. package/dist/utils/spawn.js +124 -0
  34. package/dist/utils/utils.d.ts +19 -3
  35. package/dist/utils/utils.js +52 -3
  36. package/package.json +5 -3
  37. package/src/index.ts +10 -11
  38. package/src/messages/handlers.test.ts +10 -37
  39. package/src/messages/handlers.ts +2 -2
  40. package/src/plan/change-set.test.ts +220 -0
  41. package/src/plan/change-set.ts +235 -0
  42. package/src/plan/plan-types.ts +27 -0
  43. package/src/{entities → plan}/plan.test.ts +35 -29
  44. package/src/plan/plan.ts +353 -0
  45. package/src/{entities → plugin}/plugin.test.ts +14 -13
  46. package/src/{entities → plugin}/plugin.ts +28 -24
  47. package/src/resource/config-parser.ts +77 -0
  48. package/src/{entities/resource-options.test.ts → resource/parsed-resource-settings.test.ts} +8 -7
  49. package/src/resource/parsed-resource-settings.ts +179 -0
  50. package/src/{entities/resource-stateful-mode.test.ts → resource/resource-controller-stateful-mode.test.ts} +36 -39
  51. package/src/{entities/resource.test.ts → resource/resource-controller.test.ts} +116 -176
  52. package/src/resource/resource-controller.ts +343 -0
  53. package/src/resource/resource-settings.test.ts +494 -0
  54. package/src/resource/resource-settings.ts +192 -0
  55. package/src/resource/resource.ts +149 -0
  56. package/src/resource/stateful-parameter.test.ts +93 -0
  57. package/src/resource/stateful-parameter.ts +217 -0
  58. package/src/utils/test-utils.test.ts +87 -0
  59. package/src/utils/utils.test.ts +2 -2
  60. package/src/utils/utils.ts +51 -5
  61. package/tsconfig.json +0 -1
  62. package/vitest.config.ts +10 -0
  63. package/src/entities/change-set.test.ts +0 -155
  64. package/src/entities/change-set.ts +0 -244
  65. package/src/entities/plan-types.ts +0 -44
  66. package/src/entities/plan.ts +0 -178
  67. package/src/entities/resource-options.ts +0 -155
  68. package/src/entities/resource-parameters.test.ts +0 -604
  69. package/src/entities/resource-types.ts +0 -31
  70. package/src/entities/resource.ts +0 -470
  71. package/src/entities/stateful-parameter.test.ts +0 -114
  72. package/src/entities/stateful-parameter.ts +0 -92
  73. package/src/entities/transform-parameter.ts +0 -13
  74. /package/src/{entities/errors.ts → errors.ts} +0 -0
@@ -0,0 +1,124 @@
1
+ import { Ajv } from 'ajv';
2
+ import { MessageCmd, SudoRequestResponseDataSchema } from 'codify-schemas';
3
+ import { spawn } from 'node:child_process';
4
+ import { SudoError } from '../errors.js';
5
+ const ajv = new Ajv({
6
+ strict: true,
7
+ });
8
+ const validateSudoRequestResponse = ajv.compile(SudoRequestResponseDataSchema);
9
+ export var SpawnStatus;
10
+ (function (SpawnStatus) {
11
+ SpawnStatus["SUCCESS"] = "success";
12
+ SpawnStatus["ERROR"] = "error";
13
+ })(SpawnStatus || (SpawnStatus = {}));
14
+ /**
15
+ *
16
+ * @param cmd Command to run. Ex: `rm -rf`
17
+ * @param opts Standard options for node spawn. Additional argument:
18
+ * throws determines if a shell will throw a JS error. Defaults to true
19
+ *
20
+ * @see promiseSpawn
21
+ * @see spawn
22
+ *
23
+ * @returns SpawnResult { status: SUCCESS | ERROR; data: string }
24
+ */
25
+ export async function $(cmd, opts) {
26
+ const throws = opts?.throws ?? true;
27
+ console.log(`Running command: ${cmd}`);
28
+ try {
29
+ // TODO: Need to benchmark the effects of using sh vs zsh for shell.
30
+ // Seems like zsh shells run slower
31
+ let result;
32
+ if (!opts?.requiresRoot) {
33
+ result = await internalSpawn(cmd, opts ?? {});
34
+ }
35
+ else {
36
+ result = await externalSpawnWithSudo(cmd, opts);
37
+ }
38
+ if (result.status !== SpawnStatus.SUCCESS) {
39
+ throw new Error(result.data);
40
+ }
41
+ return result;
42
+ }
43
+ catch (error) {
44
+ if (isDebug()) {
45
+ console.error(`CodifySpawn error for command ${cmd}`, error);
46
+ }
47
+ if (error.message?.startsWith('sudo:')) {
48
+ throw new SudoError(cmd);
49
+ }
50
+ if (throws) {
51
+ throw error;
52
+ }
53
+ if (error instanceof Error) {
54
+ return {
55
+ status: SpawnStatus.ERROR,
56
+ data: error.message,
57
+ };
58
+ }
59
+ return {
60
+ status: SpawnStatus.ERROR,
61
+ data: error + '',
62
+ };
63
+ }
64
+ }
65
+ async function internalSpawn(cmd, opts) {
66
+ return new Promise((resolve, reject) => {
67
+ const output = [];
68
+ // Source start up shells to emulate a users environment vs. a non-interactive non-login shell script
69
+ // Ignore all stdin
70
+ const _process = spawn(`source ~/.zshrc; ${cmd}`, [], {
71
+ ...opts,
72
+ stdio: ['ignore', 'pipe', 'pipe'],
73
+ shell: 'zsh',
74
+ });
75
+ const { stdout, stderr, stdin } = _process;
76
+ stdout.setEncoding('utf8');
77
+ stderr.setEncoding('utf8');
78
+ stdout.on('data', (data) => {
79
+ output.push(data.toString());
80
+ });
81
+ stderr.on('data', (data) => {
82
+ output.push(data.toString());
83
+ });
84
+ _process.on('error', (data) => {
85
+ });
86
+ // please node that this is not a full replacement for 'inherit'
87
+ // the child process can and will detect if stdout is a pty and change output based on it
88
+ // the terminal context is lost & ansi information (coloring) etc will be lost
89
+ if (stdout && stderr) {
90
+ stdout.pipe(process.stdout);
91
+ stderr.pipe(process.stderr);
92
+ }
93
+ _process.on('close', (code) => {
94
+ resolve({
95
+ status: code === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR,
96
+ data: output.join('\n'),
97
+ });
98
+ });
99
+ });
100
+ }
101
+ async function externalSpawnWithSudo(cmd, opts) {
102
+ return await new Promise((resolve) => {
103
+ const listener = (data) => {
104
+ if (data.cmd === MessageCmd.SUDO_REQUEST + '_Response') {
105
+ process.removeListener('message', listener);
106
+ if (!validateSudoRequestResponse(data.data)) {
107
+ throw new Error(`Invalid response for sudo request: ${JSON.stringify(validateSudoRequestResponse.errors, null, 2)}`);
108
+ }
109
+ resolve(data.data);
110
+ }
111
+ };
112
+ process.on('message', listener);
113
+ process.send({
114
+ cmd: MessageCmd.SUDO_REQUEST,
115
+ data: {
116
+ command: cmd,
117
+ options: opts ?? {},
118
+ }
119
+ });
120
+ });
121
+ }
122
+ export function isDebug() {
123
+ return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
124
+ }
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { SpawnOptions } from 'child_process';
3
2
  import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
3
+ import { SpawnOptions } from 'node:child_process';
4
+ import { ArrayParameterSetting } from '../resource/resource-settings.js';
4
5
  export declare enum SpawnStatus {
5
6
  SUCCESS = "success",
6
7
  ERROR = "error"
@@ -13,13 +14,28 @@ type CodifySpawnOptions = {
13
14
  cwd?: string;
14
15
  stdioString?: boolean;
15
16
  } & SpawnOptions;
17
+ /**
18
+ *
19
+ * @param cmd Command to run. Ex: `rm -rf`
20
+ * @param args Optional additional arguments to append
21
+ * @param opts Standard options for node spawn. Additional argument:
22
+ * throws determines if a shell will throw a JS error. Defaults to true
23
+ * @param extras From PromiseSpawn
24
+ *
25
+ * @see promiseSpawn
26
+ * @see spawn
27
+ *
28
+ * @returns SpawnResult { status: SUCCESS | ERROR; data: string }
29
+ */
16
30
  export declare function codifySpawn(cmd: string, args?: string[], opts?: Omit<CodifySpawnOptions, 'stdio' | 'stdioString'> & {
17
31
  throws?: boolean;
18
32
  }, extras?: Record<any, any>): Promise<SpawnResult>;
19
33
  export declare function isDebug(): boolean;
20
- export declare function splitUserConfig<T extends StringIndexedObject>(config: T & ResourceConfig): {
34
+ export declare function splitUserConfig<T extends StringIndexedObject>(config: ResourceConfig & T): {
21
35
  parameters: T;
22
- resourceMetadata: ResourceConfig;
36
+ coreParameters: ResourceConfig;
23
37
  };
24
38
  export declare function setsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean;
39
+ export declare function untildify(pathWithTilde: string): string;
40
+ export declare function areArraysEqual(parameter: ArrayParameterSetting, desired: unknown, current: unknown): boolean;
25
41
  export {};
@@ -1,11 +1,27 @@
1
1
  import promiseSpawn from '@npmcli/promise-spawn';
2
+ import os from 'node:os';
2
3
  export var SpawnStatus;
3
4
  (function (SpawnStatus) {
4
5
  SpawnStatus["SUCCESS"] = "success";
5
6
  SpawnStatus["ERROR"] = "error";
6
7
  })(SpawnStatus || (SpawnStatus = {}));
8
+ /**
9
+ *
10
+ * @param cmd Command to run. Ex: `rm -rf`
11
+ * @param args Optional additional arguments to append
12
+ * @param opts Standard options for node spawn. Additional argument:
13
+ * throws determines if a shell will throw a JS error. Defaults to true
14
+ * @param extras From PromiseSpawn
15
+ *
16
+ * @see promiseSpawn
17
+ * @see spawn
18
+ *
19
+ * @returns SpawnResult { status: SUCCESS | ERROR; data: string }
20
+ */
7
21
  export async function codifySpawn(cmd, args, opts, extras) {
8
22
  try {
23
+ // TODO: Need to benchmark the effects of using sh vs zsh for shell.
24
+ // Seems like zsh shells run slower
9
25
  const result = await promiseSpawn(cmd, args ?? [], { ...opts, stdio: 'pipe', stdioString: true, shell: opts?.shell ?? process.env.SHELL }, extras);
10
26
  if (isDebug()) {
11
27
  console.log(`codifySpawn result for: ${cmd}`);
@@ -34,20 +50,53 @@ export async function codifySpawn(cmd, args, opts, extras) {
34
50
  }
35
51
  }
36
52
  export function isDebug() {
37
- return process.env.DEBUG != null && process.env.DEBUG.includes('codify');
53
+ return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
38
54
  }
39
55
  export function splitUserConfig(config) {
40
- const resourceMetadata = {
56
+ const coreParameters = {
41
57
  type: config.type,
42
58
  ...(config.name ? { name: config.name } : {}),
43
59
  ...(config.dependsOn ? { dependsOn: config.dependsOn } : {}),
44
60
  };
61
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
62
  const { type, name, dependsOn, ...parameters } = config;
46
63
  return {
47
64
  parameters: parameters,
48
- resourceMetadata,
65
+ coreParameters,
49
66
  };
50
67
  }
51
68
  export function setsEqual(set1, set2) {
52
69
  return set1.size === set2.size && [...set1].every((v) => set2.has(v));
53
70
  }
71
+ const homeDirectory = os.homedir();
72
+ export function untildify(pathWithTilde) {
73
+ return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
74
+ }
75
+ export function areArraysEqual(parameter, desired, current) {
76
+ if (!Array.isArray(desired) || !Array.isArray(current)) {
77
+ throw new Error(`A non-array value:
78
+
79
+ Desired: ${JSON.stringify(desired, null, 2)}
80
+
81
+ Current: ${JSON.stringify(desired, null, 2)}
82
+
83
+ Was provided even though type array was specified.
84
+ `);
85
+ }
86
+ if (desired.length !== current.length) {
87
+ return false;
88
+ }
89
+ const desiredCopy = [...desired];
90
+ const currentCopy = [...current];
91
+ // Algorithm for to check equality between two un-ordered; un-hashable arrays using
92
+ // an isElementEqual method. Time: O(n^2)
93
+ for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
94
+ const idx = currentCopy.findIndex((e2) => (parameter.isElementEqual ?? ((a, b) => a === b))(desiredCopy[counter], e2));
95
+ if (idx === -1) {
96
+ return false;
97
+ }
98
+ desiredCopy.splice(counter, 1);
99
+ currentCopy.splice(idx, 1);
100
+ }
101
+ return currentCopy.length === 0;
102
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.76",
4
- "description": "",
3
+ "version": "1.0.78",
4
+ "description": "Library plugin library",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
7
7
  "type": "module",
@@ -15,7 +15,8 @@
15
15
  "ajv": "^8.12.0",
16
16
  "ajv-formats": "^2.1.1",
17
17
  "codify-schemas": "1.0.45",
18
- "@npmcli/promise-spawn": "^7.0.1"
18
+ "@npmcli/promise-spawn": "^7.0.1",
19
+ "uuid": "^10.0.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@oclif/prettier-config": "^0.2.1",
@@ -24,6 +25,7 @@
24
25
  "@types/node": "^18",
25
26
  "@types/semver": "^7.5.4",
26
27
  "@types/sinon": "^17.0.3",
28
+ "@types/uuid": "^10.0.0",
27
29
  "chai-as-promised": "^7.1.1",
28
30
  "vitest": "^1.4.0",
29
31
  "vitest-mock-extended": "^1.3.1",
package/src/index.ts CHANGED
@@ -1,16 +1,15 @@
1
- import { Plugin } from './entities/plugin.js';
2
1
  import { MessageHandler } from './messages/handlers.js';
2
+ import { Plugin } from './plugin/plugin.js';
3
3
 
4
- export * from './entities/resource.js'
5
- export * from './entities/resource-types.js'
6
- export * from './entities/resource-options.js'
7
- export * from './entities/plugin.js'
8
- export * from './entities/change-set.js'
9
- export * from './entities/plan.js'
10
- export * from './entities/plan-types.js'
11
- export * from './entities/stateful-parameter.js'
12
- export * from './entities/errors.js'
13
-
4
+ export * from './errors.js'
5
+ export * from './plan/change-set.js'
6
+ export * from './plan/plan.js'
7
+ export * from './plan/plan-types.js'
8
+ export * from './plugin/plugin.js'
9
+ export * from './resource/parsed-resource-settings.js';
10
+ export * from './resource/resource.js'
11
+ export * from './resource/resource-settings.js'
12
+ export * from './resource/stateful-parameter.js'
14
13
  export * from './utils/utils.js'
15
14
 
16
15
  export async function runPlugin(plugin: Plugin) {
@@ -1,10 +1,10 @@
1
1
  import { MessageHandler } from './handlers.js';
2
- import { Plugin } from '../entities/plugin.js';
2
+ import { Plugin } from '../plugin/plugin.js';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import { mock } from 'vitest-mock-extended'
5
- import { Resource } from '../entities/resource.js';
6
- import { Plan } from '../entities/plan.js';
5
+ import { Resource } from '../resource/resource.js';
7
6
  import { MessageStatus, ResourceOperation } from 'codify-schemas';
7
+ import { TestResource } from '../utils/test-utils.test.js';
8
8
 
9
9
  describe('Message handler tests', () => {
10
10
  it('handles plan requests', async () => {
@@ -151,7 +151,7 @@ describe('Message handler tests', () => {
151
151
  })
152
152
 
153
153
  it('handles errors for plan', async () => {
154
- const resource= testResource();
154
+ const resource = new TestResource()
155
155
  const plugin = testPlugin(resource);
156
156
 
157
157
  const handler = new MessageHandler(plugin);
@@ -179,7 +179,7 @@ describe('Message handler tests', () => {
179
179
  })
180
180
 
181
181
  it('handles errors for apply (create)', async () => {
182
- const resource= testResource();
182
+ const resource = new TestResource()
183
183
  const plugin = testPlugin(resource);
184
184
 
185
185
  const handler = new MessageHandler(plugin);
@@ -188,7 +188,6 @@ describe('Message handler tests', () => {
188
188
  expect(message).toMatchObject({
189
189
  cmd: 'apply_Response',
190
190
  status: MessageStatus.ERROR,
191
- data: 'Create error',
192
191
  })
193
192
  return true;
194
193
  }
@@ -206,7 +205,7 @@ describe('Message handler tests', () => {
206
205
  })
207
206
 
208
207
  it('handles errors for apply (destroy)', async () => {
209
- const resource= testResource();
208
+ const resource = new TestResource()
210
209
  const plugin = testPlugin(resource);
211
210
 
212
211
  const handler = new MessageHandler(plugin);
@@ -215,7 +214,6 @@ describe('Message handler tests', () => {
215
214
  expect(message).toMatchObject({
216
215
  cmd: 'apply_Response',
217
216
  status: MessageStatus.ERROR,
218
- data: 'Destroy error',
219
217
  })
220
218
  return true;
221
219
  }
@@ -231,33 +229,8 @@ describe('Message handler tests', () => {
231
229
  }
232
230
  })).rejects.to.not.throw;
233
231
  })
234
-
235
-
236
- const testResource = () => new class extends Resource<any> {
237
- constructor() {
238
- super({ type: 'resourceA' });
239
- }
240
-
241
- async refresh(keys: Map<keyof any, any>): Promise<Partial<any> | null> {
242
- throw new Error('Refresh error');
243
- }
244
-
245
- applyCreate(plan: Plan<any>): Promise<void> {
246
- throw new Error('Create error');
247
- }
248
-
249
- applyDestroy(plan: Plan<any>): Promise<void> {
250
- throw new Error('Destroy error');
251
- }
252
- }
253
-
254
- const testPlugin = (resource: Resource<any>) => new class extends Plugin {
255
- constructor() {
256
- const map = new Map();
257
- map.set('resourceA', resource);
258
-
259
- super('name', map);
260
- }
261
- }
262
-
263
232
  });
233
+
234
+ function testPlugin(resource: Resource<any>) {
235
+ return Plugin.create('plugin', [resource])
236
+ }
@@ -15,8 +15,8 @@ import {
15
15
  ValidateResponseDataSchema
16
16
  } from 'codify-schemas';
17
17
 
18
- import { SudoError } from '../entities/errors.js';
19
- import { Plugin } from '../entities/plugin.js';
18
+ import { SudoError } from '../errors.js';
19
+ import { Plugin } from '../plugin/plugin.js';
20
20
 
21
21
  const SupportedRequests: Record<string, { handler: (plugin: Plugin, data: any) => Promise<unknown>; requestValidator: SchemaObject; responseValidator: SchemaObject }> = {
22
22
  'apply': {
@@ -0,0 +1,220 @@
1
+ import { ChangeSet } from './change-set.js';
2
+ import { ParameterOperation, ResourceOperation } from 'codify-schemas';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ describe('Change set tests', () => {
6
+ it ('Correctly diffs two resource configs (modify)', () => {
7
+ const after = {
8
+ propA: 'before',
9
+ propB: 'before'
10
+ }
11
+
12
+ const before = {
13
+ propA: 'after',
14
+ propB: 'after'
15
+ }
16
+
17
+ const cs = ChangeSet.calculateModification(after, before);
18
+ expect(cs.parameterChanges.length).to.eq(2);
19
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
20
+ expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.MODIFY);
21
+ expect(cs.operation).to.eq(ResourceOperation.RECREATE)
22
+ })
23
+
24
+ it ('Correctly diffs two resource configs (add)', () => {
25
+ const after = {
26
+ propA: 'before',
27
+ propB: 'after'
28
+ }
29
+
30
+ const before = {
31
+ propA: 'after',
32
+ }
33
+
34
+ const cs = ChangeSet.calculateModification(after, before,);
35
+ expect(cs.parameterChanges.length).to.eq(2);
36
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
37
+ expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.ADD);
38
+ expect(cs.operation).to.eq(ResourceOperation.RECREATE)
39
+
40
+ })
41
+
42
+ it ('Correctly diffs two resource configs (remove)', () => {
43
+ const after = {
44
+ propA: 'after',
45
+ }
46
+
47
+ const before = {
48
+ propA: 'before',
49
+ propB: 'before'
50
+ }
51
+
52
+ const cs = ChangeSet.calculateModification(after, before);
53
+ expect(cs.parameterChanges.length).to.eq(2);
54
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
55
+ expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
56
+ expect(cs.operation).to.eq(ResourceOperation.RECREATE)
57
+ })
58
+
59
+ it ('Correctly diffs two resource configs (no-op)', () => {
60
+ const after = {
61
+ propA: 'prop',
62
+ }
63
+
64
+ const before = {
65
+ propA: 'prop',
66
+ }
67
+
68
+ const cs = ChangeSet.calculateModification(after, before);
69
+ expect(cs.parameterChanges.length).to.eq(1);
70
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
71
+ expect(cs.operation).to.eq(ResourceOperation.NOOP)
72
+ })
73
+
74
+ it('Correctly diffs two resource configs (create)', () => {
75
+ const cs = ChangeSet.create({
76
+ propA: 'prop',
77
+ propB: 'propB'
78
+ });
79
+
80
+ expect(cs.parameterChanges.length).to.eq(2);
81
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.ADD);
82
+ expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.ADD);
83
+ expect(cs.operation).to.eq(ResourceOperation.CREATE)
84
+ })
85
+
86
+ it('Correctly diffs two resource configs (destory)', () => {
87
+ const cs = ChangeSet.destroy({
88
+ propA: 'prop',
89
+ propB: 'propB'
90
+ });
91
+
92
+ expect(cs.parameterChanges.length).to.eq(2);
93
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.REMOVE);
94
+ expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
95
+ expect(cs.operation).to.eq(ResourceOperation.DESTROY)
96
+ })
97
+
98
+ it ('handles simple arrays', () => {
99
+ const before = {
100
+ propA: ['a', 'b', 'c'],
101
+ }
102
+
103
+ const after = {
104
+ propA: ['b', 'a', 'c'],
105
+ }
106
+
107
+ const cs = ChangeSet.calculateModification(after, before, { propA: { type: 'array' } });
108
+ expect(cs.parameterChanges.length).to.eq(1);
109
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
110
+ expect(cs.operation).to.eq(ResourceOperation.NOOP)
111
+ })
112
+
113
+ it('handles simple arrays 2', () => {
114
+ const after = {
115
+ propA: ['a', 'b', 'c'],
116
+ }
117
+
118
+ const before = {
119
+ propA: ['b', 'a'],
120
+ }
121
+
122
+ const cs = ChangeSet.calculateModification(after, before, { propA: { type: 'array' } });
123
+ expect(cs.parameterChanges.length).to.eq(1);
124
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
125
+ expect(cs.operation).to.eq(ResourceOperation.RECREATE)
126
+ })
127
+
128
+ it('determines the order of operations with canModify 1', () => {
129
+ const after = {
130
+ propA: 'after',
131
+ }
132
+
133
+ const before = {
134
+ propA: 'before',
135
+ propB: 'before'
136
+ }
137
+
138
+ const cs = ChangeSet.calculateModification(after, before, { propA: { canModify: true } });
139
+ expect(cs.parameterChanges.length).to.eq(2);
140
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
141
+ expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
142
+ expect(cs.operation).to.eq(ResourceOperation.RECREATE)
143
+ })
144
+
145
+ it('determines the order of operations with canModify 2', () => {
146
+ const after = {
147
+ propA: 'after',
148
+ }
149
+
150
+ const before = {
151
+ propA: 'before',
152
+ propB: 'before'
153
+ }
154
+
155
+ const cs = ChangeSet.calculateModification<any>(after, before, {
156
+ propA: { canModify: true },
157
+ propB: { canModify: true }
158
+ });
159
+ expect(cs.parameterChanges.length).to.eq(2);
160
+ expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
161
+ expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
162
+ expect(cs.operation).to.eq(ResourceOperation.MODIFY)
163
+ })
164
+
165
+
166
+ it('correctly determines array equality', () => {
167
+ const arrA = ['a', 'b', 'd'];
168
+ const arrB = ['a', 'b', 'd'];
169
+
170
+ const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
171
+
172
+ expect(result.operation).to.eq(ResourceOperation.NOOP);
173
+ })
174
+
175
+ it('correctly determines array equality 2', () => {
176
+ const arrA = ['a', 'b'];
177
+ const arrB = ['a', 'b', 'd'];
178
+
179
+ const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
180
+
181
+ expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
182
+ })
183
+
184
+ it('correctly determines array equality 3', () => {
185
+ const arrA = ['b', 'a', 'd'];
186
+ const arrB = ['a', 'b', 'd'];
187
+
188
+ const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
189
+
190
+ expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
191
+ })
192
+
193
+ it('correctly determines array equality 4', () => {
194
+ const arrA = [{ key1: 'a' }, { key1: 'a' }, { key1: 'a' }];
195
+ const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
196
+
197
+ const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, {
198
+ propA: {
199
+ type: 'array',
200
+ isElementEqual: (a, b) => a.key1 === b.key1
201
+ }
202
+ })
203
+
204
+ expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
205
+ })
206
+
207
+ it('correctly determines array equality 5', () => {
208
+ const arrA = [{ key1: 'b' }, { key1: 'a' }, { key1: 'a' }];
209
+ const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
210
+
211
+ const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, {
212
+ propA: {
213
+ type: 'array',
214
+ isElementEqual: (a, b) => a.key1 === b.key1
215
+ }
216
+ })
217
+
218
+ expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
219
+ })
220
+ })