@codifycli/plugin-core 1.0.0-beta1

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 (152) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.json +30 -0
  3. package/.github/workflows/release.yaml +19 -0
  4. package/.github/workflows/unit-test-ci.yaml +18 -0
  5. package/.prettierrc.json +1 -0
  6. package/bin/build.js +189 -0
  7. package/dist/bin/build.d.ts +1 -0
  8. package/dist/bin/build.js +80 -0
  9. package/dist/bin/deploy-plugin.d.ts +2 -0
  10. package/dist/bin/deploy-plugin.js +8 -0
  11. package/dist/common/errors.d.ts +8 -0
  12. package/dist/common/errors.js +24 -0
  13. package/dist/entities/change-set.d.ts +24 -0
  14. package/dist/entities/change-set.js +152 -0
  15. package/dist/entities/errors.d.ts +4 -0
  16. package/dist/entities/errors.js +7 -0
  17. package/dist/entities/plan-types.d.ts +25 -0
  18. package/dist/entities/plan-types.js +1 -0
  19. package/dist/entities/plan.d.ts +15 -0
  20. package/dist/entities/plan.js +127 -0
  21. package/dist/entities/plugin.d.ts +16 -0
  22. package/dist/entities/plugin.js +80 -0
  23. package/dist/entities/resource-options.d.ts +31 -0
  24. package/dist/entities/resource-options.js +76 -0
  25. package/dist/entities/resource-types.d.ts +11 -0
  26. package/dist/entities/resource-types.js +1 -0
  27. package/dist/entities/resource.d.ts +42 -0
  28. package/dist/entities/resource.js +303 -0
  29. package/dist/entities/stateful-parameter.d.ts +29 -0
  30. package/dist/entities/stateful-parameter.js +46 -0
  31. package/dist/entities/transform-parameter.d.ts +4 -0
  32. package/dist/entities/transform-parameter.js +2 -0
  33. package/dist/errors.d.ts +4 -0
  34. package/dist/errors.js +7 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.js +26 -0
  37. package/dist/messages/handlers.d.ts +14 -0
  38. package/dist/messages/handlers.js +134 -0
  39. package/dist/messages/sender.d.ts +11 -0
  40. package/dist/messages/sender.js +57 -0
  41. package/dist/plan/change-set.d.ts +53 -0
  42. package/dist/plan/change-set.js +153 -0
  43. package/dist/plan/plan-types.d.ts +23 -0
  44. package/dist/plan/plan-types.js +1 -0
  45. package/dist/plan/plan.d.ts +66 -0
  46. package/dist/plan/plan.js +328 -0
  47. package/dist/plugin/plugin.d.ts +24 -0
  48. package/dist/plugin/plugin.js +200 -0
  49. package/dist/pty/background-pty.d.ts +21 -0
  50. package/dist/pty/background-pty.js +127 -0
  51. package/dist/pty/index.d.ts +50 -0
  52. package/dist/pty/index.js +20 -0
  53. package/dist/pty/promise-queue.d.ts +5 -0
  54. package/dist/pty/promise-queue.js +26 -0
  55. package/dist/pty/seqeuntial-pty.d.ts +17 -0
  56. package/dist/pty/seqeuntial-pty.js +119 -0
  57. package/dist/pty/vitest.config.d.ts +2 -0
  58. package/dist/pty/vitest.config.js +11 -0
  59. package/dist/resource/config-parser.d.ts +11 -0
  60. package/dist/resource/config-parser.js +21 -0
  61. package/dist/resource/parsed-resource-settings.d.ts +47 -0
  62. package/dist/resource/parsed-resource-settings.js +196 -0
  63. package/dist/resource/resource-controller.d.ts +36 -0
  64. package/dist/resource/resource-controller.js +402 -0
  65. package/dist/resource/resource-settings.d.ts +303 -0
  66. package/dist/resource/resource-settings.js +147 -0
  67. package/dist/resource/resource.d.ts +144 -0
  68. package/dist/resource/resource.js +44 -0
  69. package/dist/resource/stateful-parameter.d.ts +165 -0
  70. package/dist/resource/stateful-parameter.js +94 -0
  71. package/dist/scripts/deploy.d.ts +1 -0
  72. package/dist/scripts/deploy.js +2 -0
  73. package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
  74. package/dist/stateful-parameter/stateful-parameter-controller.js +81 -0
  75. package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
  76. package/dist/stateful-parameter/stateful-parameter.js +43 -0
  77. package/dist/test.d.ts +1 -0
  78. package/dist/test.js +5 -0
  79. package/dist/utils/codify-spawn.d.ts +29 -0
  80. package/dist/utils/codify-spawn.js +136 -0
  81. package/dist/utils/debug.d.ts +2 -0
  82. package/dist/utils/debug.js +10 -0
  83. package/dist/utils/file-utils.d.ts +23 -0
  84. package/dist/utils/file-utils.js +186 -0
  85. package/dist/utils/functions.d.ts +12 -0
  86. package/dist/utils/functions.js +74 -0
  87. package/dist/utils/index.d.ts +46 -0
  88. package/dist/utils/index.js +271 -0
  89. package/dist/utils/internal-utils.d.ts +12 -0
  90. package/dist/utils/internal-utils.js +74 -0
  91. package/dist/utils/load-resources.d.ts +1 -0
  92. package/dist/utils/load-resources.js +46 -0
  93. package/dist/utils/package-json-utils.d.ts +12 -0
  94. package/dist/utils/package-json-utils.js +34 -0
  95. package/dist/utils/pty-local-storage.d.ts +2 -0
  96. package/dist/utils/pty-local-storage.js +2 -0
  97. package/dist/utils/spawn-2.d.ts +5 -0
  98. package/dist/utils/spawn-2.js +7 -0
  99. package/dist/utils/spawn.d.ts +29 -0
  100. package/dist/utils/spawn.js +124 -0
  101. package/dist/utils/utils.d.ts +18 -0
  102. package/dist/utils/utils.js +86 -0
  103. package/dist/utils/verbosity-level.d.ts +5 -0
  104. package/dist/utils/verbosity-level.js +9 -0
  105. package/package.json +59 -0
  106. package/rollup.config.js +24 -0
  107. package/src/common/errors.test.ts +43 -0
  108. package/src/common/errors.ts +31 -0
  109. package/src/errors.ts +8 -0
  110. package/src/index.test.ts +6 -0
  111. package/src/index.ts +30 -0
  112. package/src/messages/handlers.test.ts +329 -0
  113. package/src/messages/handlers.ts +181 -0
  114. package/src/messages/sender.ts +69 -0
  115. package/src/plan/change-set.test.ts +280 -0
  116. package/src/plan/change-set.ts +236 -0
  117. package/src/plan/plan-types.ts +27 -0
  118. package/src/plan/plan.test.ts +413 -0
  119. package/src/plan/plan.ts +499 -0
  120. package/src/plugin/plugin.test.ts +533 -0
  121. package/src/plugin/plugin.ts +291 -0
  122. package/src/pty/background-pty.test.ts +69 -0
  123. package/src/pty/background-pty.ts +154 -0
  124. package/src/pty/index.test.ts +129 -0
  125. package/src/pty/index.ts +66 -0
  126. package/src/pty/promise-queue.ts +33 -0
  127. package/src/pty/seqeuntial-pty.ts +151 -0
  128. package/src/pty/sequential-pty.test.ts +194 -0
  129. package/src/resource/config-parser.ts +42 -0
  130. package/src/resource/parsed-resource-settings.test.ts +186 -0
  131. package/src/resource/parsed-resource-settings.ts +307 -0
  132. package/src/resource/resource-controller-stateful-mode.test.ts +253 -0
  133. package/src/resource/resource-controller.test.ts +1081 -0
  134. package/src/resource/resource-controller.ts +563 -0
  135. package/src/resource/resource-settings.test.ts +1213 -0
  136. package/src/resource/resource-settings.ts +545 -0
  137. package/src/resource/resource.ts +157 -0
  138. package/src/stateful-parameter/stateful-parameter-controller.test.ts +244 -0
  139. package/src/stateful-parameter/stateful-parameter-controller.ts +111 -0
  140. package/src/stateful-parameter/stateful-parameter.ts +160 -0
  141. package/src/utils/debug.ts +11 -0
  142. package/src/utils/file-utils.test.ts +7 -0
  143. package/src/utils/file-utils.ts +231 -0
  144. package/src/utils/functions.ts +103 -0
  145. package/src/utils/index.ts +340 -0
  146. package/src/utils/internal-utils.test.ts +52 -0
  147. package/src/utils/pty-local-storage.ts +3 -0
  148. package/src/utils/test-utils.test.ts +96 -0
  149. package/src/utils/verbosity-level.ts +11 -0
  150. package/tsconfig.json +26 -0
  151. package/tsconfig.test.json +9 -0
  152. package/vitest.config.ts +10 -0
@@ -0,0 +1,86 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ export const VerbosityLevel = new class {
4
+ level = 0;
5
+ get() {
6
+ return this.level;
7
+ }
8
+ set(level) {
9
+ this.level = level;
10
+ }
11
+ };
12
+ export function isDebug() {
13
+ return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
14
+ }
15
+ export function splitUserConfig(config) {
16
+ const coreParameters = {
17
+ type: config.type,
18
+ ...(config.name ? { name: config.name } : {}),
19
+ ...(config.dependsOn ? { dependsOn: config.dependsOn } : {}),
20
+ };
21
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
+ const { type, name, dependsOn, ...parameters } = config;
23
+ return {
24
+ parameters: parameters,
25
+ coreParameters,
26
+ };
27
+ }
28
+ export function setsEqual(set1, set2) {
29
+ return set1.size === set2.size && [...set1].every((v) => set2.has(v));
30
+ }
31
+ const homeDirectory = os.homedir();
32
+ export function untildify(pathWithTilde) {
33
+ return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
34
+ }
35
+ export function tildify(pathWithTilde) {
36
+ return homeDirectory ? pathWithTilde.replace(homeDirectory, '~') : pathWithTilde;
37
+ }
38
+ export function resolvePathWithVariables(pathWithVariables) {
39
+ // @ts-expect-error Ignore this for now
40
+ return pathWithVariables.replace(/\$([A-Z_]+[A-Z0-9_]*)|\${([A-Z0-9_]*)}/ig, (_, a, b) => process.env[a || b]);
41
+ }
42
+ export function addVariablesToPath(pathWithoutVariables) {
43
+ let result = pathWithoutVariables;
44
+ for (const [key, value] of Object.entries(process.env)) {
45
+ if (!value || !path.isAbsolute(value) || value === '/' || key === 'HOME' || key === 'PATH' || key === 'SHELL' || key === 'PWD') {
46
+ continue;
47
+ }
48
+ result = result.replaceAll(value, `$${key}`);
49
+ }
50
+ return result;
51
+ }
52
+ export function unhome(pathWithHome) {
53
+ return pathWithHome.includes('$HOME') ? pathWithHome.replaceAll('$HOME', os.homedir()) : pathWithHome;
54
+ }
55
+ export function areArraysEqual(isElementEqual, desired, current) {
56
+ if (!desired || !current) {
57
+ return false;
58
+ }
59
+ if (!Array.isArray(desired) || !Array.isArray(current)) {
60
+ throw new Error(`A non-array value:
61
+
62
+ Desired: ${JSON.stringify(desired, null, 2)}
63
+
64
+ Current: ${JSON.stringify(desired, null, 2)}
65
+
66
+ Was provided even though type array was specified.
67
+ `);
68
+ }
69
+ if (desired.length !== current.length) {
70
+ return false;
71
+ }
72
+ const desiredCopy = [...desired];
73
+ const currentCopy = [...current];
74
+ // Algorithm for to check equality between two un-ordered; un-hashable arrays using
75
+ // an isElementEqual method. Time: O(n^2)
76
+ for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
77
+ const idx = currentCopy.findIndex((e2) => (isElementEqual
78
+ ?? ((a, b) => a === b))(desiredCopy[counter], e2));
79
+ if (idx === -1) {
80
+ return false;
81
+ }
82
+ desiredCopy.splice(counter, 1);
83
+ currentCopy.splice(idx, 1);
84
+ }
85
+ return currentCopy.length === 0;
86
+ }
@@ -0,0 +1,5 @@
1
+ export declare const VerbosityLevel: {
2
+ level: number;
3
+ get(): number;
4
+ set(level: number): void;
5
+ };
@@ -0,0 +1,9 @@
1
+ export const VerbosityLevel = new class {
2
+ level = 0;
3
+ get() {
4
+ return this.level;
5
+ }
6
+ set(level) {
7
+ this.level = level;
8
+ }
9
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@codifycli/plugin-core",
3
+ "version": "1.0.0-beta1",
4
+ "description": "Core library for implementing a plugin for Codify",
5
+ "main": "dist/index.js",
6
+ "typings": "dist/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "test": "vitest",
10
+ "posttest": "tsc",
11
+ "prepublishOnly": "tsc"
12
+ },
13
+ "bin": {
14
+ "codify-build": "./bin/build.js"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
21
+ "@npmcli/promise-spawn": "^7.0.1",
22
+ "ajv": "^8.12.0",
23
+ "ajv-formats": "^2.1.1",
24
+ "clean-deep": "^3.4.0",
25
+ "codify-schemas": "1.0.86-beta11",
26
+ "lodash.isequal": "^4.5.0",
27
+ "nanoid": "^5.0.9",
28
+ "strip-ansi": "^7.1.0",
29
+ "uuid": "^10.0.0",
30
+ "zod": "4.1.13"
31
+ },
32
+ "devDependencies": {
33
+ "@apidevtools/json-schema-ref-parser": "^11.7.2",
34
+ "@oclif/prettier-config": "^0.2.1",
35
+ "@oclif/test": "^3",
36
+ "@types/lodash.isequal": "^4.5.8",
37
+ "@types/node": "^20",
38
+ "@types/npmcli__promise-spawn": "^6.0.3",
39
+ "@types/semver": "^7.5.4",
40
+ "@types/sinon": "^17.0.3",
41
+ "@types/uuid": "^10.0.0",
42
+ "chai-as-promised": "^7.1.1",
43
+ "eslint": "^8.51.0",
44
+ "eslint-config-oclif": "^5",
45
+ "eslint-config-oclif-typescript": "^3",
46
+ "eslint-config-prettier": "^9.0.0",
47
+ "merge-json-schemas": "^1.0.0",
48
+ "shx": "^0.3.3",
49
+ "sinon": "^17.0.1",
50
+ "ts-node": "^10.9.1",
51
+ "tsc-watch": "^6.0.4",
52
+ "typescript": "^5",
53
+ "vitest": "^3.0.5",
54
+ "vitest-mock-extended": "^1.3.1"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ }
59
+ }
@@ -0,0 +1,24 @@
1
+ import commonjs from '@rollup/plugin-commonjs';
2
+ import json from '@rollup/plugin-json';
3
+ import nodeResolve from '@rollup/plugin-node-resolve';
4
+ import terser from '@rollup/plugin-terser';
5
+ import typescript from '@rollup/plugin-typescript';
6
+
7
+ export default {
8
+ input: 'src/index.ts',
9
+ output: {
10
+ dir: 'dist',
11
+ format: 'cjs',
12
+ inlineDynamicImports: true,
13
+ },
14
+ external: ['@homebridge/node-pty-prebuilt-multiarch'],
15
+ plugins: [
16
+ json(),
17
+ nodeResolve({ exportConditions: ['node'] }),
18
+ typescript({
19
+ exclude: ['**/*.test.ts', '**/*.d.ts', 'test']
20
+ }),
21
+ commonjs(),
22
+ terser()
23
+ ]
24
+ }
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ApplyValidationError } from './errors.js';
3
+ import { testPlan } from '../utils/test-utils.test.js';
4
+
5
+ describe('Test file for errors file', () => {
6
+ it('Can properly format ApplyValidationError', () => {
7
+ const plan = testPlan({
8
+ desired: null,
9
+ current: [{ propZ: ['a', 'b', 'c'] }],
10
+ state: { propZ: ['a', 'b', 'c'] },
11
+ core: {
12
+ type: 'homebrew',
13
+ name: 'first'
14
+ },
15
+ isStateful: true,
16
+ })
17
+
18
+ try {
19
+ throw new ApplyValidationError(plan);
20
+ } catch (e) {
21
+ console.error(e);
22
+ expect(e.message).toMatch(
23
+ `Failed to apply changes to resource: "homebrew.first". Additional changes are needed to complete apply.
24
+ Changes remaining:
25
+ {
26
+ "operation": "destroy",
27
+ "parameters": [
28
+ {
29
+ "name": "propZ",
30
+ "operation": "remove",
31
+ "currentValue": [
32
+ "a",
33
+ "b",
34
+ "c"
35
+ ],
36
+ "desiredValue": null
37
+ }
38
+ ]
39
+ }`
40
+ )
41
+ }
42
+ })
43
+ })
@@ -0,0 +1,31 @@
1
+ import { Plan } from '../plan/plan.js';
2
+
3
+ export class ApplyValidationError extends Error {
4
+ resourceType: string;
5
+ resourceName?: string;
6
+ plan: Plan<any>;
7
+
8
+ constructor(plan: Plan<any>) {
9
+ super(`Failed to apply changes to resource: "${plan.resourceId}". Additional changes are needed to complete apply.\nChanges remaining:\n${ApplyValidationError.prettyPrintPlan(plan)}`);
10
+
11
+ this.resourceType = plan.coreParameters.type;
12
+ this.resourceName = plan.coreParameters.name;
13
+ this.plan = plan;
14
+ }
15
+
16
+ private static prettyPrintPlan(plan: Plan<any>): string {
17
+ const { operation, parameters } = plan.toResponse();
18
+
19
+ const prettyParameters = parameters.map(({ name, operation, previousValue, newValue }) => ({
20
+ name,
21
+ operation,
22
+ currentValue: previousValue,
23
+ desiredValue: newValue,
24
+ }));
25
+
26
+ return JSON.stringify({
27
+ operation,
28
+ parameters: prettyParameters,
29
+ }, null, 2);
30
+ }
31
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,8 @@
1
+ export class SudoError extends Error {
2
+ command: string;
3
+
4
+ constructor(command: string) {
5
+ super();
6
+ this.command = command;
7
+ }
8
+ }
@@ -0,0 +1,6 @@
1
+ import { describe, it } from 'vitest';
2
+
3
+ describe('Library integration tests', () => {
4
+ it('does something', async () => {
5
+ })
6
+ });
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { MessageHandler } from './messages/handlers.js';
2
+ import { Plugin } from './plugin/plugin.js';
3
+
4
+ export * from './errors.js'
5
+ export * from './messages/sender.js'
6
+ export * from './plan/change-set.js'
7
+ export * from './plan/plan.js'
8
+ export * from './plan/plan-types.js'
9
+ export * from './plugin/plugin.js'
10
+ export * from './pty/background-pty.js';
11
+ export * from './pty/index.js'
12
+ export * from './pty/seqeuntial-pty.js';
13
+ export * from './resource/parsed-resource-settings.js';
14
+ export * from './resource/resource.js'
15
+ export * from './resource/resource-settings.js'
16
+ export * from './stateful-parameter/stateful-parameter.js'
17
+ export * from './utils/file-utils.js'
18
+ export * from './utils/functions.js'
19
+ export * from './utils/index.js'
20
+ export * from './utils/verbosity-level.js'
21
+ export * from 'zod/v4';
22
+
23
+ export async function runPlugin(plugin: Plugin) {
24
+ const messageHandler = new MessageHandler(plugin);
25
+ process.on('message', (message) => messageHandler.onMessage(message))
26
+
27
+ process.on('beforeExit', () => {
28
+ plugin.kill();
29
+ })
30
+ }
@@ -0,0 +1,329 @@
1
+ import { MessageHandler } from './handlers.js';
2
+ import { Plugin } from '../plugin/plugin.js';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { mock } from 'vitest-mock-extended'
5
+ import { Resource } from '../resource/resource.js';
6
+ import { MessageStatus, ResourceOperation } from 'codify-schemas';
7
+ import { TestResource } from '../utils/test-utils.test.js';
8
+
9
+ describe('Message handler tests', () => {
10
+ it('handles plan requests', async () => {
11
+ const plugin = mock<Plugin>();
12
+ const handler = new MessageHandler(plugin);
13
+
14
+ process.send = (message) => {
15
+ expect(message).toMatchObject({
16
+ cmd: 'plan_Response',
17
+ status: MessageStatus.SUCCESS,
18
+ });
19
+
20
+ return true;
21
+ }
22
+
23
+ // Message handler also validates the response. That part does not need to be tested
24
+ try {
25
+ await handler.onMessage({
26
+ cmd: 'plan',
27
+ data: {
28
+ core: {
29
+ type: 'resourceType',
30
+ name: 'name',
31
+ },
32
+ desired: {
33
+ prop1: 'A',
34
+ prop2: 'B',
35
+ },
36
+ isStateful: false,
37
+ }
38
+ })
39
+ } catch (e) {}
40
+
41
+ expect(plugin.plan.mock.calls.length).to.eq(1);
42
+ process.send = undefined;
43
+ })
44
+
45
+ it('rejects bad plan requests', async () => {
46
+ const plugin = mock<Plugin>();
47
+ const handler = new MessageHandler(plugin);
48
+
49
+ process.send = (message) => {
50
+ expect(message).toMatchObject({
51
+ cmd: 'plan_Response',
52
+ status: MessageStatus.ERROR,
53
+ });
54
+
55
+ return true;
56
+ }
57
+
58
+ // Message handler also validates the response. That part does not need to be tested
59
+ try {
60
+ await handler.onMessage({
61
+ cmd: 'plan',
62
+ data: {
63
+ name: '1name',
64
+ prop1: 'A',
65
+ prop2: 'B',
66
+ }
67
+ })
68
+ } catch (e) {
69
+ console.log(e);
70
+ }
71
+
72
+ expect(plugin.plan.mock.calls.length).to.eq(0);
73
+ process.send = undefined;
74
+ })
75
+
76
+ it('handles apply requests', async () => {
77
+ const plugin = mock<Plugin>();
78
+ const handler = new MessageHandler(plugin);
79
+
80
+ // Message handler also validates the response. That part does not need to be tested
81
+ try {
82
+ await handler.onMessage({
83
+ cmd: 'apply',
84
+ data: {
85
+ planId: '1803fff7-a378-4006-95bb-7c97cba02c82'
86
+ }
87
+ })
88
+ } catch (e) {}
89
+
90
+ expect(plugin.apply.mock.calls.length).to.be.eq(1);
91
+ })
92
+
93
+ it('rejects bad apply requests', async () => {
94
+ const plugin = mock<Plugin>();
95
+ const handler = new MessageHandler(plugin);
96
+
97
+ // Message handler also validates the response. That part does not need to be tested
98
+ try {
99
+ await handler.onMessage({
100
+ cmd: 'apply',
101
+ data: {}
102
+ })
103
+ } catch (e) {}
104
+
105
+ expect(plugin.apply.mock.calls.length).to.be.eq(0);
106
+ })
107
+
108
+ it('handles validate requests', async () => {
109
+ const plugin = mock<Plugin>();
110
+ const handler = new MessageHandler(plugin);
111
+
112
+ // Message handler also validates the response. That part does not need to be tested
113
+ try {
114
+ await handler.onMessage({
115
+ cmd: 'validate',
116
+ data: {
117
+ configs: [
118
+ {
119
+ type: 'type1',
120
+ name: 'name1'
121
+ },
122
+ {
123
+ type: 'type2',
124
+ name: 'name2'
125
+ },
126
+ {
127
+ type: 'type2',
128
+ name: 'name3'
129
+ }
130
+ ]
131
+ }
132
+ })
133
+ } catch (e) {}
134
+
135
+ expect(plugin.validate.mock.calls.length).to.eq(1);
136
+ })
137
+
138
+ it('rejects bad validate requests', async () => {
139
+ const plugin = mock<Plugin>();
140
+ const handler = new MessageHandler(plugin);
141
+
142
+ process.send = () => true;
143
+
144
+ // Message handler also validates the response. That part does not need to be tested
145
+ // This should not throw
146
+ expect(await handler.onMessage({
147
+ cmd: 'validate',
148
+ data: {}
149
+ })).to.eq(undefined);
150
+
151
+ expect(plugin.apply.mock.calls.length).to.be.eq(0);
152
+ })
153
+
154
+ it('handles errors for plan', async () => {
155
+ const resource = new TestResource()
156
+ const plugin = testPlugin(resource);
157
+
158
+ const handler = new MessageHandler(plugin);
159
+
160
+ process.send = (message) => {
161
+ expect(message).toMatchObject({
162
+ cmd: 'plan_Response',
163
+ status: MessageStatus.ERROR,
164
+ data: 'Refresh error',
165
+ })
166
+ return true;
167
+ }
168
+
169
+ expect(async () => await handler.onMessage({
170
+ cmd: 'plan',
171
+ data: {
172
+ core: {
173
+ type: 'resourceA',
174
+ },
175
+ desired: {
176
+ type: 'resourceA'
177
+ },
178
+ isStateful: false,
179
+ }
180
+ })).rejects.to.not.throw;
181
+
182
+ process.send = undefined;
183
+ })
184
+
185
+ it('handles errors for apply (create)', async () => {
186
+ const resource = new TestResource()
187
+ const plugin = testPlugin(resource);
188
+
189
+ const handler = new MessageHandler(plugin);
190
+
191
+ process.send = (message) => {
192
+ expect(message).toMatchObject({
193
+ cmd: 'apply_Response',
194
+ status: MessageStatus.ERROR,
195
+ })
196
+ return true;
197
+ }
198
+
199
+ expect(async () => await handler.onMessage({
200
+ cmd: 'apply',
201
+ data: {
202
+ plan: {
203
+ resourceType: 'resourceA',
204
+ operation: ResourceOperation.CREATE,
205
+ parameters: []
206
+ }
207
+ }
208
+ })).rejects.to.not.throw;
209
+ })
210
+
211
+ it('handles errors for apply (destroy)', async () => {
212
+ const resource = new TestResource()
213
+ const plugin = testPlugin(resource);
214
+ const handler = new MessageHandler(plugin);
215
+
216
+ process.send = (message) => {
217
+ expect(message).toMatchObject({
218
+ cmd: 'apply_Response',
219
+ status: MessageStatus.ERROR,
220
+ })
221
+ return true;
222
+ }
223
+
224
+ expect(async () => await handler.onMessage({
225
+ cmd: 'apply',
226
+ data: {
227
+ plan: {
228
+ resourceType: 'resourceA',
229
+ operation: ResourceOperation.DESTROY,
230
+ parameters: []
231
+ }
232
+ }
233
+ })).rejects.to.not.throw;
234
+
235
+ process.send = undefined;
236
+ })
237
+
238
+ it('handles changing the verbosity level', async () => {
239
+ const resource = new TestResource()
240
+ const plugin = testPlugin(resource);
241
+ const handler = new MessageHandler(plugin);
242
+
243
+ process.send = (message) => {
244
+ expect(message).toMatchObject({
245
+ cmd: 'setVerbosityLevel_Response',
246
+ status: MessageStatus.SUCCESS,
247
+ })
248
+ return true;
249
+ }
250
+
251
+ expect(async () => await handler.onMessage({
252
+ cmd: 'setVerbosityLevel',
253
+ data: {
254
+ verbosityLevel: 2,
255
+ }
256
+ })).rejects.to.not.throw;
257
+
258
+ process.send = undefined;
259
+ })
260
+
261
+ it('Supports ipc message v2 (success)', async () => {
262
+ const resource = new TestResource()
263
+ const plugin = testPlugin(resource);
264
+ const handler = new MessageHandler(plugin);
265
+
266
+ process.send = (message) => {
267
+ console.log(message)
268
+ expect(message).toMatchObject({
269
+ cmd: 'plan_Response',
270
+ requestId: 'abcdef',
271
+ status: MessageStatus.SUCCESS,
272
+ })
273
+ return true;
274
+ }
275
+
276
+ await expect(handler.onMessage({
277
+ cmd: 'plan',
278
+ requestId: 'abcdef',
279
+ data: {
280
+ core: {
281
+ type: 'type',
282
+ name: 'name',
283
+ },
284
+ desired: {
285
+ prop1: 'A',
286
+ prop2: 'B',
287
+ },
288
+ isStateful: false,
289
+ }
290
+ })).resolves.to.eq(undefined);
291
+
292
+ process.send = undefined;
293
+ })
294
+
295
+ it('Supports ipc message v2 (error)', async () => {
296
+ const resource = new TestResource()
297
+ const plugin = testPlugin(resource);
298
+ const handler = new MessageHandler(plugin);
299
+
300
+ process.send = (message) => {
301
+ expect(message).toMatchObject({
302
+ cmd: 'apply_Response',
303
+ requestId: 'abcdef',
304
+ status: MessageStatus.ERROR,
305
+ })
306
+ return true;
307
+ }
308
+
309
+ await expect(handler.onMessage({
310
+ cmd: 'apply', // Supposed to be a plan so that's why it throws
311
+ requestId: 'abcdef',
312
+ data: {
313
+ desired: {
314
+ type: 'type',
315
+ name: 'name',
316
+ prop1: 'A',
317
+ prop2: 'B',
318
+ },
319
+ isStateful: false,
320
+ }
321
+ })).resolves.to.eq(undefined);
322
+
323
+ process.send = undefined;
324
+ })
325
+ });
326
+
327
+ function testPlugin(resource: Resource<any>) {
328
+ return Plugin.create('plugin', [resource])
329
+ }