codify-plugin-lib 1.0.100 → 1.0.102

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 (31) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/plan/change-set.d.ts +2 -2
  4. package/dist/plan/plan.js +11 -8
  5. package/dist/resource/config-parser.d.ts +2 -2
  6. package/dist/resource/parsed-resource-settings.d.ts +17 -4
  7. package/dist/resource/parsed-resource-settings.js +25 -3
  8. package/dist/resource/resource-controller.js +2 -1
  9. package/dist/resource/resource-settings.d.ts +7 -6
  10. package/dist/resource/resource-settings.js +21 -5
  11. package/dist/stateful-parameter/stateful-parameter-controller.d.ts +21 -0
  12. package/dist/stateful-parameter/stateful-parameter-controller.js +82 -0
  13. package/dist/stateful-parameter/stateful-parameter.d.ts +144 -0
  14. package/dist/stateful-parameter/stateful-parameter.js +43 -0
  15. package/dist/utils/utils.d.ts +1 -2
  16. package/dist/utils/utils.js +3 -2
  17. package/package.json +4 -2
  18. package/src/index.ts +1 -1
  19. package/src/plan/change-set.ts +5 -5
  20. package/src/plan/plan.test.ts +6 -1
  21. package/src/plan/plan.ts +21 -16
  22. package/src/resource/config-parser.ts +3 -3
  23. package/src/resource/parsed-resource-settings.ts +53 -6
  24. package/src/resource/resource-controller.ts +2 -1
  25. package/src/resource/resource-settings.test.ts +101 -0
  26. package/src/resource/resource-settings.ts +34 -9
  27. package/src/{resource/stateful-parameter.test.ts → stateful-parameter/stateful-parameter-controller.test.ts} +91 -12
  28. package/src/stateful-parameter/stateful-parameter-controller.ts +112 -0
  29. package/src/{resource → stateful-parameter}/stateful-parameter.ts +9 -67
  30. package/src/utils/test-utils.test.ts +1 -1
  31. package/src/utils/utils.ts +9 -4
@@ -1,6 +1,6 @@
1
1
  import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2
2
 
3
- import { ParameterSetting } from '../resource/resource-settings.js';
3
+ import { ParsedParameterSetting } from '../resource/parsed-resource-settings.js';
4
4
 
5
5
  /**
6
6
  * A parameter change describes a parameter level change to a resource.
@@ -87,7 +87,7 @@ export class ChangeSet<T extends StringIndexedObject> {
87
87
  static calculateModification<T extends StringIndexedObject>(
88
88
  desired: Partial<T>,
89
89
  current: Partial<T>,
90
- parameterSettings: Partial<Record<keyof T, ParameterSetting>> = {},
90
+ parameterSettings: Partial<Record<keyof T, ParsedParameterSetting>> = {},
91
91
  ): ChangeSet<T> {
92
92
  const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
93
93
 
@@ -128,7 +128,7 @@ export class ChangeSet<T extends StringIndexedObject> {
128
128
  private static calculateParameterChanges<T extends StringIndexedObject>(
129
129
  desiredParameters: Partial<T>,
130
130
  currentParameters: Partial<T>,
131
- parameterOptions?: Partial<Record<keyof T, ParameterSetting>>,
131
+ parameterOptions?: Partial<Record<keyof T, ParsedParameterSetting>>,
132
132
  ): ParameterChange<T>[] {
133
133
  const parameterChangeSet = new Array<ParameterChange<T>>();
134
134
 
@@ -204,8 +204,8 @@ export class ChangeSet<T extends StringIndexedObject> {
204
204
  private static isSame(
205
205
  desired: unknown,
206
206
  current: unknown,
207
- setting?: ParameterSetting,
207
+ setting?: ParsedParameterSetting,
208
208
  ): boolean {
209
- return (setting?.isEqual ?? ((a, b) => a === b))(desired, current)
209
+ return (setting?.isEqual ?? ((a: unknown, b: unknown) => a === b))(desired, current)
210
210
  }
211
211
  }
@@ -183,7 +183,12 @@ describe('Plan entity tests', () => {
183
183
  return {
184
184
  id: 'type',
185
185
  parameterSettings: {
186
- propZ: { type: 'array', canModify: true, isElementEqual: (a, b) => b.includes(a), filterInStatelessMode: false }
186
+ propZ: {
187
+ type: 'array',
188
+ canModify: true,
189
+ isElementEqual: (a, b) => b.includes(a),
190
+ filterInStatelessMode: false
191
+ }
187
192
  }
188
193
  }
189
194
  }
package/src/plan/plan.ts CHANGED
@@ -8,8 +8,12 @@ import {
8
8
  } from 'codify-schemas';
9
9
  import { v4 as uuidV4 } from 'uuid';
10
10
 
11
- import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
12
- import { ArrayParameterSetting, ResourceSettings, StatefulParameterSetting } from '../resource/resource-settings.js';
11
+ import {
12
+ ParsedArrayParameterSetting,
13
+ ParsedResourceSettings,
14
+ ParsedStatefulParameterSetting
15
+ } from '../resource/parsed-resource-settings.js';
16
+ import { ArrayParameterSetting, ResourceSettings } from '../resource/resource-settings.js';
13
17
  import { ChangeSet } from './change-set.js';
14
18
 
15
19
  /**
@@ -248,26 +252,27 @@ export class Plan<T extends StringIndexedObject> {
248
252
  }
249
253
 
250
254
  function isArrayParameterWithFiltering(k: string, v: T[keyof T]): boolean {
251
- return (((settings.parameterSettings?.[k]?.type === 'stateful'
252
- && (settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings().type === 'array')
253
- && (((settings.parameterSettings[k] as StatefulParameterSetting).definition.getSettings() as ArrayParameterSetting).filterInStatelessMode ?? true)
254
- ) || (
255
- settings.parameterSettings?.[k]?.type === 'array'
256
- && ((settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode ?? true)
257
- ))
258
- && Array.isArray(v)
255
+ if (settings.parameterSettings?.[k]?.type === 'stateful') {
256
+ const statefulSetting = settings.parameterSettings[k] as ParsedStatefulParameterSetting;
257
+ return statefulSetting.nestedSettings.type === 'array' &&
258
+ ((statefulSetting.nestedSettings as ArrayParameterSetting).filterInStatelessMode ?? true)
259
+ && Array.isArray(v);
260
+ }
261
+
262
+ return settings.parameterSettings?.[k]?.type === 'array'
263
+ && ((settings.parameterSettings?.[k] as ArrayParameterSetting).filterInStatelessMode ?? true)
264
+ && Array.isArray(v);
259
265
  }
260
266
 
261
267
  // For stateless mode, we must filter the current array so that the diff algorithm will not detect any deletes
262
268
  function filterArrayStatefulParameter(k: string, v: unknown[]): unknown[] {
263
269
  const desiredArray = desired![k] as unknown[];
264
270
  const matcher = settings.parameterSettings![k]!.type === 'stateful'
265
- ? ((settings.parameterSettings![k] as StatefulParameterSetting)
266
- .definition
267
- .getSettings() as ArrayParameterSetting)
268
- .isElementEqual ?? ((a, b) => a === b)
269
- : (settings.parameterSettings![k] as ArrayParameterSetting)
270
- .isElementEqual ?? ((a, b) => a === b)
271
+ ? ((settings.parameterSettings![k] as ParsedStatefulParameterSetting)
272
+ .nestedSettings as ParsedArrayParameterSetting)
273
+ .isElementEqual
274
+ : (settings.parameterSettings![k] as ParsedArrayParameterSetting)
275
+ .isElementEqual
271
276
 
272
277
  const desiredCopy = [...desiredArray];
273
278
  const currentCopy = [...v];
@@ -1,17 +1,17 @@
1
1
  import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
2
2
 
3
+ import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
3
4
  import { splitUserConfig } from '../utils/utils.js';
4
- import { StatefulParameter } from './stateful-parameter.js';
5
5
 
6
6
  export class ConfigParser<T extends StringIndexedObject> {
7
7
  private readonly desiredConfig: Partial<T> & ResourceConfig | null;
8
8
  private readonly stateConfig: Partial<T> & ResourceConfig | null;
9
- private statefulParametersMap: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
9
+ private statefulParametersMap: Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
10
10
 
11
11
  constructor(
12
12
  desiredConfig: Partial<T> & ResourceConfig | null,
13
13
  stateConfig: Partial<T> & ResourceConfig | null,
14
- statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>,
14
+ statefulParameters: Map<keyof T, StatefulParameterController<T, T[keyof T]>>,
15
15
  ) {
16
16
  this.desiredConfig = desiredConfig;
17
17
  this.stateConfig = stateConfig
@@ -1,14 +1,36 @@
1
1
  import { JSONSchemaType } from 'ajv';
2
2
  import { StringIndexedObject } from 'codify-schemas';
3
3
 
4
+ import { StatefulParameterController } from '../stateful-parameter/stateful-parameter-controller.js';
4
5
  import {
6
+ ArrayParameterSetting,
7
+ DefaultParameterSetting,
5
8
  ParameterSetting,
6
9
  resolveEqualsFn,
10
+ resolveFnFromEqualsFnOrString,
7
11
  resolveParameterTransformFn,
8
12
  ResourceSettings,
9
13
  StatefulParameterSetting
10
14
  } from './resource-settings.js';
11
- import { StatefulParameter as StatefulParameterImpl } from './stateful-parameter.js'
15
+
16
+ export interface ParsedStatefulParameterSetting extends DefaultParameterSetting {
17
+ type: 'stateful',
18
+ controller: StatefulParameterController<any, unknown>
19
+ order?: number,
20
+ nestedSettings: ParsedParameterSetting;
21
+ }
22
+
23
+ export type ParsedArrayParameterSetting = {
24
+ isElementEqual: (a: unknown, b: unknown) => boolean;
25
+ isEqual: (a: unknown, b: unknown) => boolean;
26
+ } & ArrayParameterSetting
27
+
28
+ export type ParsedParameterSetting =
29
+ {
30
+ isEqual: (desired: unknown, current: unknown) => boolean;
31
+ } & (DefaultParameterSetting
32
+ | ParsedArrayParameterSetting
33
+ | ParsedStatefulParameterSetting)
12
34
 
13
35
  export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
14
36
  private cache = new Map<string, unknown>();
@@ -36,24 +58,49 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
36
58
  return this.id;
37
59
  }
38
60
 
39
- get statefulParameters(): Map<keyof T, StatefulParameterImpl<T, T[keyof T]>> {
61
+ get statefulParameters(): Map<keyof T, StatefulParameterController<T, T[keyof T]>> {
40
62
  return this.getFromCacheOrCreate('statefulParameters', () => {
41
63
 
42
64
  const statefulParameters = Object.entries(this.settings.parameterSettings ?? {})
43
65
  .filter(([, p]) => p?.type === 'stateful')
44
- .map(([k, v]) => [k, (v as StatefulParameterSetting).definition] as const)
66
+ .map(([k, v]) => [
67
+ k,
68
+ new StatefulParameterController((v as StatefulParameterSetting).definition)
69
+ ] as const)
45
70
 
46
- return new Map(statefulParameters) as Map<keyof T, StatefulParameterImpl<T, T[keyof T]>>;
71
+ return new Map(statefulParameters) as Map<keyof T, StatefulParameterController<T, T[keyof T]>>;
47
72
  })
48
73
  }
49
74
 
50
- get parameterSettings(): Record<keyof T, ParameterSetting> {
75
+ get parameterSettings(): Record<keyof T, ParsedParameterSetting> {
51
76
  return this.getFromCacheOrCreate('parameterSetting', () => {
52
77
 
53
78
  const settings = Object.entries(this.settings.parameterSettings ?? {})
54
79
  .map(([k, v]) => [k, v!] as const)
55
80
  .map(([k, v]) => {
56
- v.isEqual = resolveEqualsFn(v, k);
81
+ v.isEqual = resolveEqualsFn(v);
82
+
83
+ if (v.type === 'stateful') {
84
+ const spController = this.statefulParameters.get(k);
85
+ const parsed = {
86
+ ...v,
87
+ controller: spController,
88
+ nestedSettings: spController?.parsedSettings,
89
+ definition: undefined,
90
+ };
91
+
92
+ return [k, parsed as ParsedStatefulParameterSetting];
93
+ }
94
+
95
+ if (v.type === 'array') {
96
+ const parsed = {
97
+ ...v,
98
+ isElementEqual: resolveFnFromEqualsFnOrString((v as ArrayParameterSetting).isElementEqual)
99
+ ?? ((a: unknown, b: unknown) => a === b),
100
+ }
101
+
102
+ return [k, parsed as ParsedArrayParameterSetting];
103
+ }
57
104
 
58
105
  return [k, v];
59
106
  })
@@ -63,7 +63,8 @@ export class ResourceController<T extends StringIndexedObject> {
63
63
  this.addDefaultValues(parameters);
64
64
 
65
65
  if (this.schemaValidator) {
66
- const isValid = this.schemaValidator(parameters);
66
+ // Schema validator uses pre transformation parameters
67
+ const isValid = this.schemaValidator(desiredConfig);
67
68
 
68
69
  if (!isValid) {
69
70
  return {
@@ -629,4 +629,105 @@ describe('Resource parameter tests', () => {
629
629
  }
630
630
  };
631
631
  })
632
+
633
+ it('Accepts a string isEqual method which selects from one of the defaults', async () => {
634
+ const resource = new class extends TestResource {
635
+ getSettings(): ResourceSettings<TestConfig> {
636
+ return {
637
+ id: 'resourceType',
638
+ parameterSettings: {
639
+ propA: { type: 'string', isEqual: 'version' }
640
+ }
641
+ }
642
+ }
643
+
644
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
645
+ return {
646
+ propA: '10.0.0'
647
+ }
648
+ }
649
+ };
650
+
651
+ const controller = new ResourceController(resource);
652
+
653
+ const result = await controller.plan({ type: 'resourceType', propA: '10.0' });
654
+ expect(result.changeSet).toMatchObject({
655
+ operation: ResourceOperation.NOOP,
656
+ })
657
+ });
658
+
659
+ it('Object equals method (works when equal)', async () => {
660
+ const resource = new class extends TestResource {
661
+ getSettings(): ResourceSettings<TestConfig> {
662
+ return {
663
+ id: 'resourceType',
664
+ parameterSettings: {
665
+ propD: { type: 'object' }
666
+ }
667
+ }
668
+ }
669
+
670
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
671
+ return {
672
+ propD: {
673
+ testA: 'a',
674
+ testB: 'b',
675
+ testC: 10,
676
+ }
677
+ }
678
+ }
679
+ };
680
+
681
+ const controller = new ResourceController(resource);
682
+
683
+ const result = await controller.plan({
684
+ type: 'resourceType',
685
+ propD: {
686
+ testC: 10,
687
+ testA: 'a',
688
+ testB: 'b',
689
+ }
690
+ });
691
+
692
+ expect(result.changeSet).toMatchObject({
693
+ operation: ResourceOperation.NOOP,
694
+ })
695
+ });
696
+
697
+ it('Object equals method (works when not equal)', async () => {
698
+ const resource = new class extends TestResource {
699
+ getSettings(): ResourceSettings<TestConfig> {
700
+ return {
701
+ id: 'resourceType',
702
+ parameterSettings: {
703
+ propD: { type: 'object' }
704
+ }
705
+ }
706
+ }
707
+
708
+ async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
709
+ return {
710
+ propD: {
711
+ testA: 'a',
712
+ testB: 'b',
713
+ }
714
+ }
715
+ }
716
+ };
717
+
718
+ const controller = new ResourceController(resource);
719
+
720
+ const result = await controller.plan({
721
+ type: 'resourceType',
722
+ propD: {
723
+ testC: 10,
724
+ testA: 'a',
725
+ testB: 'b',
726
+ }
727
+ });
728
+
729
+ expect(result.changeSet).toMatchObject({
730
+ operation: ResourceOperation.RECREATE,
731
+ })
732
+ });
632
733
  })
@@ -1,8 +1,9 @@
1
1
  import { StringIndexedObject } from 'codify-schemas';
2
+ import isObjectsEqual from 'lodash.isequal'
2
3
  import path from 'node:path';
3
4
 
5
+ import { ArrayStatefulParameter, StatefulParameter } from '../stateful-parameter/stateful-parameter.js';
4
6
  import { areArraysEqual, untildify } from '../utils/utils.js';
5
- import { StatefulParameter } from './stateful-parameter.js';
6
7
 
7
8
  /**
8
9
  * The configuration and settings for a resource.
@@ -130,6 +131,7 @@ export type ParameterSettingType =
130
131
  | 'boolean'
131
132
  | 'directory'
132
133
  | 'number'
134
+ | 'object'
133
135
  | 'setting'
134
136
  | 'stateful'
135
137
  | 'string'
@@ -181,7 +183,7 @@ export interface DefaultParameterSetting {
181
183
  *
182
184
  * @return Return true if equal
183
185
  */
184
- isEqual?: (desired: any, current: any) => boolean;
186
+ isEqual?: ((desired: any, current: any) => boolean) | ParameterSettingType;
185
187
 
186
188
  /**
187
189
  * Chose if the resource can be modified instead of re-created when there is a change to this parameter.
@@ -210,7 +212,7 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
210
212
  *
211
213
  * @return Return true if desired is equivalent to current.
212
214
  */
213
- isElementEqual?: (desired: any, current: any) => boolean
215
+ isElementEqual?: ((desired: any, current: any) => boolean) | ParameterSettingType;
214
216
 
215
217
  /**
216
218
  * Filter the contents of the refreshed array by the desired. This way items currently on the system but not
@@ -249,7 +251,7 @@ export interface StatefulParameterSetting extends DefaultParameterSetting {
249
251
  * as a resource and taps, formulas and casks are represented as a stateful parameter. A formula can be installed,
250
252
  * modified and removed (has state) but it is still tied to the overall lifecycle of homebrew.
251
253
  */
252
- definition: StatefulParameter<any, unknown>,
254
+ definition: ArrayStatefulParameter<any, unknown> | StatefulParameter<any, unknown>,
253
255
 
254
256
  /**
255
257
  * The order multiple stateful parameters should be applied in. The order is applied in ascending order (1, 2, 3...).
@@ -263,19 +265,42 @@ const ParameterEqualsDefaults: Partial<Record<ParameterSettingType, (a: unknown,
263
265
  'number': (a: unknown, b: unknown) => Number(a) === Number(b),
264
266
  'string': (a: unknown, b: unknown) => String(a) === String(b),
265
267
  'version': (desired: unknown, current: unknown) => String(current).includes(String(desired)),
266
- 'setting': (a: unknown, b: unknown) => true,
268
+ 'setting': () => true,
269
+ 'object': isObjectsEqual,
267
270
  }
268
271
 
269
- export function resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean {
272
+ export function resolveEqualsFn(parameter: ParameterSetting): (desired: unknown, current: unknown) => boolean {
273
+ const isEqual = resolveFnFromEqualsFnOrString(parameter.isEqual);
274
+
270
275
  if (parameter.type === 'array') {
271
- return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter as ArrayParameterSetting)
276
+ const arrayParameter = parameter as ArrayParameterSetting;
277
+ const isElementEqual = resolveFnFromEqualsFnOrString(arrayParameter.isElementEqual);
278
+
279
+ return isEqual ?? areArraysEqual.bind(areArraysEqual, isElementEqual)
272
280
  }
273
281
 
274
282
  if (parameter.type === 'stateful') {
275
- return resolveEqualsFn((parameter as StatefulParameterSetting).definition.getSettings(), key)
283
+ return resolveEqualsFn((parameter as StatefulParameterSetting).definition.getSettings())
284
+ }
285
+
286
+ return isEqual ?? ParameterEqualsDefaults[parameter.type as ParameterSettingType] ?? (((a, b) => a === b));
287
+ }
288
+
289
+ // This resolves the fn if it is a string.
290
+ // A string can be specified to use a default equals method
291
+ export function resolveFnFromEqualsFnOrString(
292
+ fnOrString: ((a: unknown, b: unknown) => boolean) | ParameterSettingType | undefined,
293
+ ): ((a: unknown, b: unknown) => boolean) | undefined {
294
+
295
+ if (fnOrString && typeof fnOrString === 'string') {
296
+ if (!ParameterEqualsDefaults[fnOrString]) {
297
+ throw new Error(`isEqual of type ${fnOrString} was not found`)
298
+ }
299
+
300
+ return ParameterEqualsDefaults[fnOrString]!
276
301
  }
277
302
 
278
- return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type as ParameterSettingType] ?? (((a, b) => a === b));
303
+ return fnOrString as ((a: unknown, b: unknown) => boolean) | undefined;
279
304
  }
280
305
 
281
306
  const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, (input: any) => Promise<any> | any>> = {
@@ -8,8 +8,9 @@ import {
8
8
  TestResource,
9
9
  TestStatefulParameter
10
10
  } from '../utils/test-utils.test.js';
11
- import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from './resource-settings.js';
12
- import { ResourceController } from './resource-controller.js';
11
+ import { ArrayParameterSetting, ParameterSetting, ResourceSettings } from '../resource/resource-settings.js';
12
+ import { ResourceController } from '../resource/resource-controller.js';
13
+ import { StatefulParameterController } from './stateful-parameter-controller.js';
13
14
 
14
15
  describe('Stateful parameter tests', () => {
15
16
  it('addItem is called the correct number of times', async () => {
@@ -20,11 +21,12 @@ describe('Stateful parameter tests', () => {
20
21
  expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
21
22
  expect(plan.changeSet.parameterChanges.length).to.eq(1);
22
23
 
23
- const testParameter = spy(new TestArrayStatefulParameter());
24
- await testParameter.add((plan.desiredConfig! as any).propZ, plan);
24
+ const parameter = spy(new TestArrayStatefulParameter());
25
+ const controller = new StatefulParameterController(parameter);
26
+ await controller.add((plan.desiredConfig! as any).propZ, plan);
25
27
 
26
- expect(testParameter.addItem.callCount).to.eq(3);
27
- expect(testParameter.removeItem.called).to.be.false;
28
+ expect(parameter.addItem.callCount).to.eq(3);
29
+ expect(parameter.removeItem.called).to.be.false;
28
30
  })
29
31
 
30
32
  it('applyRemoveItem is called the correct number of times', async () => {
@@ -38,11 +40,12 @@ describe('Stateful parameter tests', () => {
38
40
  expect(plan.changeSet.operation).to.eq(ResourceOperation.DESTROY);
39
41
  expect(plan.changeSet.parameterChanges.length).to.eq(1);
40
42
 
41
- const testParameter = spy(new TestArrayStatefulParameter());
42
- await testParameter.remove((plan.currentConfig as any).propZ, plan);
43
+ const parameter = spy(new TestArrayStatefulParameter());
44
+ const controller = new StatefulParameterController(parameter);
45
+ await controller.remove((plan.currentConfig as any).propZ, plan);
43
46
 
44
- expect(testParameter.addItem.called).to.be.false;
45
- expect(testParameter.removeItem.callCount).to.eq(3);
47
+ expect(parameter.addItem.called).to.be.false;
48
+ expect(parameter.removeItem.callCount).to.eq(3);
46
49
  })
47
50
 
48
51
  it('In stateless mode only applyAddItem is called only for modifies', async () => {
@@ -61,8 +64,10 @@ describe('Stateful parameter tests', () => {
61
64
  operation: ParameterOperation.MODIFY,
62
65
  })
63
66
 
67
+
64
68
  const testParameter = spy(parameter);
65
- await testParameter.modify((plan.desiredConfig as any).propZ, (plan.currentConfig as any).propZ, plan);
69
+ const controller = new StatefulParameterController(testParameter);
70
+ await controller.modify((plan.desiredConfig as any).propZ, (plan.currentConfig as any).propZ, plan);
66
71
 
67
72
  expect(testParameter.addItem.calledThrice).to.be.true;
68
73
  expect(testParameter.removeItem.called).to.be.false;
@@ -92,7 +97,8 @@ describe('Stateful parameter tests', () => {
92
97
  operation: ParameterOperation.MODIFY,
93
98
  })
94
99
 
95
- await testParameter.modify((plan.desiredConfig as any).propZ, (plan.currentConfig as any).propZ, plan);
100
+ const controller = new StatefulParameterController(testParameter);
101
+ await controller.modify((plan.desiredConfig as any).propZ, (plan.currentConfig as any).propZ, plan);
96
102
 
97
103
  expect(testParameter.addItem.calledOnce).to.be.true;
98
104
  expect(testParameter.removeItem.called).to.be.false;
@@ -153,4 +159,77 @@ describe('Stateful parameter tests', () => {
153
159
 
154
160
  expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
155
161
  })
162
+
163
+ it('Accepts a string equals value', async () => {
164
+ const testParameter = spy(new class extends TestStatefulParameter {
165
+ getSettings(): ParameterSetting {
166
+ return {
167
+ type: 'string',
168
+ isEqual: 'version'
169
+ }
170
+ }
171
+
172
+ async refresh(): Promise<any> {
173
+ return '20.15.0';
174
+ }
175
+ });
176
+
177
+ const resource = new class extends TestResource {
178
+ getSettings(): ResourceSettings<any> {
179
+ return {
180
+ id: 'type',
181
+ parameterSettings: { propA: { type: 'stateful', definition: testParameter } }
182
+ }
183
+ }
184
+
185
+ async refresh(): Promise<Partial<any> | null> {
186
+ return {};
187
+ }
188
+ }
189
+
190
+ const controller = new ResourceController(resource);
191
+ const plan = await controller.plan({
192
+ propA: '20.15',
193
+ } as any)
194
+
195
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
196
+ })
197
+
198
+ it('Accepts a string isElementEquals value', async () => {
199
+ const testParameter = spy(new class extends TestStatefulParameter {
200
+ getSettings(): ParameterSetting {
201
+ return {
202
+ type: 'array',
203
+ isElementEqual: 'version'
204
+ }
205
+ }
206
+
207
+ async refresh(): Promise<any> {
208
+ return [
209
+ '20.15.0',
210
+ '20.18.0'
211
+ ]
212
+ }
213
+ });
214
+
215
+ const resource = new class extends TestResource {
216
+ getSettings(): ResourceSettings<any> {
217
+ return {
218
+ id: 'type',
219
+ parameterSettings: { propA: { type: 'stateful', definition: testParameter } }
220
+ }
221
+ }
222
+
223
+ async refresh(): Promise<Partial<any> | null> {
224
+ return {};
225
+ }
226
+ }
227
+
228
+ const controller = new ResourceController(resource);
229
+ const plan = await controller.plan({
230
+ propA: ['20.15', '20.18'],
231
+ } as any)
232
+
233
+ expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
234
+ })
156
235
  })