codify-plugin-lib 1.0.42 → 1.0.43
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/change-set.d.ts +0 -1
- package/dist/entities/change-set.js +0 -13
- package/dist/entities/plan.d.ts +1 -1
- package/dist/entities/plan.js +3 -6
- package/dist/entities/resource.js +3 -3
- package/dist/entities/stateful-parameter.d.ts +3 -2
- package/dist/entities/stateful-parameter.js +15 -2
- package/package.json +1 -1
- package/src/entities/change-set.ts +14 -14
- package/src/entities/plan.ts +5 -8
- package/src/entities/resource-parameters.test.ts +2 -1
- package/src/entities/resource.test.ts +6 -3
- package/src/entities/resource.ts +5 -4
- package/src/entities/stateful-parameter.test.ts +118 -0
- package/src/entities/stateful-parameter.ts +19 -4
|
@@ -12,7 +12,6 @@ export declare class ChangeSet<T extends StringIndexedObject> {
|
|
|
12
12
|
constructor(operation: ResourceOperation, parameterChanges: Array<ParameterChange<T>>);
|
|
13
13
|
get desiredParameters(): T;
|
|
14
14
|
get currentParameters(): T;
|
|
15
|
-
static newCreate<T extends {}>(desiredConfig: T): ChangeSet<StringIndexedObject>;
|
|
16
15
|
static calculateParameterChangeSet<T extends StringIndexedObject>(desired: T | null, current: T | null, options: {
|
|
17
16
|
statefulMode: boolean;
|
|
18
17
|
parameterConfigurations?: Record<keyof T, ParameterConfiguration>;
|
|
@@ -20,19 +20,6 @@ export class ChangeSet {
|
|
|
20
20
|
[pc.name]: pc.previousValue,
|
|
21
21
|
}), {});
|
|
22
22
|
}
|
|
23
|
-
static newCreate(desiredConfig) {
|
|
24
|
-
const parameterChangeSet = Object.entries(desiredConfig)
|
|
25
|
-
.filter(([k,]) => k !== 'type' && k !== 'name')
|
|
26
|
-
.map(([k, v]) => {
|
|
27
|
-
return {
|
|
28
|
-
name: k,
|
|
29
|
-
operation: ParameterOperation.ADD,
|
|
30
|
-
previousValue: null,
|
|
31
|
-
newValue: v,
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
return new ChangeSet(ResourceOperation.CREATE, parameterChangeSet);
|
|
35
|
-
}
|
|
36
23
|
static calculateParameterChangeSet(desired, current, options) {
|
|
37
24
|
if (options.statefulMode) {
|
|
38
25
|
return ChangeSet.calculateStatefulModeChangeSet(desired, current, options.parameterConfigurations);
|
package/dist/entities/plan.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare class Plan<T extends StringIndexedObject> {
|
|
|
6
6
|
changeSet: ChangeSet<T>;
|
|
7
7
|
resourceMetadata: ResourceConfig;
|
|
8
8
|
constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig);
|
|
9
|
-
static create<T extends StringIndexedObject>(
|
|
9
|
+
static create<T extends StringIndexedObject>(desiredParameters: Partial<T> | null, currentParameters: Partial<T> | null, resourceMetadata: ResourceConfig, configuration: PlanConfiguration<T>): Plan<T>;
|
|
10
10
|
getResourceType(): string;
|
|
11
11
|
static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan']): Plan<T>;
|
|
12
12
|
get desiredConfig(): T;
|
package/dist/entities/plan.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ChangeSet } from './change-set.js';
|
|
2
2
|
import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
|
-
import { splitUserConfig } from '../utils/utils.js';
|
|
5
4
|
export class Plan {
|
|
6
5
|
id;
|
|
7
6
|
changeSet;
|
|
@@ -11,19 +10,17 @@ export class Plan {
|
|
|
11
10
|
this.changeSet = changeSet;
|
|
12
11
|
this.resourceMetadata = resourceMetadata;
|
|
13
12
|
}
|
|
14
|
-
static create(
|
|
13
|
+
static create(desiredParameters, currentParameters, resourceMetadata, configuration) {
|
|
15
14
|
const parameterConfigurations = configuration.parameterConfigurations ?? {};
|
|
16
15
|
const statefulParameterNames = new Set([...Object.entries(parameterConfigurations)]
|
|
17
16
|
.filter(([k, v]) => v.isStatefulParameter)
|
|
18
17
|
.map(([k, v]) => k));
|
|
19
|
-
const { resourceMetadata, parameters: desiredParameters } = splitUserConfig(desiredConfig);
|
|
20
|
-
const { parameters: currentParameters } = currentConfig != null ? splitUserConfig(currentConfig) : { parameters: null };
|
|
21
18
|
const parameterChangeSet = ChangeSet.calculateParameterChangeSet(desiredParameters, currentParameters, { statefulMode: configuration.statefulMode, parameterConfigurations });
|
|
22
19
|
let resourceOperation;
|
|
23
|
-
if (!
|
|
20
|
+
if (!currentParameters && desiredParameters) {
|
|
24
21
|
resourceOperation = ResourceOperation.CREATE;
|
|
25
22
|
}
|
|
26
|
-
else if (
|
|
23
|
+
else if (currentParameters && !desiredParameters) {
|
|
27
24
|
resourceOperation = ResourceOperation.DESTROY;
|
|
28
25
|
}
|
|
29
26
|
else {
|
|
@@ -33,18 +33,18 @@ export class Resource {
|
|
|
33
33
|
const keysToRefresh = new Set(Object.keys(resourceParameters));
|
|
34
34
|
const currentParameters = await this.refresh(keysToRefresh);
|
|
35
35
|
if (currentParameters == null) {
|
|
36
|
-
return Plan.create(
|
|
36
|
+
return Plan.create(desiredParameters, null, resourceMetadata, planConfiguration);
|
|
37
37
|
}
|
|
38
38
|
this.validateRefreshResults(currentParameters, keysToRefresh);
|
|
39
39
|
for (const statefulParameter of statefulParameters) {
|
|
40
40
|
const desiredValue = desiredParameters[statefulParameter.name];
|
|
41
|
-
let currentValue = await statefulParameter.refresh(
|
|
41
|
+
let currentValue = await statefulParameter.refresh() ?? undefined;
|
|
42
42
|
if (Array.isArray(currentValue) && Array.isArray(desiredValue) && !planConfiguration.statefulMode) {
|
|
43
43
|
currentValue = currentValue.filter((p) => desiredValue?.includes(p));
|
|
44
44
|
}
|
|
45
45
|
currentParameters[statefulParameter.name] = currentValue;
|
|
46
46
|
}
|
|
47
|
-
return Plan.create(
|
|
47
|
+
return Plan.create(desiredParameters, currentParameters, resourceMetadata, planConfiguration);
|
|
48
48
|
}
|
|
49
49
|
async apply(plan) {
|
|
50
50
|
if (plan.getResourceType() !== this.typeId) {
|
|
@@ -12,17 +12,18 @@ export declare abstract class StatefulParameter<T extends StringIndexedObject, V
|
|
|
12
12
|
readonly name: keyof T;
|
|
13
13
|
readonly configuration: StatefulParameterConfiguration<T>;
|
|
14
14
|
protected constructor(configuration: StatefulParameterConfiguration<T>);
|
|
15
|
-
abstract refresh(
|
|
15
|
+
abstract refresh(): Promise<V | null>;
|
|
16
16
|
abstract applyAdd(valueToAdd: V, plan: Plan<T>): Promise<void>;
|
|
17
17
|
abstract applyModify(newValue: V, previousValue: V, allowDeletes: boolean, plan: Plan<T>): Promise<void>;
|
|
18
18
|
abstract applyRemove(valueToRemove: V, plan: Plan<T>): Promise<void>;
|
|
19
19
|
}
|
|
20
20
|
export declare abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> extends StatefulParameter<T, any> {
|
|
21
|
+
configuration: ArrayStatefulParameterConfiguration<T>;
|
|
21
22
|
constructor(configuration: ArrayStatefulParameterConfiguration<T>);
|
|
22
23
|
applyAdd(valuesToAdd: V[], plan: Plan<T>): Promise<void>;
|
|
23
24
|
applyModify(newValues: V[], previousValues: V[], allowDeletes: boolean, plan: Plan<T>): Promise<void>;
|
|
24
25
|
applyRemove(valuesToRemove: V[], plan: Plan<T>): Promise<void>;
|
|
25
|
-
abstract refresh(
|
|
26
|
+
abstract refresh(): Promise<V[] | null>;
|
|
26
27
|
abstract applyAddItem(item: V, plan: Plan<T>): Promise<void>;
|
|
27
28
|
abstract applyRemoveItem(item: V, plan: Plan<T>): Promise<void>;
|
|
28
29
|
}
|
|
@@ -7,8 +7,10 @@ export class StatefulParameter {
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
export class ArrayStatefulParameter extends StatefulParameter {
|
|
10
|
+
configuration;
|
|
10
11
|
constructor(configuration) {
|
|
11
12
|
super(configuration);
|
|
13
|
+
this.configuration = configuration;
|
|
12
14
|
}
|
|
13
15
|
async applyAdd(valuesToAdd, plan) {
|
|
14
16
|
for (const value of valuesToAdd) {
|
|
@@ -16,8 +18,19 @@ export class ArrayStatefulParameter extends StatefulParameter {
|
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
async applyModify(newValues, previousValues, allowDeletes, plan) {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
+
const configuration = this.configuration;
|
|
22
|
+
const valuesToAdd = newValues.filter((n) => !previousValues.some((p) => {
|
|
23
|
+
if ((configuration).isElementEqual) {
|
|
24
|
+
return configuration.isElementEqual(n, p);
|
|
25
|
+
}
|
|
26
|
+
return n === p;
|
|
27
|
+
}));
|
|
28
|
+
const valuesToRemove = previousValues.filter((p) => !newValues.some((n) => {
|
|
29
|
+
if ((configuration).isElementEqual) {
|
|
30
|
+
return configuration.isElementEqual(n, p);
|
|
31
|
+
}
|
|
32
|
+
return n === p;
|
|
33
|
+
}));
|
|
21
34
|
for (const value of valuesToAdd) {
|
|
22
35
|
await this.applyAddItem(value, plan);
|
|
23
36
|
}
|
package/package.json
CHANGED
|
@@ -43,20 +43,20 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
43
43
|
// const operation = ChangeSet.combineResourceOperations(prev, );
|
|
44
44
|
// }
|
|
45
45
|
|
|
46
|
-
static newCreate<T extends {}>(desiredConfig: T) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
46
|
+
// static newCreate<T extends {}>(desiredConfig: T) {
|
|
47
|
+
// const parameterChangeSet = Object.entries(desiredConfig)
|
|
48
|
+
// .filter(([k,]) => k !== 'type' && k !== 'name')
|
|
49
|
+
// .map(([k, v]) => {
|
|
50
|
+
// return {
|
|
51
|
+
// name: k,
|
|
52
|
+
// operation: ParameterOperation.ADD,
|
|
53
|
+
// previousValue: null,
|
|
54
|
+
// newValue: v,
|
|
55
|
+
// }
|
|
56
|
+
// })
|
|
57
|
+
//
|
|
58
|
+
// return new ChangeSet(ResourceOperation.CREATE, parameterChangeSet);
|
|
59
|
+
// }
|
|
60
60
|
|
|
61
61
|
static calculateParameterChangeSet<T extends StringIndexedObject>(
|
|
62
62
|
desired: T | null,
|
package/src/entities/plan.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from 'codify-schemas';
|
|
10
10
|
import { randomUUID } from 'crypto';
|
|
11
11
|
import { ParameterConfiguration, PlanConfiguration } from './plan-types.js';
|
|
12
|
-
import { splitUserConfig } from '../utils/utils.js';
|
|
13
12
|
|
|
14
13
|
export class Plan<T extends StringIndexedObject> {
|
|
15
14
|
id: string;
|
|
@@ -23,8 +22,9 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
static create<T extends StringIndexedObject>(
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
desiredParameters: Partial<T> | null,
|
|
26
|
+
currentParameters: Partial<T> | null,
|
|
27
|
+
resourceMetadata: ResourceConfig,
|
|
28
28
|
configuration: PlanConfiguration<T>
|
|
29
29
|
): Plan<T> {
|
|
30
30
|
const parameterConfigurations = configuration.parameterConfigurations ?? {} as Record<keyof T, ParameterConfiguration>;
|
|
@@ -34,9 +34,6 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
34
34
|
.map(([k, v]) => k)
|
|
35
35
|
);
|
|
36
36
|
|
|
37
|
-
const { resourceMetadata, parameters: desiredParameters } = splitUserConfig(desiredConfig);
|
|
38
|
-
const { parameters: currentParameters } = currentConfig != null ? splitUserConfig(currentConfig) : { parameters: null };
|
|
39
|
-
|
|
40
37
|
|
|
41
38
|
// TODO: After adding in state files, need to calculate deletes here
|
|
42
39
|
// Where current config exists and state config exists but desired config doesn't
|
|
@@ -51,9 +48,9 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
51
48
|
);
|
|
52
49
|
|
|
53
50
|
let resourceOperation: ResourceOperation;
|
|
54
|
-
if (!
|
|
51
|
+
if (!currentParameters && desiredParameters) {
|
|
55
52
|
resourceOperation = ResourceOperation.CREATE;
|
|
56
|
-
} else if (
|
|
53
|
+
} else if (currentParameters && !desiredParameters) {
|
|
57
54
|
resourceOperation = ResourceOperation.DESTROY;
|
|
58
55
|
} else {
|
|
59
56
|
resourceOperation = parameterChangeSet
|
|
@@ -50,8 +50,9 @@ describe('Resource parameters tests', () => {
|
|
|
50
50
|
const resourceSpy = spy(resource);
|
|
51
51
|
const result = await resourceSpy.apply(
|
|
52
52
|
Plan.create<TestConfig>(
|
|
53
|
-
{
|
|
53
|
+
{ propA: 'a', propB: 0, propC: 'b' },
|
|
54
54
|
null,
|
|
55
|
+
{ type: 'resource' },
|
|
55
56
|
{ statefulMode: false },
|
|
56
57
|
)
|
|
57
58
|
);
|
|
@@ -136,6 +136,7 @@ describe('Resource tests', () => {
|
|
|
136
136
|
const result = await resourceSpy.apply(
|
|
137
137
|
Plan.create<TestConfig>(
|
|
138
138
|
{ type: 'resource', propA: 'a', propB: 0 },
|
|
139
|
+
null,
|
|
139
140
|
{ type: 'resource' },
|
|
140
141
|
{ statefulMode: false },
|
|
141
142
|
)
|
|
@@ -154,8 +155,9 @@ describe('Resource tests', () => {
|
|
|
154
155
|
const resourceSpy = spy(resource);
|
|
155
156
|
const result = await resourceSpy.apply(
|
|
156
157
|
Plan.create<TestConfig>(
|
|
158
|
+
null,
|
|
159
|
+
{ propA: 'a', propB: 0 },
|
|
157
160
|
{ type: 'resource' },
|
|
158
|
-
{ type: 'resource', propA: 'a', propB: 0 },
|
|
159
161
|
{ statefulMode: true },
|
|
160
162
|
)
|
|
161
163
|
)
|
|
@@ -173,8 +175,9 @@ describe('Resource tests', () => {
|
|
|
173
175
|
const resourceSpy = spy(resource);
|
|
174
176
|
const result = await resourceSpy.apply(
|
|
175
177
|
Plan.create<TestConfig>(
|
|
176
|
-
{
|
|
177
|
-
{
|
|
178
|
+
{ propA: 'a', propB: 0 },
|
|
179
|
+
{ propA: 'b', propB: -1 },
|
|
180
|
+
{ type: 'resource' },
|
|
178
181
|
{ statefulMode: true },
|
|
179
182
|
)
|
|
180
183
|
);
|
package/src/entities/resource.ts
CHANGED
|
@@ -66,7 +66,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
66
66
|
|
|
67
67
|
// Short circuit here. If resource is non-existent, then there's no point checking stateful parameters
|
|
68
68
|
if (currentParameters == null) {
|
|
69
|
-
return Plan.create(
|
|
69
|
+
return Plan.create(desiredParameters, null, resourceMetadata, planConfiguration);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
this.validateRefreshResults(currentParameters, keysToRefresh);
|
|
@@ -76,7 +76,7 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
76
76
|
for(const statefulParameter of statefulParameters) {
|
|
77
77
|
const desiredValue = desiredParameters[statefulParameter.name];
|
|
78
78
|
|
|
79
|
-
let currentValue = await statefulParameter.refresh(
|
|
79
|
+
let currentValue = await statefulParameter.refresh() ?? undefined;
|
|
80
80
|
|
|
81
81
|
// In stateless mode, filter the refreshed parameters by the desired to ensure that no deletes happen
|
|
82
82
|
if (Array.isArray(currentValue) && Array.isArray(desiredValue) && !planConfiguration.statefulMode) {
|
|
@@ -87,8 +87,9 @@ export abstract class Resource<T extends StringIndexedObject> {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
return Plan.create(
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
desiredParameters,
|
|
91
|
+
currentParameters as Partial<T>,
|
|
92
|
+
resourceMetadata,
|
|
92
93
|
planConfiguration,
|
|
93
94
|
)
|
|
94
95
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ArrayStatefulParameter, ArrayStatefulParameterConfiguration, } from './stateful-parameter.js';
|
|
3
|
+
import { Plan } from './plan.js';
|
|
4
|
+
import { spy } from 'sinon';
|
|
5
|
+
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
6
|
+
|
|
7
|
+
interface TestConfig {
|
|
8
|
+
propA: string[];
|
|
9
|
+
[x: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class TestArrayParameter extends ArrayStatefulParameter<TestConfig, string> {
|
|
13
|
+
constructor(configuration?: ArrayStatefulParameterConfiguration<TestConfig>) {
|
|
14
|
+
super(configuration ?? {
|
|
15
|
+
name: 'propA'
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async applyAddItem(item: string, plan: Plan<TestConfig>): Promise<void> {}
|
|
20
|
+
async applyRemoveItem(item: string, plan: Plan<TestConfig>): Promise<void> {}
|
|
21
|
+
|
|
22
|
+
async refresh(): Promise<string[] | null> {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
describe('Stateful parameter tests', () => {
|
|
29
|
+
it('applyAddItem is called the correct number of times', async () => {
|
|
30
|
+
const plan = Plan.create<TestConfig>(
|
|
31
|
+
{ propA: ['a', 'b', 'c'] },
|
|
32
|
+
null,
|
|
33
|
+
{ type: 'typeA' },
|
|
34
|
+
{ statefulMode: false }
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.CREATE);
|
|
38
|
+
expect(plan.changeSet.parameterChanges.length).to.eq(1);
|
|
39
|
+
|
|
40
|
+
const testParameter = spy(new TestArrayParameter());
|
|
41
|
+
await testParameter.applyAdd(plan.desiredConfig.propA, plan);
|
|
42
|
+
|
|
43
|
+
expect(testParameter.applyAddItem.callCount).to.eq(3);
|
|
44
|
+
expect(testParameter.applyRemoveItem.called).to.be.false;
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('applyRemoveItem is called the correct number of times', async () => {
|
|
48
|
+
const plan = Plan.create<TestConfig>(
|
|
49
|
+
null,
|
|
50
|
+
{ propA: ['a', 'b', 'c'] },
|
|
51
|
+
{ type: 'typeA' },
|
|
52
|
+
{ statefulMode: true }
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.DESTROY);
|
|
56
|
+
expect(plan.changeSet.parameterChanges.length).to.eq(1);
|
|
57
|
+
|
|
58
|
+
const testParameter = spy(new TestArrayParameter());
|
|
59
|
+
await testParameter.applyRemove(plan.currentConfig.propA, plan);
|
|
60
|
+
|
|
61
|
+
expect(testParameter.applyAddItem.called).to.be.false;
|
|
62
|
+
expect(testParameter.applyRemoveItem.callCount).to.eq(3);
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('In stateless mode only applyAddItem is called only for modifies', async () => {
|
|
66
|
+
const plan = Plan.create<TestConfig>(
|
|
67
|
+
{ propA: ['a', 'c', 'd', 'e', 'f'] }, // b to remove, d, e, f to add
|
|
68
|
+
{ propA: ['a', 'b', 'c'] },
|
|
69
|
+
{ type: 'typeA' },
|
|
70
|
+
{ statefulMode: true, parameterConfigurations: { propA: { isStatefulParameter: true }} }
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.MODIFY);
|
|
74
|
+
expect(plan.changeSet.parameterChanges[0]).toMatchObject({
|
|
75
|
+
name: 'propA',
|
|
76
|
+
previousValue: ['a', 'b', 'c'],
|
|
77
|
+
newValue: ['a', 'c', 'd', 'e', 'f'],
|
|
78
|
+
operation: ParameterOperation.MODIFY,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const testParameter = spy(new TestArrayParameter());
|
|
82
|
+
await testParameter.applyModify(plan.desiredConfig.propA, plan.currentConfig.propA, false, plan);
|
|
83
|
+
|
|
84
|
+
expect(testParameter.applyAddItem.calledThrice).to.be.true;
|
|
85
|
+
expect(testParameter.applyRemoveItem.called).to.be.false;
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('isElementEqual is called for modifies', async () => {
|
|
89
|
+
const plan = Plan.create<TestConfig>(
|
|
90
|
+
{ propA: ['9.12', '9.13'] }, // b to remove, d, e, f to add
|
|
91
|
+
{ propA: ['9.12.9'] },
|
|
92
|
+
{ type: 'typeA' },
|
|
93
|
+
{ statefulMode: false, parameterConfigurations: { propA: { isStatefulParameter: true }} }
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.MODIFY);
|
|
97
|
+
expect(plan.changeSet.parameterChanges[0]).toMatchObject({
|
|
98
|
+
name: 'propA',
|
|
99
|
+
previousValue: ['9.12.9'],
|
|
100
|
+
newValue: ['9.12', '9.13'],
|
|
101
|
+
operation: ParameterOperation.MODIFY,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const testParameter = spy(new class extends TestArrayParameter {
|
|
105
|
+
constructor() {
|
|
106
|
+
super({
|
|
107
|
+
name: 'propA',
|
|
108
|
+
isElementEqual: (desired, current) => current.includes(desired),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await testParameter.applyModify(plan.desiredConfig.propA, plan.currentConfig.propA, false, plan);
|
|
114
|
+
|
|
115
|
+
expect(testParameter.applyAddItem.calledOnce).to.be.true;
|
|
116
|
+
expect(testParameter.applyRemoveItem.called).to.be.false;
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -21,7 +21,7 @@ export abstract class StatefulParameter<T extends StringIndexedObject, V extends
|
|
|
21
21
|
this.configuration = configuration
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
abstract refresh(
|
|
24
|
+
abstract refresh(): Promise<V | null>;
|
|
25
25
|
|
|
26
26
|
// TODO: Add an additional parameter here for what has actually changed.
|
|
27
27
|
abstract applyAdd(valueToAdd: V, plan: Plan<T>): Promise<void>;
|
|
@@ -30,9 +30,11 @@ export abstract class StatefulParameter<T extends StringIndexedObject, V extends
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> extends StatefulParameter<T, any>{
|
|
33
|
+
configuration: ArrayStatefulParameterConfiguration<T>;
|
|
33
34
|
|
|
34
35
|
constructor(configuration: ArrayStatefulParameterConfiguration<T>) {
|
|
35
36
|
super(configuration);
|
|
37
|
+
this.configuration = configuration;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
async applyAdd(valuesToAdd: V[], plan: Plan<T>): Promise<void> {
|
|
@@ -42,8 +44,21 @@ export abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> e
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
async applyModify(newValues: V[], previousValues: V[], allowDeletes: boolean, plan: Plan<T>): Promise<void> {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
+
const configuration = this.configuration as ArrayStatefulParameterConfiguration<T>;
|
|
48
|
+
|
|
49
|
+
const valuesToAdd = newValues.filter((n) => !previousValues.some((p) => {
|
|
50
|
+
if ((configuration).isElementEqual) {
|
|
51
|
+
return configuration.isElementEqual(n, p);
|
|
52
|
+
}
|
|
53
|
+
return n === p;
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const valuesToRemove = previousValues.filter((p) => !newValues.some((n) => {
|
|
57
|
+
if ((configuration).isElementEqual) {
|
|
58
|
+
return configuration.isElementEqual(n, p);
|
|
59
|
+
}
|
|
60
|
+
return n === p;
|
|
61
|
+
}));
|
|
47
62
|
|
|
48
63
|
for (const value of valuesToAdd) {
|
|
49
64
|
await this.applyAddItem(value, plan)
|
|
@@ -62,7 +77,7 @@ export abstract class ArrayStatefulParameter<T extends StringIndexedObject, V> e
|
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
abstract refresh(
|
|
80
|
+
abstract refresh(): Promise<V[] | null>;
|
|
66
81
|
abstract applyAddItem(item: V, plan: Plan<T>): Promise<void>;
|
|
67
82
|
abstract applyRemoveItem(item: V, plan: Plan<T>): Promise<void>;
|
|
68
83
|
}
|