@highstate/backend 0.19.1 → 0.20.0
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/{chunk-V2NILDHS.js → chunk-52MY2TCE.js} +347 -19
- package/dist/chunk-52MY2TCE.js.map +1 -0
- package/dist/{chunk-I7BWSAN6.js → chunk-UAWBPTDW.js} +3 -3
- package/dist/{chunk-I7BWSAN6.js.map → chunk-UAWBPTDW.js.map} +1 -1
- package/dist/highstate.manifest.json +4 -4
- package/dist/index.js +4159 -785
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +5 -2
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +2 -2
- package/package.json +7 -7
- package/prisma/backend/_schema/object.prisma +12 -0
- package/prisma/backend/sqlite/migrations/20260222113554_add_object_tracking/migration.sql +7 -0
- package/prisma/project/artifact.prisma +3 -0
- package/prisma/project/entity.prisma +125 -0
- package/prisma/project/instance.prisma +6 -0
- package/prisma/project/migrations/20260301210131_add_entity_tracking/migration.sql +70 -0
- package/prisma/project/migrations/20260302212734_add_resource_hooks_flag/migration.sql +1 -0
- package/prisma/project/operation.prisma +3 -0
- package/src/business/artifact.test.ts +22 -2
- package/src/business/artifact.ts +7 -1
- package/src/business/entity-snapshot.test.ts +684 -0
- package/src/business/entity-snapshot.ts +904 -0
- package/src/business/evaluation.test.ts +56 -0
- package/src/business/evaluation.ts +102 -22
- package/src/business/global-search.test.ts +344 -0
- package/src/business/global-search.ts +902 -0
- package/src/business/index.ts +4 -0
- package/src/business/instance-lock.ts +58 -74
- package/src/business/instance-state.test.ts +15 -1
- package/src/business/instance-state.ts +37 -14
- package/src/business/object-ref-index.test.ts +140 -0
- package/src/business/object-ref-index.ts +193 -0
- package/src/business/operation.test.ts +15 -1
- package/src/business/operation.ts +4 -0
- package/src/business/project-model.ts +154 -13
- package/src/business/project-unlock.ts +25 -2
- package/src/business/project.ts +9 -0
- package/src/business/secret.test.ts +35 -2
- package/src/business/secret.ts +32 -9
- package/src/business/settings.ts +761 -0
- package/src/business/unit-output.test.ts +477 -0
- package/src/business/unit-output.ts +461 -0
- package/src/business/worker.ts +55 -4
- package/src/database/_generated/backend/postgresql/browser.ts +6 -0
- package/src/database/_generated/backend/postgresql/client.ts +6 -0
- package/src/database/_generated/backend/postgresql/internal/class.ts +23 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/postgresql/models/Object.ts +1076 -0
- package/src/database/_generated/backend/postgresql/models.ts +1 -0
- package/src/database/_generated/backend/sqlite/browser.ts +6 -0
- package/src/database/_generated/backend/sqlite/client.ts +6 -0
- package/src/database/_generated/backend/sqlite/internal/class.ts +23 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/sqlite/models/Object.ts +1074 -0
- package/src/database/_generated/backend/sqlite/models.ts +1 -0
- package/src/database/_generated/project/browser.ts +23 -0
- package/src/database/_generated/project/client.ts +23 -0
- package/src/database/_generated/project/commonInputTypes.ts +87 -53
- package/src/database/_generated/project/enums.ts +8 -0
- package/src/database/_generated/project/internal/class.ts +53 -5
- package/src/database/_generated/project/internal/prismaNamespace.ts +367 -13
- package/src/database/_generated/project/internal/prismaNamespaceBrowser.ts +48 -1
- package/src/database/_generated/project/models/Artifact.ts +199 -11
- package/src/database/_generated/project/models/Entity.ts +1274 -0
- package/src/database/_generated/project/models/EntitySnapshot.ts +2389 -0
- package/src/database/_generated/project/models/EntitySnapshotContent.ts +1260 -0
- package/src/database/_generated/project/models/EntitySnapshotReference.ts +1449 -0
- package/src/database/_generated/project/models/InstanceState.ts +361 -1
- package/src/database/_generated/project/models/Operation.ts +148 -3
- package/src/database/_generated/project/models/OperationLog.ts +0 -4
- package/src/database/_generated/project/models.ts +4 -0
- package/src/database/migration.ts +3 -0
- package/src/library/worker/evaluator.ts +7 -1
- package/src/orchestrator/manager.ts +7 -0
- package/src/orchestrator/operation-context.captured-outputs.test.ts +118 -0
- package/src/orchestrator/operation-context.ts +154 -16
- package/src/orchestrator/operation-plan.destroy.test.md +33 -12
- package/src/orchestrator/operation-plan.destroy.test.ts +140 -2
- package/src/orchestrator/operation-plan.fixtures.ts +2 -0
- package/src/orchestrator/operation-plan.md +4 -1
- package/src/orchestrator/operation-plan.ts +286 -92
- package/src/orchestrator/operation-plan.update.test.md +286 -11
- package/src/orchestrator/operation-plan.update.test.ts +656 -5
- package/src/orchestrator/operation-workset.ts +72 -22
- package/src/orchestrator/operation.cancel.test.ts +4 -0
- package/src/orchestrator/operation.composite.test.ts +341 -0
- package/src/orchestrator/operation.destroy.test.ts +4 -0
- package/src/orchestrator/operation.output-validation.failure.test.ts +124 -0
- package/src/orchestrator/operation.preview.test.ts +4 -0
- package/src/orchestrator/operation.refresh.test.ts +4 -0
- package/src/orchestrator/operation.test-utils.ts +52 -13
- package/src/orchestrator/operation.ts +228 -68
- package/src/orchestrator/operation.update.failure.test.ts +4 -0
- package/src/orchestrator/operation.update.skip.test.ts +110 -0
- package/src/orchestrator/operation.update.test.ts +4 -0
- package/src/orchestrator/plan-test-builder.ts +1 -0
- package/src/orchestrator/unit-input-values.test.ts +450 -0
- package/src/orchestrator/unit-input-values.ts +281 -0
- package/src/pubsub/manager.ts +3 -0
- package/src/runner/abstractions.ts +23 -54
- package/src/runner/local.ts +109 -85
- package/src/services.ts +52 -1
- package/src/shared/models/prisma.ts +1 -0
- package/src/shared/models/project/entity.ts +121 -0
- package/src/shared/models/project/index.ts +1 -0
- package/src/shared/models/project/operation.ts +61 -3
- package/src/shared/models/project/state.ts +10 -0
- package/src/shared/models/project/worker.ts +7 -0
- package/src/shared/resolvers/effective-output-type.test.ts +494 -0
- package/src/shared/resolvers/effective-output-type.ts +162 -0
- package/src/shared/resolvers/index.ts +1 -0
- package/src/shared/resolvers/input.ts +59 -9
- package/src/shared/utils/index.ts +1 -0
- package/src/shared/utils/stable-json.ts +41 -0
- package/src/terminal/manager.ts +6 -0
- package/src/worker/manager.ts +97 -1
- package/dist/chunk-V2NILDHS.js.map +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { InstanceState } from "../shared"
|
|
2
|
+
import { getInstanceId } from "@highstate/contract"
|
|
1
3
|
import { describe } from "vitest"
|
|
2
4
|
import { createOperationPlan } from "./operation-plan"
|
|
3
5
|
import { operationPlanTest } from "./operation-plan.fixtures"
|
|
@@ -49,7 +51,7 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
49
51
|
)
|
|
50
52
|
|
|
51
53
|
operationPlanTest(
|
|
52
|
-
"1a. should
|
|
54
|
+
"1a. should skip only changed dependencies when ignoreChangedDependencies enabled",
|
|
53
55
|
async ({ testBuilder, expect }) => {
|
|
54
56
|
// arrange
|
|
55
57
|
const { context, operation } = await testBuilder()
|
|
@@ -59,6 +61,93 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
59
61
|
.depends("C", "B")
|
|
60
62
|
.depends("B", "A")
|
|
61
63
|
.states({ A: "upToDate", B: "changed", C: "upToDate" })
|
|
64
|
+
.options({ ignoreChangedDependencies: true })
|
|
65
|
+
.request("update", "C")
|
|
66
|
+
.build()
|
|
67
|
+
|
|
68
|
+
// act
|
|
69
|
+
const plan = createOperationPlan(
|
|
70
|
+
context,
|
|
71
|
+
operation.type,
|
|
72
|
+
operation.requestedInstanceIds,
|
|
73
|
+
operation.options,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// assert
|
|
77
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
78
|
+
[
|
|
79
|
+
{
|
|
80
|
+
"instances": [
|
|
81
|
+
{
|
|
82
|
+
"id": "component.v1:C",
|
|
83
|
+
"message": "explicitly requested",
|
|
84
|
+
"parentId": undefined,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
"type": "update",
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
`)
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
operationPlanTest(
|
|
95
|
+
"1b. should still include undeployed dependencies when ignoreChangedDependencies enabled",
|
|
96
|
+
async ({ testBuilder, expect }) => {
|
|
97
|
+
// arrange
|
|
98
|
+
const { context, operation } = await testBuilder()
|
|
99
|
+
.unit("A")
|
|
100
|
+
.unit("B")
|
|
101
|
+
.unit("C")
|
|
102
|
+
.depends("C", "B")
|
|
103
|
+
.depends("B", "A")
|
|
104
|
+
.states({ A: "changed", B: "undeployed", C: "upToDate" })
|
|
105
|
+
.options({ ignoreChangedDependencies: true })
|
|
106
|
+
.request("update", "C")
|
|
107
|
+
.build()
|
|
108
|
+
|
|
109
|
+
// act
|
|
110
|
+
const plan = createOperationPlan(
|
|
111
|
+
context,
|
|
112
|
+
operation.type,
|
|
113
|
+
operation.requestedInstanceIds,
|
|
114
|
+
operation.options,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// assert
|
|
118
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
119
|
+
[
|
|
120
|
+
{
|
|
121
|
+
"instances": [
|
|
122
|
+
{
|
|
123
|
+
"id": "component.v1:B",
|
|
124
|
+
"message": "undeployed and required by "component.v1:C"",
|
|
125
|
+
"parentId": undefined,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "component.v1:C",
|
|
129
|
+
"message": "explicitly requested",
|
|
130
|
+
"parentId": undefined,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
"type": "update",
|
|
134
|
+
},
|
|
135
|
+
]
|
|
136
|
+
`)
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
operationPlanTest(
|
|
141
|
+
"1c. should ignore all dependencies when ignoreDependencies enabled",
|
|
142
|
+
async ({ testBuilder, expect }) => {
|
|
143
|
+
// arrange
|
|
144
|
+
const { context, operation } = await testBuilder()
|
|
145
|
+
.unit("A")
|
|
146
|
+
.unit("B")
|
|
147
|
+
.unit("C")
|
|
148
|
+
.depends("C", "B")
|
|
149
|
+
.depends("B", "A")
|
|
150
|
+
.states({ A: "changed", B: "undeployed", C: "upToDate" })
|
|
62
151
|
.options({ ignoreDependencies: true })
|
|
63
152
|
.request("update", "C")
|
|
64
153
|
.build()
|
|
@@ -90,7 +179,167 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
90
179
|
)
|
|
91
180
|
|
|
92
181
|
operationPlanTest(
|
|
93
|
-
"
|
|
182
|
+
"1d. should tolerate missing parent instance in model when walking substantive ancestors",
|
|
183
|
+
async ({ createContext, createTestOperation, expect }) => {
|
|
184
|
+
// arrange
|
|
185
|
+
const missingParentId = getInstanceId("composite.v1", "MissingParent")
|
|
186
|
+
const parentId = getInstanceId("composite.v1", "Parent")
|
|
187
|
+
const childId = getInstanceId("component.v1", "Child")
|
|
188
|
+
const requestedId = getInstanceId("component.v1", "Requested")
|
|
189
|
+
|
|
190
|
+
const context = await createContext(
|
|
191
|
+
[
|
|
192
|
+
{
|
|
193
|
+
id: parentId,
|
|
194
|
+
name: "Parent",
|
|
195
|
+
type: "composite.v1",
|
|
196
|
+
kind: "composite",
|
|
197
|
+
parentId: missingParentId,
|
|
198
|
+
inputs: {},
|
|
199
|
+
args: {},
|
|
200
|
+
outputs: {},
|
|
201
|
+
resolvedInputs: {},
|
|
202
|
+
resolvedOutputs: {},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: childId,
|
|
206
|
+
name: "Child",
|
|
207
|
+
type: "component.v1",
|
|
208
|
+
kind: "unit",
|
|
209
|
+
parentId,
|
|
210
|
+
inputs: {},
|
|
211
|
+
args: {},
|
|
212
|
+
outputs: {},
|
|
213
|
+
resolvedInputs: {},
|
|
214
|
+
resolvedOutputs: {},
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: requestedId,
|
|
218
|
+
name: "Requested",
|
|
219
|
+
type: "component.v1",
|
|
220
|
+
kind: "unit",
|
|
221
|
+
parentId: undefined,
|
|
222
|
+
inputs: {
|
|
223
|
+
dependency: [{ instanceId: childId, output: "default" }],
|
|
224
|
+
},
|
|
225
|
+
args: {},
|
|
226
|
+
outputs: {},
|
|
227
|
+
resolvedInputs: {
|
|
228
|
+
dependency: [{ instanceId: childId, output: "default" }],
|
|
229
|
+
},
|
|
230
|
+
resolvedOutputs: {},
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
[
|
|
234
|
+
{
|
|
235
|
+
id: parentId,
|
|
236
|
+
instanceId: parentId,
|
|
237
|
+
status: "deployed",
|
|
238
|
+
source: "resident",
|
|
239
|
+
kind: "composite",
|
|
240
|
+
hasResourceHooks: false,
|
|
241
|
+
parentId: null,
|
|
242
|
+
parentInstanceId: missingParentId,
|
|
243
|
+
selfHash: null,
|
|
244
|
+
inputHash: null,
|
|
245
|
+
outputHash: null,
|
|
246
|
+
dependencyOutputHash: null,
|
|
247
|
+
statusFields: null,
|
|
248
|
+
exportedArtifactIds: null,
|
|
249
|
+
inputHashNonce: null,
|
|
250
|
+
currentResourceCount: null,
|
|
251
|
+
model: null,
|
|
252
|
+
resolvedInputs: null,
|
|
253
|
+
lastOperationState: undefined,
|
|
254
|
+
evaluationState: null,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: childId,
|
|
258
|
+
instanceId: childId,
|
|
259
|
+
status: "undeployed",
|
|
260
|
+
source: "resident",
|
|
261
|
+
kind: "unit",
|
|
262
|
+
hasResourceHooks: false,
|
|
263
|
+
parentId: null,
|
|
264
|
+
parentInstanceId: parentId,
|
|
265
|
+
selfHash: null,
|
|
266
|
+
inputHash: null,
|
|
267
|
+
outputHash: null,
|
|
268
|
+
dependencyOutputHash: null,
|
|
269
|
+
statusFields: null,
|
|
270
|
+
exportedArtifactIds: null,
|
|
271
|
+
inputHashNonce: null,
|
|
272
|
+
currentResourceCount: null,
|
|
273
|
+
model: null,
|
|
274
|
+
resolvedInputs: null,
|
|
275
|
+
lastOperationState: undefined,
|
|
276
|
+
evaluationState: {} as InstanceState["evaluationState"],
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: requestedId,
|
|
280
|
+
instanceId: requestedId,
|
|
281
|
+
status: "deployed",
|
|
282
|
+
source: "resident",
|
|
283
|
+
kind: "unit",
|
|
284
|
+
hasResourceHooks: false,
|
|
285
|
+
parentId: null,
|
|
286
|
+
parentInstanceId: null,
|
|
287
|
+
selfHash: null,
|
|
288
|
+
inputHash: null,
|
|
289
|
+
outputHash: null,
|
|
290
|
+
dependencyOutputHash: null,
|
|
291
|
+
statusFields: null,
|
|
292
|
+
exportedArtifactIds: null,
|
|
293
|
+
inputHashNonce: null,
|
|
294
|
+
currentResourceCount: null,
|
|
295
|
+
model: null,
|
|
296
|
+
resolvedInputs: null,
|
|
297
|
+
lastOperationState: undefined,
|
|
298
|
+
evaluationState: {} as InstanceState["evaluationState"],
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
const operation = createTestOperation("update", [requestedId])
|
|
304
|
+
|
|
305
|
+
// act
|
|
306
|
+
const plan = createOperationPlan(
|
|
307
|
+
context,
|
|
308
|
+
operation.type,
|
|
309
|
+
operation.requestedInstanceIds,
|
|
310
|
+
operation.options,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
// assert
|
|
314
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
315
|
+
[
|
|
316
|
+
{
|
|
317
|
+
"instances": [
|
|
318
|
+
{
|
|
319
|
+
"id": "component.v1:Child",
|
|
320
|
+
"message": "undeployed and required by "component.v1:Requested"",
|
|
321
|
+
"parentId": "composite.v1:Parent",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"id": "component.v1:Requested",
|
|
325
|
+
"message": "explicitly requested",
|
|
326
|
+
"parentId": undefined,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"id": "composite.v1:Parent",
|
|
330
|
+
"message": "parent of included child "component.v1:Child"",
|
|
331
|
+
"parentId": "composite.v1:MissingParent",
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
"type": "update",
|
|
335
|
+
},
|
|
336
|
+
]
|
|
337
|
+
`)
|
|
338
|
+
},
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
operationPlanTest(
|
|
342
|
+
"2. should include full ancestor chain for compositional inclusion",
|
|
94
343
|
async ({ testBuilder, expect }) => {
|
|
95
344
|
// arrange
|
|
96
345
|
const { context, operation } = await testBuilder()
|
|
@@ -140,6 +389,11 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
140
389
|
"message": "parent of included child "component.v1:A"",
|
|
141
390
|
"parentId": "composite.v1:GrandParent",
|
|
142
391
|
},
|
|
392
|
+
{
|
|
393
|
+
"id": "composite.v1:GrandParent",
|
|
394
|
+
"message": "parent of included child "composite.v1:Parent"",
|
|
395
|
+
"parentId": undefined,
|
|
396
|
+
},
|
|
143
397
|
],
|
|
144
398
|
"type": "update",
|
|
145
399
|
},
|
|
@@ -210,10 +464,37 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
210
464
|
.depends("C", "B")
|
|
211
465
|
.depends("B", "A")
|
|
212
466
|
.states({ A: "upToDate", B: "upToDate", C: "upToDate" })
|
|
213
|
-
.options({ forceUpdateDependencies: true,
|
|
467
|
+
.options({ forceUpdateDependencies: true, ignoreChangedDependencies: true })
|
|
214
468
|
.request("update", "C")
|
|
215
469
|
.build()
|
|
216
470
|
|
|
471
|
+
// act & assert
|
|
472
|
+
expect(() =>
|
|
473
|
+
createOperationPlan(
|
|
474
|
+
context,
|
|
475
|
+
operation.type,
|
|
476
|
+
operation.requestedInstanceIds,
|
|
477
|
+
operation.options,
|
|
478
|
+
),
|
|
479
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
480
|
+
"[Error: Operation options are invalid: forceUpdateDependencies and ignoreChangedDependencies cannot both be enabled.]",
|
|
481
|
+
)
|
|
482
|
+
},
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
operationPlanTest(
|
|
486
|
+
"3b. should reject conflicting force dependency and ignore options",
|
|
487
|
+
async ({ testBuilder, expect }) => {
|
|
488
|
+
// arrange
|
|
489
|
+
const { context, operation } = await testBuilder()
|
|
490
|
+
.unit("A")
|
|
491
|
+
.unit("B")
|
|
492
|
+
.depends("B", "A")
|
|
493
|
+
.states({ A: "upToDate", B: "upToDate" })
|
|
494
|
+
.options({ forceUpdateDependencies: true, ignoreDependencies: true })
|
|
495
|
+
.request("update", "B")
|
|
496
|
+
.build()
|
|
497
|
+
|
|
217
498
|
// act & assert
|
|
218
499
|
expect(() =>
|
|
219
500
|
createOperationPlan(
|
|
@@ -228,6 +509,33 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
228
509
|
},
|
|
229
510
|
)
|
|
230
511
|
|
|
512
|
+
operationPlanTest(
|
|
513
|
+
"3c. should reject conflicting ignore changed and ignore options",
|
|
514
|
+
async ({ testBuilder, expect }) => {
|
|
515
|
+
// arrange
|
|
516
|
+
const { context, operation } = await testBuilder()
|
|
517
|
+
.unit("A")
|
|
518
|
+
.unit("B")
|
|
519
|
+
.depends("B", "A")
|
|
520
|
+
.states({ A: "upToDate", B: "upToDate" })
|
|
521
|
+
.options({ ignoreChangedDependencies: true, ignoreDependencies: true })
|
|
522
|
+
.request("update", "B")
|
|
523
|
+
.build()
|
|
524
|
+
|
|
525
|
+
// act & assert
|
|
526
|
+
expect(() =>
|
|
527
|
+
createOperationPlan(
|
|
528
|
+
context,
|
|
529
|
+
operation.type,
|
|
530
|
+
operation.requestedInstanceIds,
|
|
531
|
+
operation.options,
|
|
532
|
+
),
|
|
533
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
534
|
+
"[Error: Operation options are invalid: ignoreChangedDependencies and ignoreDependencies cannot both be enabled.]",
|
|
535
|
+
)
|
|
536
|
+
},
|
|
537
|
+
)
|
|
538
|
+
|
|
231
539
|
operationPlanTest(
|
|
232
540
|
"4. should include outdated children of substantive composite",
|
|
233
541
|
async ({ testBuilder, expect }) => {
|
|
@@ -332,7 +640,7 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
332
640
|
)
|
|
333
641
|
|
|
334
642
|
operationPlanTest(
|
|
335
|
-
"5a. should
|
|
643
|
+
"5a. should cleanup ghost children when forceUpdateChildren is enabled",
|
|
336
644
|
async ({ testBuilder, expect }) => {
|
|
337
645
|
// arrange
|
|
338
646
|
const { context, operation } = await testBuilder()
|
|
@@ -378,6 +686,249 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
378
686
|
},
|
|
379
687
|
)
|
|
380
688
|
|
|
689
|
+
operationPlanTest(
|
|
690
|
+
"5b. should cleanup ghosts from nested child composites during parent composite update",
|
|
691
|
+
async ({ testBuilder, expect }) => {
|
|
692
|
+
// arrange
|
|
693
|
+
const { context, operation } = await testBuilder()
|
|
694
|
+
.composite("Parent")
|
|
695
|
+
.composite("ChildComposite")
|
|
696
|
+
.unit("GhostLeaf")
|
|
697
|
+
.children("Parent", "ChildComposite")
|
|
698
|
+
.children("ChildComposite", "GhostLeaf")
|
|
699
|
+
.states({
|
|
700
|
+
Parent: "upToDate",
|
|
701
|
+
ChildComposite: "upToDate",
|
|
702
|
+
GhostLeaf: "ghost",
|
|
703
|
+
})
|
|
704
|
+
.request("update", "Parent")
|
|
705
|
+
.build()
|
|
706
|
+
|
|
707
|
+
// act
|
|
708
|
+
const plan = createOperationPlan(
|
|
709
|
+
context,
|
|
710
|
+
operation.type,
|
|
711
|
+
operation.requestedInstanceIds,
|
|
712
|
+
operation.options,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
// assert
|
|
716
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
717
|
+
[
|
|
718
|
+
{
|
|
719
|
+
"instances": [
|
|
720
|
+
{
|
|
721
|
+
"id": "composite.v1:Parent",
|
|
722
|
+
"message": "explicitly requested",
|
|
723
|
+
"parentId": undefined,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
"id": "composite.v1:ChildComposite",
|
|
727
|
+
"message": "included in operation",
|
|
728
|
+
"parentId": "composite.v1:Parent",
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
"id": "component.v1:GhostLeaf",
|
|
732
|
+
"message": "ghost cleanup",
|
|
733
|
+
"parentId": "composite.v1:ChildComposite",
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
"type": "destroy",
|
|
737
|
+
},
|
|
738
|
+
]
|
|
739
|
+
`)
|
|
740
|
+
},
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
operationPlanTest(
|
|
744
|
+
"5c. should run only ghost destroy phase when onlyDestroyGhosts is enabled",
|
|
745
|
+
async ({ testBuilder, expect }) => {
|
|
746
|
+
// arrange
|
|
747
|
+
const { context, operation } = await testBuilder()
|
|
748
|
+
.composite("Parent")
|
|
749
|
+
.unit("Child1")
|
|
750
|
+
.unit("GhostChild")
|
|
751
|
+
.children("Parent", "Child1", "GhostChild")
|
|
752
|
+
.states({
|
|
753
|
+
Parent: "upToDate",
|
|
754
|
+
Child1: "changed",
|
|
755
|
+
GhostChild: "ghost",
|
|
756
|
+
})
|
|
757
|
+
.options({ onlyDestroyGhosts: true })
|
|
758
|
+
.request("update", "Parent")
|
|
759
|
+
.build()
|
|
760
|
+
|
|
761
|
+
// act
|
|
762
|
+
const plan = createOperationPlan(
|
|
763
|
+
context,
|
|
764
|
+
operation.type,
|
|
765
|
+
operation.requestedInstanceIds,
|
|
766
|
+
operation.options,
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
// assert
|
|
770
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
771
|
+
[
|
|
772
|
+
{
|
|
773
|
+
"instances": [
|
|
774
|
+
{
|
|
775
|
+
"id": "composite.v1:Parent",
|
|
776
|
+
"message": "explicitly requested",
|
|
777
|
+
"parentId": undefined,
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
"id": "component.v1:GhostChild",
|
|
781
|
+
"message": "ghost cleanup",
|
|
782
|
+
"parentId": "composite.v1:Parent",
|
|
783
|
+
},
|
|
784
|
+
],
|
|
785
|
+
"type": "destroy",
|
|
786
|
+
},
|
|
787
|
+
]
|
|
788
|
+
`)
|
|
789
|
+
},
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
operationPlanTest(
|
|
793
|
+
"5d. should place ghost destroy phase before update when firstDestroyGhosts is enabled",
|
|
794
|
+
async ({ testBuilder, expect }) => {
|
|
795
|
+
// arrange
|
|
796
|
+
const { context, operation } = await testBuilder()
|
|
797
|
+
.composite("Parent")
|
|
798
|
+
.unit("Child1")
|
|
799
|
+
.unit("GhostChild")
|
|
800
|
+
.children("Parent", "Child1", "GhostChild")
|
|
801
|
+
.states({
|
|
802
|
+
Parent: "upToDate",
|
|
803
|
+
Child1: "changed",
|
|
804
|
+
GhostChild: "ghost",
|
|
805
|
+
})
|
|
806
|
+
.options({ firstDestroyGhosts: true })
|
|
807
|
+
.request("update", "Parent")
|
|
808
|
+
.build()
|
|
809
|
+
|
|
810
|
+
// act
|
|
811
|
+
const plan = createOperationPlan(
|
|
812
|
+
context,
|
|
813
|
+
operation.type,
|
|
814
|
+
operation.requestedInstanceIds,
|
|
815
|
+
operation.options,
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
// assert
|
|
819
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
820
|
+
[
|
|
821
|
+
{
|
|
822
|
+
"instances": [
|
|
823
|
+
{
|
|
824
|
+
"id": "composite.v1:Parent",
|
|
825
|
+
"message": "explicitly requested",
|
|
826
|
+
"parentId": undefined,
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
"id": "component.v1:GhostChild",
|
|
830
|
+
"message": "ghost cleanup",
|
|
831
|
+
"parentId": "composite.v1:Parent",
|
|
832
|
+
},
|
|
833
|
+
],
|
|
834
|
+
"type": "destroy",
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
"instances": [
|
|
838
|
+
{
|
|
839
|
+
"id": "composite.v1:Parent",
|
|
840
|
+
"message": "explicitly requested",
|
|
841
|
+
"parentId": undefined,
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
"id": "component.v1:Child1",
|
|
845
|
+
"message": "changed and child of included parent",
|
|
846
|
+
"parentId": "composite.v1:Parent",
|
|
847
|
+
},
|
|
848
|
+
],
|
|
849
|
+
"type": "update",
|
|
850
|
+
},
|
|
851
|
+
]
|
|
852
|
+
`)
|
|
853
|
+
},
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
operationPlanTest(
|
|
857
|
+
"5e. should skip ghost destroy phase when ignoreGhosts is enabled",
|
|
858
|
+
async ({ testBuilder, expect }) => {
|
|
859
|
+
// arrange
|
|
860
|
+
const { context, operation } = await testBuilder()
|
|
861
|
+
.composite("Parent")
|
|
862
|
+
.unit("Child1")
|
|
863
|
+
.unit("GhostChild")
|
|
864
|
+
.children("Parent", "Child1", "GhostChild")
|
|
865
|
+
.states({
|
|
866
|
+
Parent: "upToDate",
|
|
867
|
+
Child1: "changed",
|
|
868
|
+
GhostChild: "ghost",
|
|
869
|
+
})
|
|
870
|
+
.options({ ignoreGhosts: true })
|
|
871
|
+
.request("update", "Parent")
|
|
872
|
+
.build()
|
|
873
|
+
|
|
874
|
+
// act
|
|
875
|
+
const plan = createOperationPlan(
|
|
876
|
+
context,
|
|
877
|
+
operation.type,
|
|
878
|
+
operation.requestedInstanceIds,
|
|
879
|
+
operation.options,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
// assert
|
|
883
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
884
|
+
[
|
|
885
|
+
{
|
|
886
|
+
"instances": [
|
|
887
|
+
{
|
|
888
|
+
"id": "composite.v1:Parent",
|
|
889
|
+
"message": "explicitly requested",
|
|
890
|
+
"parentId": undefined,
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
"id": "component.v1:Child1",
|
|
894
|
+
"message": "changed and child of included parent",
|
|
895
|
+
"parentId": "composite.v1:Parent",
|
|
896
|
+
},
|
|
897
|
+
],
|
|
898
|
+
"type": "update",
|
|
899
|
+
},
|
|
900
|
+
]
|
|
901
|
+
`)
|
|
902
|
+
},
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
operationPlanTest(
|
|
906
|
+
"5f. should reject mutually exclusive ghost options",
|
|
907
|
+
async ({ testBuilder, expect }) => {
|
|
908
|
+
// arrange
|
|
909
|
+
const { context, operation } = await testBuilder()
|
|
910
|
+
.composite("Parent")
|
|
911
|
+
.unit("GhostChild")
|
|
912
|
+
.children("Parent", "GhostChild")
|
|
913
|
+
.states({ Parent: "upToDate", GhostChild: "ghost" })
|
|
914
|
+
.options({ onlyDestroyGhosts: true, ignoreGhosts: true })
|
|
915
|
+
.request("update", "Parent")
|
|
916
|
+
.build()
|
|
917
|
+
|
|
918
|
+
// act & assert
|
|
919
|
+
expect(() =>
|
|
920
|
+
createOperationPlan(
|
|
921
|
+
context,
|
|
922
|
+
operation.type,
|
|
923
|
+
operation.requestedInstanceIds,
|
|
924
|
+
operation.options,
|
|
925
|
+
),
|
|
926
|
+
).toThrow(
|
|
927
|
+
"Operation options are invalid: only one of onlyDestroyGhosts, firstDestroyGhosts, ignoreGhosts can be enabled.",
|
|
928
|
+
)
|
|
929
|
+
},
|
|
930
|
+
)
|
|
931
|
+
|
|
381
932
|
operationPlanTest(
|
|
382
933
|
"6. should handle complex nested hierarchy correctly",
|
|
383
934
|
async ({ testBuilder, expect }) => {
|
|
@@ -750,7 +1301,7 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
750
1301
|
)
|
|
751
1302
|
|
|
752
1303
|
operationPlanTest(
|
|
753
|
-
"13. should
|
|
1304
|
+
"13. should include deep ancestor chain without ancestor siblings",
|
|
754
1305
|
async ({ testBuilder, expect }) => {
|
|
755
1306
|
// arrange
|
|
756
1307
|
const { context, operation } = await testBuilder()
|
|
@@ -797,6 +1348,16 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
797
1348
|
"message": "parent of included child "component.v1:Child"",
|
|
798
1349
|
"parentId": "composite.v1:GrandParent",
|
|
799
1350
|
},
|
|
1351
|
+
{
|
|
1352
|
+
"id": "composite.v1:GrandParent",
|
|
1353
|
+
"message": "parent of included child "composite.v1:Parent"",
|
|
1354
|
+
"parentId": "composite.v1:GreatGrandParent",
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
"id": "composite.v1:GreatGrandParent",
|
|
1358
|
+
"message": "parent of included child "composite.v1:GrandParent"",
|
|
1359
|
+
"parentId": undefined,
|
|
1360
|
+
},
|
|
800
1361
|
],
|
|
801
1362
|
"type": "update",
|
|
802
1363
|
},
|
|
@@ -1180,4 +1741,94 @@ describe("OperationPlan - Update Operations", () => {
|
|
|
1180
1741
|
`)
|
|
1181
1742
|
},
|
|
1182
1743
|
)
|
|
1744
|
+
|
|
1745
|
+
operationPlanTest(
|
|
1746
|
+
"20. should skip explicitly requested empty nested composites",
|
|
1747
|
+
async ({ testBuilder, expect }) => {
|
|
1748
|
+
// arrange
|
|
1749
|
+
const { context, operation } = await testBuilder()
|
|
1750
|
+
.composite("Root")
|
|
1751
|
+
.composite("Level1")
|
|
1752
|
+
.composite("Level2")
|
|
1753
|
+
.children("Root", "Level1")
|
|
1754
|
+
.children("Level1", "Level2")
|
|
1755
|
+
.states({
|
|
1756
|
+
Root: "upToDate",
|
|
1757
|
+
Level1: "upToDate",
|
|
1758
|
+
Level2: "upToDate",
|
|
1759
|
+
})
|
|
1760
|
+
.request("update", "Root")
|
|
1761
|
+
.build()
|
|
1762
|
+
|
|
1763
|
+
// act
|
|
1764
|
+
const plan = createOperationPlan(
|
|
1765
|
+
context,
|
|
1766
|
+
operation.type,
|
|
1767
|
+
operation.requestedInstanceIds,
|
|
1768
|
+
operation.options,
|
|
1769
|
+
)
|
|
1770
|
+
|
|
1771
|
+
// assert
|
|
1772
|
+
expect(plan).toMatchInlineSnapshot(`[]`)
|
|
1773
|
+
},
|
|
1774
|
+
)
|
|
1775
|
+
|
|
1776
|
+
operationPlanTest(
|
|
1777
|
+
"21. should keep non-empty branch while skipping empty nested composites",
|
|
1778
|
+
async ({ testBuilder, expect }) => {
|
|
1779
|
+
// arrange
|
|
1780
|
+
const { context, operation } = await testBuilder()
|
|
1781
|
+
.composite("Root")
|
|
1782
|
+
.composite("EmptyLevel1")
|
|
1783
|
+
.composite("EmptyLevel2")
|
|
1784
|
+
.composite("NonEmptyLevel1")
|
|
1785
|
+
.unit("Leaf")
|
|
1786
|
+
.children("Root", "EmptyLevel1", "NonEmptyLevel1")
|
|
1787
|
+
.children("EmptyLevel1", "EmptyLevel2")
|
|
1788
|
+
.children("NonEmptyLevel1", "Leaf")
|
|
1789
|
+
.states({
|
|
1790
|
+
Root: "upToDate",
|
|
1791
|
+
EmptyLevel1: "upToDate",
|
|
1792
|
+
EmptyLevel2: "upToDate",
|
|
1793
|
+
NonEmptyLevel1: "upToDate",
|
|
1794
|
+
Leaf: "changed",
|
|
1795
|
+
})
|
|
1796
|
+
.request("update", "Root")
|
|
1797
|
+
.build()
|
|
1798
|
+
|
|
1799
|
+
// act
|
|
1800
|
+
const plan = createOperationPlan(
|
|
1801
|
+
context,
|
|
1802
|
+
operation.type,
|
|
1803
|
+
operation.requestedInstanceIds,
|
|
1804
|
+
operation.options,
|
|
1805
|
+
)
|
|
1806
|
+
|
|
1807
|
+
// assert
|
|
1808
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
1809
|
+
[
|
|
1810
|
+
{
|
|
1811
|
+
"instances": [
|
|
1812
|
+
{
|
|
1813
|
+
"id": "composite.v1:Root",
|
|
1814
|
+
"message": "explicitly requested",
|
|
1815
|
+
"parentId": undefined,
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
"id": "component.v1:Leaf",
|
|
1819
|
+
"message": "changed and child of included parent",
|
|
1820
|
+
"parentId": "composite.v1:NonEmptyLevel1",
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
"id": "composite.v1:NonEmptyLevel1",
|
|
1824
|
+
"message": "parent of included child "component.v1:Leaf"",
|
|
1825
|
+
"parentId": "composite.v1:Root",
|
|
1826
|
+
},
|
|
1827
|
+
],
|
|
1828
|
+
"type": "update",
|
|
1829
|
+
},
|
|
1830
|
+
]
|
|
1831
|
+
`)
|
|
1832
|
+
},
|
|
1833
|
+
)
|
|
1183
1834
|
})
|