@quadnix/octo 0.0.2 → 0.0.3

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 (86) hide show
  1. package/package.json +6 -1
  2. package/.eslintrc.cjs +0 -26
  3. package/dist/tsconfig.build.tsbuildinfo +0 -1
  4. package/src/app.type.ts +0 -56
  5. package/src/decorators/action.decorator.spec.ts +0 -43
  6. package/src/decorators/action.decorator.ts +0 -24
  7. package/src/decorators/anchor.decorator.ts +0 -10
  8. package/src/decorators/container.spec.ts +0 -104
  9. package/src/decorators/container.ts +0 -133
  10. package/src/decorators/factory.decorator.ts +0 -13
  11. package/src/decorators/model.decorator.ts +0 -10
  12. package/src/decorators/module.decorator.ts +0 -13
  13. package/src/decorators/overlay.decorator.ts +0 -10
  14. package/src/decorators/resource.decorator.ts +0 -10
  15. package/src/decorators/test-container.spec.ts +0 -50
  16. package/src/decorators/test-container.ts +0 -56
  17. package/src/functions/dependency/dependency.model.spec.ts +0 -43
  18. package/src/functions/dependency/dependency.model.ts +0 -178
  19. package/src/functions/diff/diff-metadata.model.ts +0 -51
  20. package/src/functions/diff/diff.model.ts +0 -33
  21. package/src/functions/diff/diff.utility.ts +0 -163
  22. package/src/functions/module/module.abstract.ts +0 -59
  23. package/src/functions/module/module.interface.ts +0 -5
  24. package/src/index.ts +0 -62
  25. package/src/models/action.interface.ts +0 -35
  26. package/src/models/app/app.interface.ts +0 -5
  27. package/src/models/app/app.model.ts +0 -111
  28. package/src/models/deployment/deployment.interface.ts +0 -5
  29. package/src/models/deployment/deployment.model.ts +0 -35
  30. package/src/models/environment/environment.interface.ts +0 -6
  31. package/src/models/environment/environment.model.spec.ts +0 -125
  32. package/src/models/environment/environment.model.ts +0 -55
  33. package/src/models/execution/execution.interface.ts +0 -8
  34. package/src/models/execution/execution.model.spec.ts +0 -62
  35. package/src/models/execution/execution.model.ts +0 -89
  36. package/src/models/image/image.interface.ts +0 -8
  37. package/src/models/image/image.model.ts +0 -62
  38. package/src/models/model.abstract.ts +0 -324
  39. package/src/models/model.interface.ts +0 -42
  40. package/src/models/pipeline/pipeline.interface.ts +0 -6
  41. package/src/models/pipeline/pipeline.model.ts +0 -47
  42. package/src/models/region/region.interface.ts +0 -5
  43. package/src/models/region/region.model.spec.ts +0 -162
  44. package/src/models/region/region.model.ts +0 -48
  45. package/src/models/server/server.interface.ts +0 -7
  46. package/src/models/server/server.model.ts +0 -56
  47. package/src/models/service/service.interface.ts +0 -5
  48. package/src/models/service/service.model.ts +0 -25
  49. package/src/models/support/support.interface.ts +0 -6
  50. package/src/models/support/support.model.spec.ts +0 -78
  51. package/src/models/support/support.model.ts +0 -52
  52. package/src/overlays/anchor.abstract.ts +0 -41
  53. package/src/overlays/anchor.interface.ts +0 -7
  54. package/src/overlays/anchor.spec.ts +0 -41
  55. package/src/overlays/overlay-data.repository.ts +0 -76
  56. package/src/overlays/overlay.abstract.ts +0 -64
  57. package/src/overlays/overlay.interface.ts +0 -9
  58. package/src/overlays/overlay.service.ts +0 -38
  59. package/src/overlays/overlay.spec.ts +0 -86
  60. package/src/resources/resource-action.interface.ts +0 -9
  61. package/src/resources/resource-data.repository.spec.ts +0 -133
  62. package/src/resources/resource-data.repository.ts +0 -92
  63. package/src/resources/resource.abstract.ts +0 -108
  64. package/src/resources/resource.interface.ts +0 -7
  65. package/src/resources/resource.spec.ts +0 -60
  66. package/src/resources/shared-resource.abstract.ts +0 -53
  67. package/src/resources/shared-resource.spec.ts +0 -133
  68. package/src/services/serialization/model/__snapshots__/model-serialization.service.spec.ts.snap +0 -656
  69. package/src/services/serialization/model/model-serialization.service.spec.ts +0 -195
  70. package/src/services/serialization/model/model-serialization.service.ts +0 -172
  71. package/src/services/serialization/resource/__snapshots__/resource-serialization.service.spec.ts.snap +0 -284
  72. package/src/services/serialization/resource/resource-serialization.service.spec.ts +0 -311
  73. package/src/services/serialization/resource/resource-serialization.service.ts +0 -166
  74. package/src/services/state-management/local.state-provider.spec.ts +0 -42
  75. package/src/services/state-management/local.state-provider.ts +0 -23
  76. package/src/services/state-management/state-management.service.ts +0 -43
  77. package/src/services/state-management/state-provider.interface.ts +0 -5
  78. package/src/services/transaction/__snapshots__/transaction.service.spec.ts.snap +0 -284
  79. package/src/services/transaction/transaction.service.spec.ts +0 -823
  80. package/src/services/transaction/transaction.service.ts +0 -386
  81. package/test/app.e2e-spec.ts +0 -104
  82. package/test/helpers/test-classes.ts +0 -31
  83. package/test/models/__snapshots__/model.e2e-spec.ts.snap +0 -451
  84. package/test/models/model.e2e-spec.ts +0 -366
  85. package/tsconfig.build.json +0 -4
  86. package/tsconfig.json +0 -9
@@ -1,823 +0,0 @@
1
- import { jest } from '@jest/globals';
2
- import { SharedTestResource, TestAnchor, TestOverlay, TestResource } from '../../../test/helpers/test-classes.js';
3
- import { ActionInputs, ActionOutputs, UnknownResource } from '../../app.type.js';
4
- import { Container } from '../../decorators/container.js';
5
- import { DiffMetadata } from '../../functions/diff/diff-metadata.model.js';
6
- import { Diff, DiffAction } from '../../functions/diff/diff.model.js';
7
- import { IAction } from '../../models/action.interface.js';
8
- import { App } from '../../models/app/app.model.js';
9
- import { Environment } from '../../models/environment/environment.model.js';
10
- import { Region } from '../../models/region/region.model.js';
11
- import { OverlayDataRepository, OverlayDataRepositoryFactory } from '../../overlays/overlay-data.repository.js';
12
- import { IResourceAction } from '../../resources/resource-action.interface.js';
13
- import { ResourceDataRepository, ResourceDataRepositoryFactory } from '../../resources/resource-data.repository.js';
14
- import { TransactionService } from './transaction.service.js';
15
-
16
- describe('TransactionService UT', () => {
17
- beforeEach(() => {
18
- Container.registerFactory(OverlayDataRepository, OverlayDataRepositoryFactory);
19
- Container.get(OverlayDataRepository, { args: [true, [], []] });
20
-
21
- Container.registerFactory(ResourceDataRepository, ResourceDataRepositoryFactory);
22
- Container.get(ResourceDataRepository, { args: [true] });
23
- });
24
-
25
- afterEach(() => {
26
- Container.reset();
27
-
28
- jest.restoreAllMocks();
29
- });
30
-
31
- describe('applyModels()', () => {
32
- const universalModelAction: IAction<ActionInputs, ActionOutputs> = {
33
- ACTION_NAME: 'universal',
34
- collectInput: () => [],
35
- filter: () => true,
36
- handle: jest.fn() as jest.Mocked<any>,
37
- revert: jest.fn() as jest.Mocked<any>,
38
- };
39
-
40
- it('should return empty transaction if diffs is empty', async () => {
41
- const service = new TransactionService();
42
- const generator = service.beginTransaction([], { yieldModelTransaction: true });
43
-
44
- const result = await generator.next();
45
-
46
- expect(result.value).toMatchSnapshot();
47
- });
48
-
49
- it('should throw error when matching action not found', async () => {
50
- const app = new App('app');
51
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
52
-
53
- const service = new TransactionService();
54
- const generator = service.beginTransaction(diffs, { yieldModelTransaction: true });
55
-
56
- await expect(async () => {
57
- await generator.next();
58
- }).rejects.toThrowErrorMatchingInlineSnapshot(`"No matching action given for diff!"`);
59
- });
60
-
61
- it('should throw error when action inputs are not found', async () => {
62
- const app = new App('app');
63
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
64
-
65
- const action: IAction<ActionInputs, ActionOutputs> = {
66
- ACTION_NAME: 'test',
67
- collectInput: () => ['input.key1'],
68
- filter: () => true,
69
- handle: jest.fn() as jest.Mocked<any>,
70
- revert: jest.fn() as jest.Mocked<any>,
71
- };
72
-
73
- const service = new TransactionService();
74
- service.registerModelActions([action]);
75
- const generator = service.beginTransaction(diffs, { yieldModelTransaction: true });
76
-
77
- await expect(async () => {
78
- await generator.next();
79
- }).rejects.toThrowErrorMatchingInlineSnapshot(`"No matching input found to process action!"`);
80
- });
81
-
82
- it('should throw error when action resource inputs are not found', async () => {
83
- const app = new App('app');
84
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
85
-
86
- const action: IAction<ActionInputs, ActionOutputs> = {
87
- ACTION_NAME: 'test',
88
- collectInput: () => ['resource.key1'],
89
- filter: () => true,
90
- handle: jest.fn() as jest.Mocked<any>,
91
- revert: jest.fn() as jest.Mocked<any>,
92
- };
93
-
94
- const service = new TransactionService();
95
- service.registerModelActions([action]);
96
- const generator = service.beginTransaction(diffs, { yieldModelTransaction: true });
97
-
98
- await expect(async () => {
99
- await generator.next();
100
- }).rejects.toThrowErrorMatchingInlineSnapshot(`"No matching input found to process action!"`);
101
- });
102
-
103
- it('should skip processing diffs that are already applied', async () => {
104
- const app = new App('app');
105
-
106
- const diff = new Diff(app, DiffAction.ADD, 'name', 'app');
107
- const diffMetadata = new DiffMetadata(diff, [universalModelAction]);
108
- diffMetadata.applied = true;
109
- diffMetadata.applyOrder = 0;
110
-
111
- const service = new TransactionService();
112
- const result = await service['applyModels']([diffMetadata], new ResourceDataRepository([]));
113
-
114
- expect(result).toMatchSnapshot();
115
- });
116
-
117
- it('should only process 1 matching diff when duplicates found', async () => {
118
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
119
-
120
- const app = new App('app');
121
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app'), new Diff(app, DiffAction.ADD, 'name', 'app')];
122
-
123
- const service = new TransactionService();
124
- service.registerModelActions([universalModelAction]);
125
- const generator = service.beginTransaction(diffs, { yieldModelTransaction: true });
126
-
127
- const result = await generator.next();
128
-
129
- expect(result.value).toMatchSnapshot();
130
- });
131
-
132
- it('should call action and collect all input and output', async () => {
133
- const app = new App('app');
134
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
135
-
136
- const action: IAction<ActionInputs, ActionOutputs> = {
137
- ACTION_NAME: 'test',
138
- collectInput: () => ['input.key1'],
139
- filter: () => true,
140
- handle: jest.fn() as jest.Mocked<any>,
141
- revert: jest.fn() as jest.Mocked<any>,
142
- };
143
- (action.handle as jest.Mocked<any>).mockResolvedValue({
144
- resource1: new TestResource('resource1'),
145
- });
146
-
147
- const service = new TransactionService();
148
- service.registerInputs({ 'input.key1': 'value1' });
149
- service.registerModelActions([action]);
150
-
151
- const generator = service.beginTransaction(diffs, { yieldNewResources: true });
152
-
153
- const newResources = await generator.next();
154
-
155
- expect(action.handle).toHaveBeenCalledTimes(1);
156
- expect((action.handle as jest.Mock).mock.calls).toMatchSnapshot();
157
- expect(newResources.value.map((r) => r.resourceId)).toMatchSnapshot();
158
- });
159
-
160
- it('should update diff metadata with inputs and outputs', async () => {
161
- const app = new App('app');
162
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
163
-
164
- const action: IAction<ActionInputs, ActionOutputs> = {
165
- ACTION_NAME: 'test',
166
- collectInput: () => ['input.key1'],
167
- filter: () => true,
168
- handle: jest.fn() as jest.Mocked<any>,
169
- revert: jest.fn() as jest.Mocked<any>,
170
- };
171
- (action.handle as jest.Mocked<any>).mockResolvedValue({ resource1: new TestResource('resource1') });
172
-
173
- const service = new TransactionService();
174
- service.registerInputs({ 'input.key1': 'value1' });
175
- service.registerModelActions([action]);
176
- const generator = service.beginTransaction(diffs, { yieldModelTransaction: true });
177
-
178
- const result = await generator.next();
179
-
180
- expect((result.value as DiffMetadata[][])[0][0].inputs).toMatchSnapshot();
181
- expect((result.value as DiffMetadata[][])[0][0].outputs['resource1'].resourceId).toMatchSnapshot();
182
- });
183
-
184
- it('should call multiple actions and collect all input and output', async () => {
185
- const app = new App('app');
186
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
187
-
188
- const action1: IAction<ActionInputs, ActionOutputs> = {
189
- ACTION_NAME: 'test1',
190
- collectInput: () => ['input.key1'],
191
- filter: () => true,
192
- handle: jest.fn() as jest.Mocked<any>,
193
- revert: jest.fn() as jest.Mocked<any>,
194
- };
195
- (action1.handle as jest.Mocked<any>).mockResolvedValue({ resource1: new TestResource('resource1') });
196
- const action2: IAction<ActionInputs, ActionOutputs> = {
197
- ACTION_NAME: 'test2',
198
- collectInput: () => ['input.key2'],
199
- filter: () => true,
200
- handle: jest.fn() as jest.Mocked<any>,
201
- revert: jest.fn() as jest.Mocked<any>,
202
- };
203
- (action2.handle as jest.Mocked<any>).mockResolvedValue({ resource2: new TestResource('resource2') });
204
-
205
- const service = new TransactionService();
206
- service.registerInputs({ 'input.key1': 'value1', 'input.key2': 'value2' });
207
- service.registerModelActions([action1, action2]);
208
-
209
- const generator = service.beginTransaction(diffs, { yieldNewResources: true });
210
-
211
- const newResources = await generator.next();
212
-
213
- expect(action1.handle).toHaveBeenCalledTimes(1);
214
- expect((action1.handle as jest.Mock).mock.calls).toMatchSnapshot();
215
- expect(action2.handle).toHaveBeenCalledTimes(1);
216
- expect((action2.handle as jest.Mock).mock.calls).toMatchSnapshot();
217
- expect(newResources.value.map((r) => r.resourceId)).toMatchSnapshot();
218
- });
219
-
220
- it('should process diffs in different levels', async () => {
221
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
222
-
223
- const app = new App('app');
224
- const region = new Region('region');
225
- app.addRegion(region);
226
- const diffs = [
227
- new Diff(app, DiffAction.ADD, 'name', 'app'),
228
- new Diff(region, DiffAction.ADD, 'regionId', 'region'),
229
- ];
230
-
231
- const service = new TransactionService();
232
- service.registerModelActions([universalModelAction]);
233
- const generator = service.beginTransaction(diffs, { yieldModelTransaction: true });
234
-
235
- const result = await generator.next();
236
-
237
- expect(result.value).toMatchSnapshot();
238
- });
239
-
240
- it('should add the shared-resource to set of resources if it does not exist', async () => {
241
- const app = new App('app');
242
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
243
-
244
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({
245
- resource1: new SharedTestResource('shared-resource', { key1: 'value-1' }, []),
246
- });
247
-
248
- const service = new TransactionService();
249
- service.registerModelActions([universalModelAction]);
250
-
251
- const generator = service.beginTransaction(diffs, { yieldNewResources: true });
252
-
253
- const newResources = await generator.next();
254
-
255
- expect(newResources.value.map((r) => r.resourceId)).toMatchSnapshot();
256
- });
257
-
258
- it('should merge the shared-resource with existing set of resources', async () => {
259
- const app = new App('app');
260
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
261
-
262
- const sharedResource1 = new SharedTestResource('shared-resource', { key1: 'value-1' }, []);
263
- await Container.get(ResourceDataRepository, { args: [true, [sharedResource1], [sharedResource1]] });
264
-
265
- const sharedResource2 = new SharedTestResource('shared-resource', { key2: 'value-2' }, []);
266
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({ 'shared-resource': sharedResource2 });
267
- const mergeFunction = jest.spyOn(sharedResource2, 'merge');
268
-
269
- const service = new TransactionService();
270
- service.registerModelActions([universalModelAction]);
271
-
272
- const generator = service.beginTransaction(diffs, { yieldNewResources: true });
273
-
274
- const newResources = await generator.next();
275
-
276
- expect(newResources.value.map((r) => r.resourceId)).toMatchSnapshot();
277
- expect(mergeFunction).toHaveBeenCalledTimes(1);
278
-
279
- const mergeFunctionArg0 = mergeFunction.mock.calls[0][0] as UnknownResource;
280
- expect(mergeFunctionArg0.properties).toEqual({ key1: 'value-1' });
281
- });
282
- });
283
-
284
- describe('applyResources()', () => {
285
- const universalResourceAction: IResourceAction = {
286
- ACTION_NAME: 'universal',
287
- filter: () => true,
288
- handle: jest.fn() as jest.Mocked<any>,
289
- };
290
-
291
- it('should return empty transaction if diffs is empty', async () => {
292
- const service = new TransactionService();
293
- const generator = service.beginTransaction([], { yieldResourceTransaction: true });
294
-
295
- const result = await generator.next();
296
-
297
- expect(result.value).toMatchSnapshot();
298
- });
299
-
300
- it('should throw error when matching action not found', async () => {
301
- const resources = [new TestResource('resource-1')];
302
- await Container.get(ResourceDataRepository, { args: [true, [...resources], []] });
303
-
304
- const service = new TransactionService();
305
- const generator = service.beginTransaction([], { yieldResourceTransaction: true });
306
-
307
- await expect(async () => {
308
- await generator.next();
309
- }).rejects.toThrowErrorMatchingInlineSnapshot(`"No matching action given for diff!"`);
310
- });
311
-
312
- it('should skip processing diffs that are already applied', async () => {
313
- const resource1 = new TestResource('resource-1');
314
- const diff = new Diff(resource1, DiffAction.ADD, 'resourceId', 'resource-1');
315
- const diffMetadata = new DiffMetadata(diff, [universalResourceAction]);
316
- diffMetadata.applied = true;
317
- diffMetadata.applyOrder = 0;
318
-
319
- const service = new TransactionService();
320
- const result = await service['applyResources']([diffMetadata]);
321
-
322
- expect(result).toMatchSnapshot();
323
- });
324
-
325
- it('should only process 1 matching diff when duplicates found', async () => {
326
- const resources = [new TestResource('resource-1')];
327
- await Container.get(ResourceDataRepository, { args: [true, [...resources], [...resources]] });
328
-
329
- const service = new TransactionService();
330
- service.registerResourceActions([universalResourceAction]);
331
- const generator = service.beginTransaction([], { yieldResourceDiffs: true, yieldResourceTransaction: true });
332
-
333
- const resultResourceDiffs = await generator.next();
334
-
335
- // Append same resource to diff.
336
- const duplicateDiff = new Diff(new TestResource('resource-1'), DiffAction.ADD, 'resourceId', 'resource-1');
337
- const duplicateDiffMetadata = new DiffMetadata(duplicateDiff, [universalResourceAction]);
338
- duplicateDiffMetadata.applyOrder = 0;
339
- (resultResourceDiffs.value as DiffMetadata[][])[0].push(duplicateDiffMetadata);
340
-
341
- const result = await generator.next();
342
-
343
- expect(result.value).toMatchSnapshot();
344
- });
345
-
346
- it('should process diffs of adding resources per dependency graph', async () => {
347
- const resource1 = new TestResource('resource-1');
348
- const resource2 = new TestResource('resource-2');
349
- resource1.addChild('resourceId', resource2, 'resourceId');
350
-
351
- const resources = [resource1, resource2];
352
- await Container.get(ResourceDataRepository, { args: [true, [...resources], []] });
353
-
354
- const service = new TransactionService();
355
- service.registerResourceActions([universalResourceAction]);
356
- const generator = service.beginTransaction([], { yieldResourceTransaction: true });
357
-
358
- const result = await generator.next();
359
-
360
- expect(result.value).toMatchSnapshot();
361
- });
362
-
363
- it('should process diffs of deleting resources per dependency graph', async () => {
364
- const resource1_1 = new TestResource('resource-1');
365
- const resource2_1 = new TestResource('resource-2');
366
- resource1_1.addChild('resourceId', resource2_1, 'resourceId');
367
- const resource1_2 = new TestResource('resource-1');
368
- const resource2_2 = new TestResource('resource-2');
369
- resource1_2.addChild('resourceId', resource2_2, 'resourceId');
370
-
371
- await Container.get(ResourceDataRepository, {
372
- args: [true, [resource1_2, resource2_2], [resource1_1, resource2_1]],
373
- });
374
-
375
- // Upon calling beginTransaction(), assume model's apply method marks the new resource as deleted.
376
- resource2_2.markDeleted();
377
- resource1_2.markDeleted();
378
-
379
- const service = new TransactionService();
380
- service.registerResourceActions([universalResourceAction]);
381
- const generator = service.beginTransaction([], { yieldResourceTransaction: true });
382
-
383
- const result = await generator.next();
384
-
385
- expect(result.value).toMatchSnapshot();
386
- });
387
- });
388
-
389
- describe('setApplyOrder()', () => {
390
- const actions: IAction<ActionInputs, ActionOutputs>[] = [
391
- {
392
- ACTION_NAME: 'test',
393
- collectInput: () => [],
394
- filter: () => true,
395
- handle: jest.fn() as jest.Mocked<any>,
396
- revert: jest.fn() as jest.Mocked<any>,
397
- },
398
- ];
399
- let setApplyOrder: TransactionService['setApplyOrder'];
400
-
401
- beforeEach(() => {
402
- const service = new TransactionService();
403
- setApplyOrder = service['setApplyOrder'];
404
- setApplyOrder = setApplyOrder.bind(service);
405
- });
406
-
407
- it('should not set order for diff that already has an order defined', () => {
408
- const app = new App('test');
409
- const region = new Region('region-1');
410
- const environment = new Environment('qa');
411
- app.addRegion(region);
412
- region.addEnvironment(environment);
413
- const diff = new Diff(environment, DiffAction.ADD, 'environmentName', 'qa');
414
- const diffMetadata = new DiffMetadata(diff, actions);
415
- diffMetadata.applyOrder = 1;
416
-
417
- expect(diffMetadata.applyOrder).toBe(1);
418
- setApplyOrder(diffMetadata, [diffMetadata]);
419
- expect(diffMetadata.applyOrder).toBe(1);
420
- });
421
-
422
- it('should set order 0 for diff with no dependencies', () => {
423
- const app = new App('test');
424
- const region = new Region('region-1');
425
- const environment = new Environment('qa');
426
- app.addRegion(region);
427
- region.addEnvironment(environment);
428
- const diff = new Diff(environment, DiffAction.ADD, 'environmentName', 'qa');
429
- const diffMetadata = new DiffMetadata(diff, actions);
430
-
431
- expect(diffMetadata.applyOrder).toBe(-1);
432
- setApplyOrder(diffMetadata, [diffMetadata]);
433
- expect(diffMetadata.applyOrder).toBe(0);
434
- });
435
-
436
- it('should set order 0 for diff with dependencies not in current array of diffs', () => {
437
- const app = new App('test');
438
- const region = new Region('region-1');
439
- const environment = new Environment('qa');
440
- app.addRegion(region);
441
- region.addEnvironment(environment);
442
- const diff = new Diff(environment, DiffAction.ADD, 'environmentName', 'qa');
443
- const diffMetadata = new DiffMetadata(diff, actions);
444
-
445
- expect(diffMetadata.applyOrder).toBe(-1);
446
- setApplyOrder(diffMetadata, [diffMetadata]);
447
- expect(diffMetadata.applyOrder).toBe(0);
448
- });
449
-
450
- it('should only set order for the diff and its dependencies', () => {
451
- const app = new App('test');
452
- const region1 = new Region('region-1');
453
- const region2 = new Region('region-2');
454
- const environment = new Environment('qa');
455
- app.addRegion(region1);
456
- app.addRegion(region2);
457
- region1.addEnvironment(environment);
458
-
459
- const diff1 = new Diff(environment, DiffAction.ADD, 'environmentName', 'qa');
460
- const diff2 = new Diff(region1, DiffAction.ADD, 'regionId', 'region-1');
461
- const diff3 = new Diff(region2, DiffAction.ADD, 'regionId', 'region-2');
462
-
463
- const diffsMetadata = [diff1, diff2, diff3].map((d) => new DiffMetadata(d, actions));
464
-
465
- setApplyOrder(diffsMetadata[0], diffsMetadata);
466
-
467
- expect(diffsMetadata[0].applyOrder).toBe(1);
468
- expect(diffsMetadata[1].applyOrder).toBe(0);
469
- expect(diffsMetadata[2].applyOrder).toBe(-1);
470
- });
471
-
472
- it('should set order 1 for diff with 1 level of dependencies', () => {
473
- const app = new App('test');
474
- const region = new Region('region-1');
475
- const environment = new Environment('qa');
476
- app.addRegion(region);
477
- region.addEnvironment(environment);
478
- const diff1 = new Diff(environment, DiffAction.ADD, 'environmentName', 'qa');
479
- const diff2 = new Diff(region, DiffAction.ADD, 'regionId', 'region-1');
480
-
481
- const diffsMetadata = [diff1, diff2].map((d) => new DiffMetadata(d, actions));
482
-
483
- expect(diffsMetadata[0].applyOrder).toBe(-1);
484
- expect(diffsMetadata[1].applyOrder).toBe(-1);
485
- setApplyOrder(diffsMetadata[0], diffsMetadata);
486
- expect(diffsMetadata[0].applyOrder).toBe(1);
487
- expect(diffsMetadata[1].applyOrder).toBe(0);
488
- });
489
-
490
- it('should set order 2 for diff with 2 level of dependencies', () => {
491
- const app = new App('test');
492
- const region = new Region('region-1');
493
- const environment = new Environment('qa');
494
- app.addRegion(region);
495
- region.addEnvironment(environment);
496
- const diff1 = new Diff(environment, DiffAction.ADD, 'environmentName', 'qa');
497
- const diff2 = new Diff(region, DiffAction.ADD, 'regionId', 'region-1');
498
- const diff3 = new Diff(app, DiffAction.ADD, 'name', 'test');
499
-
500
- const diffsMetadata = [diff1, diff2, diff3].map((d) => new DiffMetadata(d, actions));
501
-
502
- expect(diffsMetadata[0].applyOrder).toBe(-1);
503
- expect(diffsMetadata[1].applyOrder).toBe(-1);
504
- expect(diffsMetadata[2].applyOrder).toBe(-1);
505
- setApplyOrder(diffsMetadata[0], diffsMetadata);
506
- expect(diffsMetadata[0].applyOrder).toBe(2);
507
- expect(diffsMetadata[1].applyOrder).toBe(1);
508
- expect(diffsMetadata[2].applyOrder).toBe(0);
509
- });
510
-
511
- it('should throw errors with 1 level of circular dependencies', () => {
512
- const app = new App('test');
513
- const region = new Region('region-1');
514
- const diff1 = new Diff(region, DiffAction.ADD, 'regionId', 'region-1');
515
- const diff2 = new Diff(app, DiffAction.ADD, 'name', 'test');
516
-
517
- const diffsMetadata = [diff1, diff2].map((d) => new DiffMetadata(d, actions));
518
-
519
- expect(diffsMetadata[0].applyOrder).toBe(-1);
520
- expect(diffsMetadata[1].applyOrder).toBe(-1);
521
- expect(() => {
522
- app.addRegion(region);
523
- region.addChild('regionId', app, 'name');
524
- setApplyOrder(diffsMetadata[0], diffsMetadata);
525
- }).toThrowError('Found circular dependencies!');
526
- expect(diffsMetadata[0].applyOrder).toBe(-1);
527
- expect(diffsMetadata[1].applyOrder).toBe(-1);
528
- });
529
-
530
- it('should throw errors with 2 level of circular dependencies', () => {
531
- const app = new App('test');
532
- const region = new Region('region-1');
533
- const environment = new Environment('qa');
534
- app.addRegion(region);
535
- region.addEnvironment(environment);
536
- environment.addChild('environmentName', app, 'name');
537
- const diff1 = new Diff(environment, DiffAction.ADD, 'environmentName', 'qa');
538
- const diff2 = new Diff(region, DiffAction.ADD, 'regionId', 'region-1');
539
- const diff3 = new Diff(app, DiffAction.ADD, 'name', 'test');
540
-
541
- const diffsMetadata = [diff1, diff2, diff3].map((d) => new DiffMetadata(d, actions));
542
-
543
- expect(diffsMetadata[0].applyOrder).toBe(-1);
544
- expect(diffsMetadata[1].applyOrder).toBe(-1);
545
- expect(diffsMetadata[2].applyOrder).toBe(-1);
546
- expect(() => {
547
- setApplyOrder(diffsMetadata[0], diffsMetadata);
548
- }).toThrowError('Found circular dependencies!');
549
- expect(diffsMetadata[0].applyOrder).toBe(-1);
550
- expect(diffsMetadata[1].applyOrder).toBe(-1);
551
- expect(diffsMetadata[2].applyOrder).toBe(-1);
552
- });
553
-
554
- it('should throw errors add and update of model are in same transaction', () => {
555
- const app = new App('app');
556
- const region = new Region('region');
557
- app.addRegion(region);
558
- const environment = new Environment('env');
559
- region.addEnvironment(environment);
560
-
561
- const diff1 = new Diff(region, DiffAction.ADD, 'regionId', 'region');
562
- const diff2 = new Diff(environment, DiffAction.ADD, 'environmentName', 'env');
563
- const diff3 = new Diff(environment, DiffAction.UPDATE, 'environmentVariables', '{}');
564
- const diffsMetadata = [diff1, diff2, diff3].map((d) => new DiffMetadata(d, actions));
565
-
566
- expect(() => {
567
- setApplyOrder(diffsMetadata[0], diffsMetadata);
568
- setApplyOrder(diffsMetadata[1], diffsMetadata);
569
- setApplyOrder(diffsMetadata[2], diffsMetadata);
570
- }).toThrowError('Found conflicting actions in same transaction!');
571
- });
572
- });
573
-
574
- describe('beginTransaction()', () => {
575
- describe('yieldModelTransaction', () => {
576
- const universalModelAction: IAction<ActionInputs, ActionOutputs> = {
577
- ACTION_NAME: 'universal',
578
- collectInput: () => [],
579
- filter: () => true,
580
- handle: jest.fn() as jest.Mocked<any>,
581
- revert: jest.fn() as jest.Mocked<any>,
582
- };
583
-
584
- it('should yield model transaction', async () => {
585
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
586
-
587
- const app = new App('app');
588
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
589
-
590
- const service = new TransactionService();
591
- service.registerModelActions([universalModelAction]);
592
- const generator = service.beginTransaction(diffs, { yieldModelTransaction: true });
593
-
594
- const result = await generator.next();
595
-
596
- expect(result.value).toMatchSnapshot();
597
- });
598
-
599
- it('should yield model transaction with overlays', async () => {
600
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
601
-
602
- const app = new App('app');
603
- const anchor = new TestAnchor('test-anchor', app);
604
- const overlay = new TestOverlay('test-overlay', {}, [anchor]);
605
-
606
- const overlayDataRepository = await Container.get(OverlayDataRepository);
607
- overlayDataRepository.add(overlay);
608
-
609
- const service = new TransactionService();
610
- service.registerOverlayActions([universalModelAction]);
611
- const generator = service.beginTransaction([], { yieldModelTransaction: true });
612
-
613
- const result = await generator.next();
614
-
615
- expect(result.value).toMatchSnapshot();
616
- });
617
- });
618
-
619
- describe('yieldNewResources', () => {
620
- const universalResourceAction: IResourceAction = {
621
- ACTION_NAME: 'universal',
622
- filter: () => true,
623
- handle: jest.fn() as jest.Mocked<any>,
624
- };
625
-
626
- it('should yield new resources', async () => {
627
- const oldResource = new TestResource('resource-1');
628
- const newResource = new TestResource('resource-2');
629
-
630
- const resourceDataRepository = await Container.get(ResourceDataRepository, { args: [true, [], [oldResource]] });
631
- resourceDataRepository.add(newResource);
632
-
633
- const service = new TransactionService();
634
- service.registerResourceActions([universalResourceAction]);
635
- const generator = service.beginTransaction([], { yieldNewResources: true });
636
-
637
- const result = await generator.next();
638
-
639
- expect(result.value.map((r) => r.resourceId)).toEqual(['resource-2']);
640
- });
641
- });
642
-
643
- describe('yieldResourceDiffs', () => {
644
- const universalModelAction: IAction<ActionInputs, ActionOutputs> = {
645
- ACTION_NAME: 'universal',
646
- collectInput: () => [],
647
- filter: () => true,
648
- handle: jest.fn() as jest.Mocked<any>,
649
- revert: jest.fn() as jest.Mocked<any>,
650
- };
651
- const universalResourceAction: IResourceAction = {
652
- ACTION_NAME: 'universal',
653
- filter: () => true,
654
- handle: jest.fn() as jest.Mocked<any>,
655
- };
656
-
657
- it('should yield resource diffs', async () => {
658
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
659
-
660
- const app = new App('app');
661
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
662
-
663
- const oldResource = new TestResource('resource-1');
664
- const newResource = new TestResource('resource-2');
665
-
666
- const resourceDataRepository = await Container.get(ResourceDataRepository, { args: [true, [], [oldResource]] });
667
- resourceDataRepository.add(newResource);
668
-
669
- const service = new TransactionService();
670
- service.registerModelActions([universalModelAction]);
671
- service.registerResourceActions([universalResourceAction]);
672
- const generator = service.beginTransaction(diffs, { yieldResourceDiffs: true });
673
-
674
- const result = await generator.next();
675
-
676
- expect(result.value).toMatchSnapshot();
677
- });
678
- });
679
-
680
- describe('yieldResourceTransaction', () => {
681
- const universalModelAction: IAction<ActionInputs, ActionOutputs> = {
682
- ACTION_NAME: 'universal',
683
- collectInput: () => [],
684
- filter: () => true,
685
- handle: jest.fn() as jest.Mocked<any>,
686
- revert: jest.fn() as jest.Mocked<any>,
687
- };
688
- const universalResourceAction: IResourceAction = {
689
- ACTION_NAME: 'universal',
690
- filter: () => true,
691
- handle: jest.fn() as jest.Mocked<any>,
692
- };
693
-
694
- it('should yield resource diffs', async () => {
695
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
696
- (universalResourceAction.handle as jest.Mocked<any>).mockResolvedValue({});
697
-
698
- const app = new App('app');
699
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
700
-
701
- const oldResource = new TestResource('resource-1');
702
- const newResource = new TestResource('resource-2');
703
-
704
- const resourceDataRepository = await Container.get(ResourceDataRepository, { args: [true, [], [oldResource]] });
705
- resourceDataRepository.add(newResource);
706
-
707
- const service = new TransactionService();
708
- service.registerModelActions([universalModelAction]);
709
- service.registerResourceActions([universalResourceAction]);
710
- const generator = service.beginTransaction(diffs, { yieldResourceTransaction: true });
711
-
712
- const result = await generator.next();
713
-
714
- expect(result.value).toMatchSnapshot();
715
- });
716
-
717
- it('should return model diffs', async () => {
718
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
719
- (universalResourceAction.handle as jest.Mocked<any>).mockResolvedValue({});
720
-
721
- const app = new App('app');
722
- const diffs = [new Diff(app, DiffAction.ADD, 'name', 'app')];
723
-
724
- const oldResource = new TestResource('resource-1');
725
- const newResource = new TestResource('resource-2');
726
-
727
- const resourceDataRepository = await Container.get(ResourceDataRepository, { args: [true, [], [oldResource]] });
728
- resourceDataRepository.add(newResource);
729
-
730
- const service = new TransactionService();
731
- service.registerModelActions([universalModelAction]);
732
- service.registerResourceActions([universalResourceAction]);
733
- const generator = service.beginTransaction(diffs);
734
-
735
- const result = await generator.next();
736
-
737
- expect(result.value).toMatchSnapshot();
738
- });
739
- });
740
- });
741
-
742
- describe('rollbackTransaction()', () => {
743
- const universalModelAction: IAction<ActionInputs, ActionOutputs> = {
744
- ACTION_NAME: 'universal',
745
- collectInput: () => [],
746
- filter: () => true,
747
- handle: jest.fn() as jest.Mocked<any>,
748
- revert: jest.fn() as jest.Mocked<any>,
749
- };
750
-
751
- it('should call revert() for every diff in transaction', async () => {
752
- (universalModelAction.handle as jest.Mocked<any>).mockResolvedValue({});
753
- (universalModelAction.revert as jest.Mocked<any>).mockResolvedValue({});
754
-
755
- const diffs = [new Diff(new App('app'), DiffAction.ADD, 'name', 'app')];
756
-
757
- const service = new TransactionService();
758
- service.registerModelActions([universalModelAction]);
759
-
760
- const transactionGenerator = service.beginTransaction(diffs, { yieldModelTransaction: true });
761
- const transactionResult = await transactionGenerator.next();
762
- const modelTransaction = transactionResult.value as DiffMetadata[][];
763
-
764
- const generator = service.rollbackTransaction(modelTransaction);
765
- await generator.next();
766
-
767
- expect(universalModelAction.revert).toHaveBeenCalledTimes(1);
768
- expect((universalModelAction.revert as jest.Mock).mock.calls).toMatchSnapshot();
769
- });
770
-
771
- it('should be able to revert addition of resources', async () => {
772
- // Assume "newResource" has been recently added. Upon revert, the new state should have this resource deleted.
773
- const newResource = new TestResource('resource-1');
774
-
775
- const resourceDataRepository = await Container.get(ResourceDataRepository, {
776
- args: [true, [newResource], [newResource]],
777
- });
778
-
779
- // Upon calling rollbackTransaction(), assume model's revert method marks the new resource as deleted.
780
- resourceDataRepository.getById('resource-1')!.markDeleted();
781
-
782
- const service = new TransactionService();
783
- service.registerResourceActions([
784
- {
785
- ACTION_NAME: 'test',
786
- filter: (): boolean => true,
787
- handle: jest.fn() as jest.Mocked<any>,
788
- },
789
- ]);
790
-
791
- const generator = service.rollbackTransaction([], { yieldResourceTransaction: true });
792
- const result = await generator.next();
793
-
794
- // Notice the results are in reverse order, i.e. on revert new resources are reverted.
795
- expect(result.value).toMatchSnapshot();
796
- });
797
-
798
- it('should be able to revert deletion of resources', async () => {
799
- // Assume "oldResource" has been recently deleted. Upon revert, the new state should have this resource added.
800
- const oldResource = new TestResource('resource-1');
801
-
802
- const resourceDataRepository = await Container.get(ResourceDataRepository, { args: [true, [], []] });
803
-
804
- // Upon calling rollbackTransaction(), assume model's revert method adds the new resource again.
805
- resourceDataRepository.add(oldResource);
806
-
807
- const service = new TransactionService();
808
- service.registerResourceActions([
809
- {
810
- ACTION_NAME: 'test',
811
- filter: (): boolean => true,
812
- handle: jest.fn() as jest.Mocked<any>,
813
- },
814
- ]);
815
-
816
- const generator = service.rollbackTransaction([], { yieldResourceDiffs: true });
817
- const result = await generator.next();
818
-
819
- // Notice the results are in reverse order, i.e. on revert new resources are reverted.
820
- expect(result.value).toMatchSnapshot();
821
- });
822
- });
823
- });