@camunda/e2e-test-suite 0.0.1242 → 0.0.1244

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 (83) hide show
  1. package/dist/fixtures/api.d.ts +15 -0
  2. package/dist/fixtures/api.js +27 -0
  3. package/dist/pages/SM-8.10/ModelerCreatePage.js +5 -4
  4. package/dist/tests/api/v2/authorization/authorization-api-tests.spec.d.ts +1 -0
  5. package/dist/tests/api/v2/authorization/authorization-api-tests.spec.js +247 -0
  6. package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.d.ts +18 -0
  7. package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.js +208 -0
  8. package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.d.ts +1 -0
  9. package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.js +264 -0
  10. package/dist/tests/api/v2/cluster/cluster-api-tests.spec.d.ts +1 -0
  11. package/dist/tests/api/v2/cluster/cluster-api-tests.spec.js +86 -0
  12. package/dist/tests/api/v2/decision/decision-api-tests.spec.d.ts +1 -0
  13. package/dist/tests/api/v2/decision/decision-api-tests.spec.js +233 -0
  14. package/dist/tests/api/v2/deployment/deployment-api-tests.spec.d.ts +1 -0
  15. package/dist/tests/api/v2/deployment/deployment-api-tests.spec.js +147 -0
  16. package/dist/tests/api/v2/document/document-api-tests.spec.d.ts +9 -0
  17. package/dist/tests/api/v2/document/document-api-tests.spec.js +111 -0
  18. package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.d.ts +1 -0
  19. package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.js +127 -0
  20. package/dist/tests/api/v2/flows/end-to-end-flows.spec.d.ts +9 -0
  21. package/dist/tests/api/v2/flows/end-to-end-flows.spec.js +204 -0
  22. package/dist/tests/api/v2/form/form-api-tests.spec.d.ts +1 -0
  23. package/dist/tests/api/v2/form/form-api-tests.spec.js +88 -0
  24. package/dist/tests/api/v2/identity/group-api-tests.spec.d.ts +1 -0
  25. package/dist/tests/api/v2/identity/group-api-tests.spec.js +172 -0
  26. package/dist/tests/api/v2/identity/membership-api-tests.spec.d.ts +10 -0
  27. package/dist/tests/api/v2/identity/membership-api-tests.spec.js +164 -0
  28. package/dist/tests/api/v2/identity/role-api-tests.spec.d.ts +1 -0
  29. package/dist/tests/api/v2/identity/role-api-tests.spec.js +212 -0
  30. package/dist/tests/api/v2/identity/tenant-api-tests.spec.d.ts +1 -0
  31. package/dist/tests/api/v2/identity/tenant-api-tests.spec.js +319 -0
  32. package/dist/tests/api/v2/identity/user-api-tests.spec.d.ts +1 -0
  33. package/dist/tests/api/v2/identity/user-api-tests.spec.js +148 -0
  34. package/dist/tests/api/v2/job/job-api-tests.spec.d.ts +1 -0
  35. package/dist/tests/api/v2/job/job-api-tests.spec.js +260 -0
  36. package/dist/tests/api/v2/message/message-api-tests.spec.d.ts +1 -0
  37. package/dist/tests/api/v2/message/message-api-tests.spec.js +219 -0
  38. package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.d.ts +1 -0
  39. package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.js +118 -0
  40. package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.d.ts +1 -0
  41. package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.js +335 -0
  42. package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.d.ts +1 -0
  43. package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.js +123 -0
  44. package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.d.ts +1 -0
  45. package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.js +174 -0
  46. package/dist/tests/api/v2/system/clock-api-tests.spec.d.ts +19 -0
  47. package/dist/tests/api/v2/system/clock-api-tests.spec.js +62 -0
  48. package/dist/tests/api/v2/system/system-api-tests.spec.d.ts +1 -0
  49. package/dist/tests/api/v2/system/system-api-tests.spec.js +183 -0
  50. package/dist/tests/api/v2/user-task/user-task-api-tests.spec.d.ts +1 -0
  51. package/dist/tests/api/v2/user-task/user-task-api-tests.spec.js +258 -0
  52. package/dist/utils/api/assertions.d.ts +50 -0
  53. package/dist/utils/api/assertions.js +142 -0
  54. package/dist/utils/api/auth.d.ts +27 -0
  55. package/dist/utils/api/auth.js +137 -0
  56. package/dist/utils/api/beans.d.ts +46 -0
  57. package/dist/utils/api/beans.js +87 -0
  58. package/dist/utils/api/bpmn.d.ts +50 -0
  59. package/dist/utils/api/bpmn.js +217 -0
  60. package/dist/utils/api/capabilities.d.ts +90 -0
  61. package/dist/utils/api/capabilities.js +198 -0
  62. package/dist/utils/api/cleanup.d.ts +24 -0
  63. package/dist/utils/api/cleanup.js +120 -0
  64. package/dist/utils/api/config.d.ts +103 -0
  65. package/dist/utils/api/config.js +62 -0
  66. package/dist/utils/api/deployments.d.ts +45 -0
  67. package/dist/utils/api/deployments.js +120 -0
  68. package/dist/utils/api/eventually.d.ts +36 -0
  69. package/dist/utils/api/eventually.js +54 -0
  70. package/dist/utils/api/http.d.ts +56 -0
  71. package/dist/utils/api/http.js +244 -0
  72. package/dist/utils/api/identity.d.ts +29 -0
  73. package/dist/utils/api/identity.js +43 -0
  74. package/dist/utils/api/keys.d.ts +37 -0
  75. package/dist/utils/api/keys.js +40 -0
  76. package/dist/utils/api/paths.d.ts +25 -0
  77. package/dist/utils/api/paths.js +167 -0
  78. package/dist/utils/api/tenancy.d.ts +31 -0
  79. package/dist/utils/api/tenancy.js +65 -0
  80. package/package.json +1 -1
  81. package/playwright.config.ts +14 -2
  82. package/dist/resources/test-api-v2-complete.sh +0 -12246
  83. package/resources/test-api-v2-complete.sh +0 -12246
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const api_1 = require("../../../../fixtures/api");
4
+ const test_1 = require("@playwright/test");
5
+ const assertions_1 = require("../../../../utils/api/assertions");
6
+ const deployments_1 = require("../../../../utils/api/deployments");
7
+ const bpmn_1 = require("../../../../utils/api/bpmn");
8
+ const eventually_1 = require("../../../../utils/api/eventually");
9
+ const capabilities_1 = require("../../../../utils/api/capabilities");
10
+ const keys_1 = require("../../../../utils/api/keys");
11
+ /**
12
+ * Deploys the one-service-task process this file's tests start instances of,
13
+ * and tracks it for deletion.
14
+ *
15
+ * Per test rather than once per file: a definition deployed in `beforeAll`
16
+ * outlives the test that needs it, so no test can clean it up and the run
17
+ * leaves it on the cluster and in its indices for good. The service task is
18
+ * what makes an instance wait rather than complete, which every assertion
19
+ * below about a running instance depends on.
20
+ */
21
+ async function deployProcessUnderTest(api, resources, prefix = 'process-instance-api') {
22
+ const processId = (0, bpmn_1.uniqueId)(prefix);
23
+ const deployed = await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('pi-job'))));
24
+ return { processId, deployed };
25
+ }
26
+ async function startInstance(api, processDefinitionId, variables = {}) {
27
+ const response = await api.post('/process-instances', {
28
+ body: { processDefinitionId, variables },
29
+ tenant: 'root',
30
+ });
31
+ await (0, assertions_1.assertStatusIn)(response, [200], 'instance creation answers synchronously unless awaitCompletion is set');
32
+ const json = (await response.json());
33
+ (0, assertions_1.assertRequiredFields)(json, [
34
+ 'processInstanceKey',
35
+ 'processDefinitionKey',
36
+ 'processDefinitionId',
37
+ ]);
38
+ return json;
39
+ }
40
+ api_1.test.describe('Process instances', () => {
41
+ (0, api_1.test)('starts an instance of the deployed process', async ({ api, resources, }) => {
42
+ const { processId, deployed } = await deployProcessUnderTest(api, resources);
43
+ const instance = await startInstance(api, processId);
44
+ resources.track('processInstance', instance.processInstanceKey);
45
+ (0, test_1.expect)(String(instance.processDefinitionKey)).toBe(deployed.processDefinitionKey);
46
+ (0, test_1.expect)(instance.processDefinitionId).toBe(processId);
47
+ });
48
+ (0, api_1.test)('starts an instance carrying variables', async ({ api, resources }) => {
49
+ const { processId } = await deployProcessUnderTest(api, resources);
50
+ const instance = await startInstance(api, processId, {
51
+ orderId: 'order-42',
52
+ amount: 99,
53
+ });
54
+ resources.track('processInstance', instance.processInstanceKey);
55
+ await (0, eventually_1.eventually)(async () => {
56
+ const response = await api.post('/variables/search', {
57
+ body: {
58
+ filter: { processInstanceKey: instance.processInstanceKey },
59
+ page: { limit: 50 },
60
+ },
61
+ });
62
+ const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
63
+ const names = page.items.map((item) => item.name);
64
+ (0, test_1.expect)(names).toEqual(test_1.expect.arrayContaining(['orderId', 'amount']));
65
+ });
66
+ });
67
+ (0, api_1.test)('finds a started instance through search', async ({ api, resources }) => {
68
+ const { processId } = await deployProcessUnderTest(api, resources);
69
+ const instance = await startInstance(api, processId);
70
+ resources.track('processInstance', instance.processInstanceKey);
71
+ await (0, eventually_1.eventually)(async () => {
72
+ const response = await api.post('/process-instances/search', {
73
+ body: { filter: { processInstanceKey: instance.processInstanceKey } },
74
+ });
75
+ const page = await (0, assertions_1.assertPaginated)(response, { exactItems: 1 });
76
+ const item = page.items[0];
77
+ (0, test_1.expect)(item.processDefinitionId).toBe(processId);
78
+ (0, test_1.expect)(item.state).toBe('ACTIVE');
79
+ });
80
+ });
81
+ (0, api_1.test)('returns a started instance by key', async ({ api, resources }) => {
82
+ const { processId } = await deployProcessUnderTest(api, resources);
83
+ const instance = await startInstance(api, processId);
84
+ const key = instance.processInstanceKey;
85
+ resources.track('processInstance', key);
86
+ await (0, eventually_1.eventually)(async () => {
87
+ const response = await api.get('/process-instances/{processInstanceKey}', { params: { processInstanceKey: key } });
88
+ const json = (await (0, assertions_1.assertOk)(response));
89
+ (0, assertions_1.assertRequiredFields)(json, [
90
+ 'processInstanceKey',
91
+ 'processDefinitionId',
92
+ 'state',
93
+ ]);
94
+ (0, test_1.expect)(json.state).toBe('ACTIVE');
95
+ });
96
+ });
97
+ (0, api_1.test)('reports sequence flows taken by the instance', async ({ api, resources, }) => {
98
+ const { processId } = await deployProcessUnderTest(api, resources);
99
+ const instance = await startInstance(api, processId);
100
+ const key = instance.processInstanceKey;
101
+ resources.track('processInstance', key);
102
+ await (0, eventually_1.eventually)(async () => {
103
+ const response = await api.get('/process-instances/{processInstanceKey}/sequence-flows', { params: { processInstanceKey: key } });
104
+ const json = (await (0, assertions_1.assertOk)(response));
105
+ (0, test_1.expect)(Array.isArray(json.items)).toBe(true);
106
+ // The instance waits at the service task, so the flow into it is taken.
107
+ (0, test_1.expect)(json.items.length).toBeGreaterThan(0);
108
+ });
109
+ });
110
+ (0, api_1.test)('reports element-instance statistics for the instance', async ({ api, resources, }) => {
111
+ const { processId } = await deployProcessUnderTest(api, resources);
112
+ const instance = await startInstance(api, processId);
113
+ const key = instance.processInstanceKey;
114
+ resources.track('processInstance', key);
115
+ await (0, eventually_1.eventually)(async () => {
116
+ const response = await api.get('/process-instances/{processInstanceKey}/statistics/element-instances', { params: { processInstanceKey: key } });
117
+ const json = (await (0, assertions_1.assertOk)(response));
118
+ (0, test_1.expect)(Array.isArray(json.items)).toBe(true);
119
+ });
120
+ });
121
+ (0, api_1.test)('reports an empty call hierarchy for a top-level instance', async ({ api, resources, }) => {
122
+ const { processId } = await deployProcessUnderTest(api, resources);
123
+ const instance = await startInstance(api, processId);
124
+ const key = instance.processInstanceKey;
125
+ resources.track('processInstance', key);
126
+ await (0, eventually_1.eventually)(async () => {
127
+ const response = await api.get('/process-instances/{processInstanceKey}/call-hierarchy', { params: { processInstanceKey: key } });
128
+ await (0, assertions_1.assertStatusIn)(response, [200], 'call hierarchy is available once the instance is exported');
129
+ const json = (await response.json());
130
+ // A process with no call activity is its own only hierarchy entry.
131
+ (0, test_1.expect)(Array.isArray(json)).toBe(true);
132
+ });
133
+ });
134
+ (0, api_1.test)('reports no incidents for a healthy instance', async ({ api, resources, }) => {
135
+ const { processId } = await deployProcessUnderTest(api, resources);
136
+ const instance = await startInstance(api, processId);
137
+ const key = instance.processInstanceKey;
138
+ resources.track('processInstance', key);
139
+ await (0, eventually_1.eventually)(async () => {
140
+ const response = await api.post('/process-instances/{processInstanceKey}/incidents/search', { params: { processInstanceKey: key }, body: { filter: {} } });
141
+ await (0, assertions_1.assertPaginated)(response, { exactItems: 0 });
142
+ });
143
+ });
144
+ (0, api_1.test)('cancels a running instance', async ({ api, resources }) => {
145
+ const { processId } = await deployProcessUnderTest(api, resources);
146
+ const instance = await startInstance(api, processId);
147
+ const key = instance.processInstanceKey;
148
+ const response = await api.post('/process-instances/{processInstanceKey}/cancellation', { params: { processInstanceKey: key }, body: {}, tenant: 'none' });
149
+ await (0, assertions_1.assertStatusIn)(response, [204], 'cancellation is acknowledged once the command is written');
150
+ await (0, eventually_1.eventually)(async () => {
151
+ const search = await api.post('/process-instances/search', {
152
+ body: { filter: { processInstanceKey: key } },
153
+ });
154
+ const page = await (0, assertions_1.assertPaginated)(search, { exactItems: 1 });
155
+ (0, test_1.expect)(page.items[0].state).toBe('TERMINATED');
156
+ });
157
+ });
158
+ (0, api_1.test)('updates instance variables through modification', async ({ api, resources, }) => {
159
+ const { processId } = await deployProcessUnderTest(api, resources);
160
+ const instance = await startInstance(api, processId);
161
+ const key = instance.processInstanceKey;
162
+ resources.track('processInstance', key);
163
+ const response = await api.post('/process-instances/{processInstanceKey}/modification', {
164
+ params: { processInstanceKey: key },
165
+ body: {
166
+ activateInstructions: [
167
+ {
168
+ elementId: 'task',
169
+ variableInstructions: [{ variables: { injected: 'value' } }],
170
+ },
171
+ ],
172
+ },
173
+ tenant: 'none',
174
+ });
175
+ await (0, assertions_1.assertStatusIn)(response, [204], 'modification is acknowledged once the command is written');
176
+ await (0, eventually_1.eventually)(async () => {
177
+ const variables = await api.post('/variables/search', {
178
+ body: {
179
+ filter: { processInstanceKey: key, name: 'injected' },
180
+ page: { limit: 10 },
181
+ },
182
+ });
183
+ await (0, assertions_1.assertPaginated)(variables, { minItems: 1 });
184
+ });
185
+ });
186
+ (0, api_1.test)('rejects starting an instance of an absent process', async ({ api }) => {
187
+ const response = await api.post('/process-instances', {
188
+ body: { processDefinitionId: (0, bpmn_1.uniqueId)('does-not-exist') },
189
+ tenant: 'root',
190
+ });
191
+ await (0, assertions_1.assertNotFound)(response);
192
+ });
193
+ (0, api_1.test)('rejects starting an instance with no process identifier', async ({ api, }) => {
194
+ const response = await api.post('/process-instances', {
195
+ body: { variables: {} },
196
+ tenant: 'root',
197
+ });
198
+ await (0, assertions_1.assertBadRequest)(response);
199
+ });
200
+ (0, api_1.test)('rejects a lookup for an absent instance key', async ({ api }) => {
201
+ const response = await api.get('/process-instances/{processInstanceKey}', {
202
+ params: { processInstanceKey: keys_1.ABSENT_KEY },
203
+ });
204
+ await (0, assertions_1.assertNotFound)(response);
205
+ });
206
+ (0, api_1.test)('rejects cancelling an absent instance', async ({ api }) => {
207
+ const response = await api.post('/process-instances/{processInstanceKey}/cancellation', {
208
+ params: { processInstanceKey: keys_1.ABSENT_ROUTABLE_KEY },
209
+ body: {},
210
+ tenant: 'none',
211
+ });
212
+ await (0, assertions_1.assertNotFound)(response);
213
+ });
214
+ (0, api_1.test)('rejects an unauthenticated instance start', async ({ api, resources, }) => {
215
+ api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
216
+ ? ''
217
+ : capabilities_1.unauthenticatedAccessAllowed);
218
+ const { processId } = await deployProcessUnderTest(api, resources);
219
+ const response = await api.post('/process-instances', {
220
+ body: { processDefinitionId: processId },
221
+ tenant: 'root',
222
+ authorization: null,
223
+ });
224
+ await (0, assertions_1.assertUnauthorized)(response);
225
+ });
226
+ });
227
+ api_1.test.describe('Process instance migration', () => {
228
+ (0, api_1.test)('migrates a running instance to a new definition version', async ({ api, resources, }) => {
229
+ const processId = (0, bpmn_1.uniqueId)('migration-api');
230
+ const jobType = (0, bpmn_1.uniqueId)('migration-job');
231
+ const source = await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask(jobType), {
232
+ name: 'version one',
233
+ }));
234
+ const target = await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask(jobType), {
235
+ name: 'version two',
236
+ }));
237
+ (0, test_1.expect)(target.version).toBe(source.version + 1);
238
+ const instance = await startInstance(api, processId);
239
+ const key = instance.processInstanceKey;
240
+ resources.track('processInstance', key);
241
+ const response = await api.post('/process-instances/{processInstanceKey}/migration', {
242
+ params: { processInstanceKey: key },
243
+ body: {
244
+ targetProcessDefinitionKey: target.processDefinitionKey,
245
+ mappingInstructions: [
246
+ { sourceElementId: 'task', targetElementId: 'task' },
247
+ ],
248
+ },
249
+ tenant: 'none',
250
+ });
251
+ await (0, assertions_1.assertStatusIn)(response, [204], 'migration is acknowledged once the command is written');
252
+ await (0, eventually_1.eventually)(async () => {
253
+ const search = await api.post('/process-instances/search', {
254
+ body: { filter: { processInstanceKey: key } },
255
+ });
256
+ const page = await (0, assertions_1.assertPaginated)(search, { exactItems: 1 });
257
+ // The shell suite accepted `200 204 400 500` for this call, so a
258
+ // migration that never happened counted as a pass. Reading the instance
259
+ // back is what proves it did.
260
+ (0, test_1.expect)(String(page.items[0].processDefinitionKey)).toBe(target.processDefinitionKey);
261
+ });
262
+ });
263
+ (0, api_1.test)('rejects migration to an absent target definition', async ({ api, resources, }) => {
264
+ const processId = (0, bpmn_1.uniqueId)('migration-bad-target');
265
+ await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job'))));
266
+ const instance = await startInstance(api, processId);
267
+ const key = instance.processInstanceKey;
268
+ resources.track('processInstance', key);
269
+ const response = await api.post('/process-instances/{processInstanceKey}/migration', {
270
+ params: { processInstanceKey: key },
271
+ body: {
272
+ targetProcessDefinitionKey: keys_1.ABSENT_KEY,
273
+ mappingInstructions: [
274
+ { sourceElementId: 'task', targetElementId: 'task' },
275
+ ],
276
+ },
277
+ tenant: 'none',
278
+ });
279
+ await (0, assertions_1.assertNotFound)(response);
280
+ });
281
+ });
282
+ api_1.test.describe('Call activity migration', () => {
283
+ /**
284
+ * Regression cover for the 8.9 NPE: migrating an instance whose active
285
+ * element is a call activity used to fail inside the engine rather than
286
+ * returning a result. The shell suite's version of this accepted
287
+ * `200 204 400 500`, which would have passed on the very 500 the test was
288
+ * written to catch.
289
+ */
290
+ (0, api_1.test)('migrates an instance waiting at a call activity', async ({ api, resources, }) => {
291
+ const childId = (0, bpmn_1.uniqueId)('migration-child');
292
+ const parentId = (0, bpmn_1.uniqueId)('migration-parent');
293
+ await (0, deployments_1.deployProcess)(api, resources, childId, (0, bpmn_1.linearProcess)(childId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('child-job'))));
294
+ const parentV1 = await (0, deployments_1.deployProcess)(api, resources, parentId, (0, bpmn_1.linearProcess)(parentId, bpmn_1.element.callActivity(childId), {
295
+ name: 'parent v1',
296
+ }));
297
+ const parentV2 = await (0, deployments_1.deployProcess)(api, resources, parentId, (0, bpmn_1.linearProcess)(parentId, bpmn_1.element.callActivity(childId), {
298
+ name: 'parent v2',
299
+ }));
300
+ (0, test_1.expect)(parentV2.version).toBe(parentV1.version + 1);
301
+ const instance = await startInstance(api, parentId);
302
+ const key = instance.processInstanceKey;
303
+ resources.track('processInstance', key);
304
+ // The call activity must be active before migrating, otherwise the test
305
+ // exercises an instance that has not reached the element under test.
306
+ await (0, eventually_1.eventually)(async () => {
307
+ const elements = await api.post('/element-instances/search', {
308
+ body: {
309
+ filter: { processInstanceKey: key, elementId: 'task' },
310
+ page: { limit: 10 },
311
+ },
312
+ });
313
+ await (0, assertions_1.assertPaginated)(elements, { minItems: 1 });
314
+ });
315
+ const response = await api.post('/process-instances/{processInstanceKey}/migration', {
316
+ params: { processInstanceKey: key },
317
+ body: {
318
+ targetProcessDefinitionKey: parentV2.processDefinitionKey,
319
+ mappingInstructions: [
320
+ { sourceElementId: 'task', targetElementId: 'task' },
321
+ ],
322
+ },
323
+ tenant: 'none',
324
+ });
325
+ (0, test_1.expect)(response.status(), 'migrating at a call activity must not fault the engine').not.toBe(500);
326
+ await (0, assertions_1.assertStatusIn)(response, [204], 'migration is acknowledged once the command is written');
327
+ await (0, eventually_1.eventually)(async () => {
328
+ const search = await api.post('/process-instances/search', {
329
+ body: { filter: { processInstanceKey: key } },
330
+ });
331
+ const page = await (0, assertions_1.assertPaginated)(search, { exactItems: 1 });
332
+ (0, test_1.expect)(String(page.items[0].processDefinitionKey)).toBe(parentV2.processDefinitionKey);
333
+ });
334
+ });
335
+ });
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const api_1 = require("../../../../fixtures/api");
4
+ const test_1 = require("@playwright/test");
5
+ const assertions_1 = require("../../../../utils/api/assertions");
6
+ const deployments_1 = require("../../../../utils/api/deployments");
7
+ const bpmn_1 = require("../../../../utils/api/bpmn");
8
+ const eventually_1 = require("../../../../utils/api/eventually");
9
+ async function seed(api, resources, count) {
10
+ const processId = (0, bpmn_1.uniqueId)('batch-mod');
11
+ const jobType = (0, bpmn_1.uniqueId)('batch-mod-job');
12
+ await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask(jobType)));
13
+ const instanceKeys = [];
14
+ for (let i = 0; i < count; i++) {
15
+ const response = await api.post('/process-instances', {
16
+ body: { processDefinitionId: processId },
17
+ tenant: 'root',
18
+ });
19
+ await (0, assertions_1.assertStatusIn)(response, [200], 'instance creation is synchronous');
20
+ const json = (await response.json());
21
+ instanceKeys.push(String(json.processInstanceKey));
22
+ }
23
+ await (0, eventually_1.eventually)(async () => {
24
+ const search = await api.post('/process-instances/search', {
25
+ body: { filter: { processDefinitionId: processId }, page: { limit: 50 } },
26
+ });
27
+ await (0, assertions_1.assertPaginated)(search, { minItems: count });
28
+ });
29
+ return { processId, jobType, instanceKeys };
30
+ }
31
+ api_1.test.describe('Batch process-instance modification', () => {
32
+ (0, api_1.test)('creates a modification batch scoped to a definition', async ({ api, resources, }) => {
33
+ const { processId, instanceKeys } = await seed(api, resources, 2);
34
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
35
+ const response = await api.post('/process-instances/modification', {
36
+ body: {
37
+ filter: { processDefinitionId: processId },
38
+ moveInstructions: [{ sourceElementId: 'task', targetElementId: 'task' }],
39
+ },
40
+ tenant: 'filter',
41
+ });
42
+ await (0, assertions_1.assertStatusIn)(response, [200], 'the batch is accepted and its key returned synchronously');
43
+ const json = (await response.json());
44
+ (0, assertions_1.assertRequiredFields)(json, ['batchOperationKey']);
45
+ });
46
+ (0, api_1.test)('rejects a modification batch with an empty filter', async ({ api }) => {
47
+ const response = await api.post('/process-instances/modification', {
48
+ body: {
49
+ filter: {},
50
+ moveInstructions: [{ sourceElementId: 'a', targetElementId: 'b' }],
51
+ },
52
+ tenant: 'none',
53
+ });
54
+ // An unscoped batch would modify every instance on the cluster.
55
+ await (0, assertions_1.assertBadRequest)(response);
56
+ });
57
+ });
58
+ api_1.test.describe('Batch incident resolution', () => {
59
+ (0, api_1.test)('resolves the incidents of a definition through a batch', async ({ api, resources, }) => {
60
+ const { processId, jobType, instanceKeys } = await seed(api, resources, 2);
61
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
62
+ // Drive both jobs to zero retries so each instance carries an incident.
63
+ const jobs = await (0, eventually_1.eventuallyGet)(async () => {
64
+ const response = await api.post('/jobs/activation', {
65
+ body: {
66
+ type: jobType,
67
+ worker: 'api-v2-suite',
68
+ maxJobsToActivate: 2,
69
+ timeout: 60000,
70
+ },
71
+ tenant: 'tenantIds',
72
+ });
73
+ const json = (await response.json());
74
+ return json.jobs ?? [];
75
+ }, (found) => found.length >= 2, { description: `jobs of type ${jobType}` });
76
+ for (const job of jobs) {
77
+ await api.post('/jobs/{jobKey}/failure', {
78
+ params: { jobKey: String(job.jobKey) },
79
+ body: { errorMessage: 'exhausted', retries: 0, retryBackOff: 0 },
80
+ tenant: 'none',
81
+ });
82
+ }
83
+ await (0, eventually_1.eventually)(async () => {
84
+ const search = await api.post('/incidents/search', {
85
+ body: {
86
+ filter: { processDefinitionId: processId, state: 'ACTIVE' },
87
+ page: { limit: 20 },
88
+ },
89
+ });
90
+ await (0, assertions_1.assertPaginated)(search, { minItems: 2 });
91
+ });
92
+ const response = await api.post('/process-instances/incident-resolution', {
93
+ body: { filter: { processDefinitionId: processId } },
94
+ tenant: 'filter',
95
+ });
96
+ await (0, assertions_1.assertStatusIn)(response, [200], 'the batch is accepted and its key returned synchronously');
97
+ const json = (await response.json());
98
+ (0, assertions_1.assertRequiredFields)(json, ['batchOperationKey']);
99
+ // The batch retries each failed element. With no retries left on the jobs
100
+ // the incidents return, so the assertion here is that the batch itself ran
101
+ // to a terminal state rather than that the incidents disappeared.
102
+ // A batch is scheduled cluster-wide and works through its items, so it
103
+ // takes materially longer than an index refresh — the search-tuned
104
+ // default left it still in CREATED. The assertion names the state it got
105
+ // stuck in, so a batch that never starts is distinguishable from one that
106
+ // is merely slow.
107
+ await (0, eventually_1.eventually)(async () => {
108
+ const lookup = await api.get('/batch-operations/{batchOperationKey}', {
109
+ params: { batchOperationKey: String(json.batchOperationKey) },
110
+ tenant: 'none',
111
+ });
112
+ const batch = (await lookup.json());
113
+ (0, test_1.expect)(['COMPLETED', 'PARTIALLY_COMPLETED'], `batch ${String(json.batchOperationKey)} reached ${String(batch.state)} rather than a terminal state`).toContain(batch.state);
114
+ }, { timeout: 180000 });
115
+ });
116
+ (0, api_1.test)('rejects an incident-resolution batch with an empty filter', async ({ api, }) => {
117
+ const response = await api.post('/process-instances/incident-resolution', {
118
+ body: { filter: {} },
119
+ tenant: 'none',
120
+ });
121
+ await (0, assertions_1.assertBadRequest)(response);
122
+ });
123
+ });
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const api_1 = require("../../../../fixtures/api");
4
+ const test_1 = require("@playwright/test");
5
+ const assertions_1 = require("../../../../utils/api/assertions");
6
+ const deployments_1 = require("../../../../utils/api/deployments");
7
+ const bpmn_1 = require("../../../../utils/api/bpmn");
8
+ const eventually_1 = require("../../../../utils/api/eventually");
9
+ const keys_1 = require("../../../../utils/api/keys");
10
+ async function startWithVariables(api, resources, variables) {
11
+ const processId = (0, bpmn_1.uniqueId)('variable-api');
12
+ await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job'))));
13
+ const start = await api.post('/process-instances', {
14
+ body: { processDefinitionId: processId, variables },
15
+ tenant: 'root',
16
+ });
17
+ await (0, assertions_1.assertStatusIn)(start, [200], 'instance creation is synchronous');
18
+ const instance = (await start.json());
19
+ return String(instance.processInstanceKey);
20
+ }
21
+ api_1.test.describe('Variables', () => {
22
+ (0, api_1.test)('finds a variable set at instance start', async ({ api, resources }) => {
23
+ const processInstanceKey = await startWithVariables(api, resources, {
24
+ customerId: 'cust-1',
25
+ });
26
+ resources.track('processInstance', processInstanceKey);
27
+ await (0, eventually_1.eventually)(async () => {
28
+ const response = await api.post('/variables/search', {
29
+ body: {
30
+ filter: { processInstanceKey, name: 'customerId' },
31
+ page: { limit: 10 },
32
+ },
33
+ });
34
+ const page = await (0, assertions_1.assertPaginated)(response, { exactItems: 1 });
35
+ const item = page.items[0];
36
+ (0, test_1.expect)(item.name).toBe('customerId');
37
+ (0, test_1.expect)(item.value).toBe('"cust-1"');
38
+ });
39
+ });
40
+ (0, api_1.test)('returns a variable by key', async ({ api, resources }) => {
41
+ const processInstanceKey = await startWithVariables(api, resources, {
42
+ total: 17,
43
+ });
44
+ resources.track('processInstance', processInstanceKey);
45
+ const items = await (0, eventually_1.eventuallyGet)(async () => {
46
+ const response = await api.post('/variables/search', {
47
+ body: {
48
+ filter: { processInstanceKey, name: 'total' },
49
+ page: { limit: 10 },
50
+ },
51
+ });
52
+ return (await (0, assertions_1.assertPaginated)(response)).items;
53
+ }, (found) => found.length > 0, { description: 'variables' });
54
+ const response = await api.get('/variables/{variableKey}', {
55
+ params: { variableKey: String(items[0].variableKey) },
56
+ });
57
+ const json = (await (0, assertions_1.assertOk)(response));
58
+ (0, assertions_1.assertRequiredFields)(json, ['variableKey', 'name', 'value', 'scopeKey']);
59
+ (0, test_1.expect)(json.name).toBe('total');
60
+ });
61
+ (0, api_1.test)('rejects a lookup for an absent variable key', async ({ api }) => {
62
+ const response = await api.get('/variables/{variableKey}', {
63
+ params: { variableKey: keys_1.ABSENT_KEY },
64
+ });
65
+ await (0, assertions_1.assertNotFound)(response);
66
+ });
67
+ });
68
+ api_1.test.describe('Incidents', () => {
69
+ /**
70
+ * Drives a service task job to zero retries, which is how the engine is made
71
+ * to raise an incident deterministically. The shell suite instead searched
72
+ * the cluster for whatever incident happened to exist, so its incident tests
73
+ * asserted against unrelated runs and passed with `exactItems: 0`.
74
+ */
75
+ async function raiseIncident(api, resources) {
76
+ const processId = (0, bpmn_1.uniqueId)('incident-api');
77
+ const jobType = (0, bpmn_1.uniqueId)('incident-job');
78
+ await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask(jobType)));
79
+ const start = await api.post('/process-instances', {
80
+ body: { processDefinitionId: processId },
81
+ tenant: 'root',
82
+ });
83
+ await (0, assertions_1.assertStatusIn)(start, [200], 'instance creation is synchronous');
84
+ const instance = (await start.json());
85
+ const processInstanceKey = String(instance.processInstanceKey);
86
+ const jobs = await (0, eventually_1.eventuallyGet)(async () => {
87
+ const response = await api.post('/jobs/activation', {
88
+ body: {
89
+ type: jobType,
90
+ worker: 'api-v2-suite',
91
+ maxJobsToActivate: 1,
92
+ timeout: 60000,
93
+ },
94
+ tenant: 'tenantIds',
95
+ });
96
+ const json = (await response.json());
97
+ return json.jobs ?? [];
98
+ }, (found) => found.length > 0, { description: 'activated job' });
99
+ await api.post('/jobs/{jobKey}/failure', {
100
+ params: { jobKey: String(jobs[0].jobKey) },
101
+ body: { errorMessage: 'no retries left', retries: 0, retryBackOff: 0 },
102
+ tenant: 'none',
103
+ });
104
+ const incidents = await (0, eventually_1.eventuallyGet)(async () => {
105
+ const response = await api.post('/incidents/search', {
106
+ body: { filter: { processInstanceKey }, page: { limit: 10 } },
107
+ });
108
+ return (await (0, assertions_1.assertPaginated)(response)).items;
109
+ }, (found) => found.length > 0, { description: 'incidents' });
110
+ return { processInstanceKey, incidentKey: String(incidents[0].incidentKey) };
111
+ }
112
+ (0, api_1.test)('raises an incident for a job with no retries left', async ({ api, resources, }) => {
113
+ const { processInstanceKey, incidentKey } = await raiseIncident(api, resources);
114
+ resources.track('processInstance', processInstanceKey);
115
+ // `PENDING` is a real state in `IncidentStateEnum`, not an error: an
116
+ // incident is exported as PENDING and becomes ACTIVE once the engine has
117
+ // finished raising it. Reading once caught the earlier state.
118
+ await (0, eventually_1.eventually)(async () => {
119
+ const response = await api.get('/incidents/{incidentKey}', {
120
+ params: { incidentKey },
121
+ });
122
+ const incident = (await (0, assertions_1.assertOk)(response));
123
+ (0, assertions_1.assertRequiredFields)(incident, [
124
+ 'incidentKey',
125
+ 'processInstanceKey',
126
+ 'errorType',
127
+ 'state',
128
+ ]);
129
+ (0, test_1.expect)(incident.state).toBe('ACTIVE');
130
+ (0, test_1.expect)(String(incident.processInstanceKey)).toBe(processInstanceKey);
131
+ });
132
+ });
133
+ (0, api_1.test)('resolves an incident once its job has retries again', async ({ api, resources, }) => {
134
+ const { processInstanceKey, incidentKey } = await raiseIncident(api, resources);
135
+ resources.track('processInstance', processInstanceKey);
136
+ const incident = (await (await api.get('/incidents/{incidentKey}', { params: { incidentKey } })).json());
137
+ // Resolution retries the element, so the job needs retries first —
138
+ // otherwise the engine immediately raises the incident again.
139
+ await api.patch('/jobs/{jobKey}', {
140
+ params: { jobKey: String(incident.jobKey) },
141
+ body: { changeset: { retries: 3 } },
142
+ tenant: 'none',
143
+ });
144
+ const response = await api.post('/incidents/{incidentKey}/resolution', {
145
+ params: { incidentKey },
146
+ body: {},
147
+ tenant: 'none',
148
+ });
149
+ await (0, assertions_1.assertStatusIn)(response, [204], 'resolution is acknowledged once the command is written');
150
+ await (0, eventually_1.eventually)(async () => {
151
+ const search = await api.post('/incidents/search', {
152
+ body: {
153
+ filter: { processInstanceKey, state: 'ACTIVE' },
154
+ page: { limit: 10 },
155
+ },
156
+ });
157
+ await (0, assertions_1.assertPaginated)(search, { exactItems: 0 });
158
+ });
159
+ });
160
+ (0, api_1.test)('rejects a lookup for an absent incident key', async ({ api }) => {
161
+ const response = await api.get('/incidents/{incidentKey}', {
162
+ params: { incidentKey: keys_1.ABSENT_KEY },
163
+ });
164
+ await (0, assertions_1.assertNotFound)(response);
165
+ });
166
+ (0, api_1.test)('rejects resolving an absent incident', async ({ api }) => {
167
+ const response = await api.post('/incidents/{incidentKey}/resolution', {
168
+ params: { incidentKey: keys_1.ABSENT_ROUTABLE_KEY },
169
+ body: {},
170
+ tenant: 'none',
171
+ });
172
+ await (0, assertions_1.assertNotFound)(response);
173
+ });
174
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cluster clock endpoints.
3
+ *
4
+ * In its own file because it is its own Playwright project: pinning the clock
5
+ * moves time for **every** caller on the cluster, so these tests cannot share
6
+ * the parallel window with the rest of the suite. See the `clock` project in
7
+ * `playwright.api.config.ts` for why a project is what isolates them and what
8
+ * that costs.
9
+ *
10
+ * `mode: 'serial'` is still needed, but only for the reason it can deliver:
11
+ * ordering the tests *within this file* so two of them do not pin and reset
12
+ * against each other.
13
+ *
14
+ * The shell suite ran these from inside its EXIT trap, after the process exit
15
+ * code had already been computed — so a clock assertion could not fail the
16
+ * run, and a crash before the trap left the cluster clock pinned for whatever
17
+ * ran next.
18
+ */
19
+ export {};