@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,260 @@
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 a one-service-task process, starts `count` instances of it, and
13
+ * activates the resulting jobs.
14
+ *
15
+ * Each test gets its own job type. The shell suite shared one type across the
16
+ * whole job section, picked by searching the cluster for `.items[0].type` —
17
+ * so it could activate a job another part of the run was relying on, and its
18
+ * per-job tests were index-dependent (`ACTIVE_JOB_KEYS[0]` completed,
19
+ * `[1]` failed, and so on), which made any shortfall silently skip tests.
20
+ */
21
+ async function activateJobs(api, resources, count) {
22
+ const processId = (0, bpmn_1.uniqueId)('job-api');
23
+ const jobType = (0, bpmn_1.uniqueId)('job-type');
24
+ await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask(jobType)));
25
+ const instanceKeys = [];
26
+ for (let i = 0; i < count; i++) {
27
+ const response = await api.post('/process-instances', {
28
+ body: { processDefinitionId: processId },
29
+ tenant: 'root',
30
+ });
31
+ await (0, assertions_1.assertStatusIn)(response, [200], 'instance creation is synchronous');
32
+ const json = (await response.json());
33
+ instanceKeys.push(String(json.processInstanceKey));
34
+ }
35
+ const jobs = await (0, eventually_1.eventuallyGet)(async () => {
36
+ const response = await api.post('/jobs/activation', {
37
+ body: {
38
+ type: jobType,
39
+ worker: 'api-v2-suite',
40
+ maxJobsToActivate: count,
41
+ timeout: 60000,
42
+ },
43
+ tenant: 'tenantIds',
44
+ });
45
+ await (0, assertions_1.assertStatusIn)(response, [200], 'activation is synchronous');
46
+ const json = (await response.json());
47
+ return json.jobs ?? [];
48
+ }, (activated) => activated.length >= count, { description: `jobs of type ${jobType}` });
49
+ return { jobType, jobs, instanceKeys };
50
+ }
51
+ api_1.test.describe('Jobs', () => {
52
+ (0, api_1.test)('activates jobs created by a service task', async ({ api, resources }) => {
53
+ const { jobType, jobs, instanceKeys } = await activateJobs(api, resources, 2);
54
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
55
+ (0, test_1.expect)(jobs.length).toBe(2);
56
+ for (const job of jobs) {
57
+ (0, assertions_1.assertRequiredFields)(job, [
58
+ 'jobKey',
59
+ 'type',
60
+ 'processInstanceKey',
61
+ 'retries',
62
+ ]);
63
+ (0, test_1.expect)(job.type).toBe(jobType);
64
+ }
65
+ });
66
+ (0, api_1.test)('completes an activated job', async ({ api, resources }) => {
67
+ const { jobs, instanceKeys } = await activateJobs(api, resources, 1);
68
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
69
+ const response = await api.post('/jobs/{jobKey}/completion', {
70
+ params: { jobKey: jobs[0].jobKey },
71
+ body: { variables: { result: 'success' } },
72
+ tenant: 'none',
73
+ });
74
+ await (0, assertions_1.assertStatusIn)(response, [204], 'completion is acknowledged once the command is written');
75
+ // Completing the only task lets the instance run to its end event.
76
+ await (0, eventually_1.eventually)(async () => {
77
+ const search = await api.post('/process-instances/search', {
78
+ body: { filter: { processInstanceKey: instanceKeys[0] } },
79
+ });
80
+ const page = await (0, assertions_1.assertPaginated)(search, { exactItems: 1 });
81
+ (0, test_1.expect)(page.items[0].state).toBe('COMPLETED');
82
+ });
83
+ });
84
+ (0, api_1.test)('fails an activated job and decrements its retries', async ({ api, resources, }) => {
85
+ const { jobs, instanceKeys } = await activateJobs(api, resources, 1);
86
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
87
+ const response = await api.post('/jobs/{jobKey}/failure', {
88
+ params: { jobKey: jobs[0].jobKey },
89
+ body: { errorMessage: 'deliberate failure', retries: 1, retryBackOff: 0 },
90
+ tenant: 'none',
91
+ });
92
+ await (0, assertions_1.assertStatusIn)(response, [204], 'failure is acknowledged once the command is written');
93
+ await (0, eventually_1.eventually)(async () => {
94
+ const search = await api.post('/jobs/search', {
95
+ body: { filter: { jobKey: jobs[0].jobKey }, page: { limit: 10 } },
96
+ });
97
+ const page = await (0, assertions_1.assertPaginated)(search, { minItems: 1 });
98
+ (0, test_1.expect)(page.items[0].retries).toBe(1);
99
+ });
100
+ });
101
+ (0, api_1.test)('raises an incident when a job fails with no retries left', async ({ api, resources, }) => {
102
+ const { jobs, instanceKeys } = await activateJobs(api, resources, 1);
103
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
104
+ const response = await api.post('/jobs/{jobKey}/failure', {
105
+ params: { jobKey: jobs[0].jobKey },
106
+ body: { errorMessage: 'exhausted', retries: 0, retryBackOff: 0 },
107
+ tenant: 'none',
108
+ });
109
+ await (0, assertions_1.assertStatusIn)(response, [204], 'failure is acknowledged');
110
+ await (0, eventually_1.eventually)(async () => {
111
+ const search = await api.post('/incidents/search', {
112
+ body: {
113
+ filter: { processInstanceKey: instanceKeys[0] },
114
+ page: { limit: 10 },
115
+ },
116
+ });
117
+ const page = await (0, assertions_1.assertPaginated)(search, { minItems: 1 });
118
+ (0, test_1.expect)(page.items[0].state).toBe('ACTIVE');
119
+ });
120
+ });
121
+ (0, api_1.test)('updates job retries through a patch', async ({ api, resources }) => {
122
+ const { jobs, instanceKeys } = await activateJobs(api, resources, 1);
123
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
124
+ const response = await api.patch('/jobs/{jobKey}', {
125
+ params: { jobKey: jobs[0].jobKey },
126
+ body: { changeset: { retries: 5 } },
127
+ tenant: 'none',
128
+ });
129
+ await (0, assertions_1.assertStatusIn)(response, [204], 'the patch is acknowledged');
130
+ await (0, eventually_1.eventually)(async () => {
131
+ const search = await api.post('/jobs/search', {
132
+ body: { filter: { jobKey: jobs[0].jobKey }, page: { limit: 10 } },
133
+ });
134
+ const page = await (0, assertions_1.assertPaginated)(search, { minItems: 1 });
135
+ (0, test_1.expect)(page.items[0].retries).toBe(5);
136
+ });
137
+ });
138
+ (0, api_1.test)('throws a business error for an activated job', async ({ api, resources, }) => {
139
+ const { jobs, instanceKeys } = await activateJobs(api, resources, 1);
140
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
141
+ const response = await api.post('/jobs/{jobKey}/error', {
142
+ params: { jobKey: jobs[0].jobKey },
143
+ body: { errorCode: 'E_TEST', errorMessage: 'thrown by the API suite' },
144
+ tenant: 'none',
145
+ });
146
+ await (0, assertions_1.assertStatusIn)(response, [204], 'the error is acknowledged');
147
+ // No boundary event catches E_TEST, so the engine raises an incident.
148
+ await (0, eventually_1.eventually)(async () => {
149
+ const search = await api.post('/incidents/search', {
150
+ body: {
151
+ filter: { processInstanceKey: instanceKeys[0] },
152
+ page: { limit: 10 },
153
+ },
154
+ });
155
+ await (0, assertions_1.assertPaginated)(search, { minItems: 1 });
156
+ });
157
+ });
158
+ (0, api_1.test)('filters a job search by type', async ({ api, resources }) => {
159
+ const { jobType, instanceKeys } = await activateJobs(api, resources, 1);
160
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
161
+ // `/jobs/search` is `x-eventually-consistent: true`: activation is a
162
+ // command against the engine, while the search reads the exported index,
163
+ // so a job is activatable before it is searchable.
164
+ await (0, eventually_1.eventually)(async () => {
165
+ const response = await api.post('/jobs/search', {
166
+ body: { filter: { type: jobType }, page: { limit: 20 } },
167
+ });
168
+ const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
169
+ for (const item of page.items) {
170
+ (0, test_1.expect)(item.type).toBe(jobType);
171
+ }
172
+ });
173
+ });
174
+ (0, api_1.test)('honours the page size on a job search', async ({ api, resources }) => {
175
+ const { jobType, instanceKeys } = await activateJobs(api, resources, 2);
176
+ instanceKeys.forEach((key) => resources.track('processInstance', key));
177
+ // Wait for both jobs to be indexed before asserting the page size, or a
178
+ // limit of 1 over an index holding 0 rows passes for the wrong reason.
179
+ await (0, eventually_1.eventually)(async () => {
180
+ const all = await api.post('/jobs/search', {
181
+ body: { filter: { type: jobType }, page: { limit: 20 } },
182
+ });
183
+ await (0, assertions_1.assertPaginated)(all, { minItems: 2 });
184
+ });
185
+ const response = await api.post('/jobs/search', {
186
+ body: { filter: { type: jobType }, page: { limit: 1 } },
187
+ });
188
+ await (0, assertions_1.assertPaginated)(response, { exactItems: 1 });
189
+ });
190
+ (0, api_1.test)('returns no jobs for an unknown job type', async ({ api }) => {
191
+ const response = await api.post('/jobs/activation', {
192
+ body: {
193
+ type: (0, bpmn_1.uniqueId)('never-deployed'),
194
+ worker: 'api-v2-suite',
195
+ maxJobsToActivate: 5,
196
+ timeout: 10000,
197
+ },
198
+ tenant: 'tenantIds',
199
+ });
200
+ const json = (await (0, assertions_1.assertOk)(response));
201
+ // The shell suite asserted only the 200 here, which a response carrying
202
+ // somebody else's jobs would also satisfy.
203
+ (0, test_1.expect)(json.jobs).toEqual([]);
204
+ });
205
+ (0, api_1.test)('rejects an activation request with no job type', async ({ api }) => {
206
+ const response = await api.post('/jobs/activation', {
207
+ body: { worker: 'api-v2-suite', maxJobsToActivate: 1, timeout: 10000 },
208
+ tenant: 'tenantIds',
209
+ });
210
+ await (0, assertions_1.assertBadRequest)(response);
211
+ });
212
+ (0, api_1.test)('rejects completing an absent job', async ({ api }) => {
213
+ const response = await api.post('/jobs/{jobKey}/completion', {
214
+ params: { jobKey: keys_1.ABSENT_ROUTABLE_KEY },
215
+ body: { variables: {} },
216
+ tenant: 'none',
217
+ });
218
+ await (0, assertions_1.assertNotFound)(response);
219
+ });
220
+ (0, api_1.test)('rejects failing an absent job', async ({ api }) => {
221
+ const response = await api.post('/jobs/{jobKey}/failure', {
222
+ params: { jobKey: keys_1.ABSENT_ROUTABLE_KEY },
223
+ body: { errorMessage: 'nope', retries: 1 },
224
+ tenant: 'none',
225
+ });
226
+ await (0, assertions_1.assertNotFound)(response);
227
+ });
228
+ (0, api_1.test)('rejects throwing an error for an absent job', async ({ api }) => {
229
+ const response = await api.post('/jobs/{jobKey}/error', {
230
+ params: { jobKey: keys_1.ABSENT_ROUTABLE_KEY },
231
+ body: { errorCode: 'E_TEST' },
232
+ tenant: 'none',
233
+ });
234
+ await (0, assertions_1.assertNotFound)(response);
235
+ });
236
+ (0, api_1.test)('rejects patching an absent job', async ({ api }) => {
237
+ const response = await api.patch('/jobs/{jobKey}', {
238
+ params: { jobKey: keys_1.ABSENT_ROUTABLE_KEY },
239
+ body: { changeset: { retries: 3 } },
240
+ tenant: 'none',
241
+ });
242
+ await (0, assertions_1.assertNotFound)(response);
243
+ });
244
+ (0, api_1.test)('rejects an unauthenticated activation', async ({ api }) => {
245
+ api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
246
+ ? ''
247
+ : capabilities_1.unauthenticatedAccessAllowed);
248
+ const response = await api.post('/jobs/activation', {
249
+ body: {
250
+ type: (0, bpmn_1.uniqueId)('job'),
251
+ worker: 'api-v2-suite',
252
+ maxJobsToActivate: 1,
253
+ timeout: 10000,
254
+ },
255
+ tenant: 'tenantIds',
256
+ authorization: null,
257
+ });
258
+ await (0, assertions_1.assertUnauthorized)(response);
259
+ });
260
+ });
@@ -0,0 +1,219 @@
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
+ /** Deploys a process that waits on a correlated message and starts it. */
11
+ async function startWaitingInstance(api, resources) {
12
+ const processId = (0, bpmn_1.uniqueId)('message-api');
13
+ const messageName = (0, bpmn_1.uniqueId)('message');
14
+ const correlationKey = (0, bpmn_1.uniqueId)('correlation');
15
+ await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.messageCatch(messageName)));
16
+ const start = await api.post('/process-instances', {
17
+ body: {
18
+ processDefinitionId: processId,
19
+ variables: { msgCorrelationKey: correlationKey },
20
+ },
21
+ tenant: 'root',
22
+ });
23
+ await (0, assertions_1.assertStatusIn)(start, [200], 'instance creation is synchronous');
24
+ const instance = (await start.json());
25
+ return {
26
+ messageName,
27
+ correlationKey,
28
+ processInstanceKey: String(instance.processInstanceKey),
29
+ };
30
+ }
31
+ api_1.test.describe('Messages', () => {
32
+ (0, api_1.test)('publishes a message', async ({ api }) => {
33
+ const response = await api.post('/messages/publication', {
34
+ body: {
35
+ name: (0, bpmn_1.uniqueId)('publish'),
36
+ correlationKey: (0, bpmn_1.uniqueId)('key'),
37
+ timeToLive: 10000,
38
+ },
39
+ tenant: 'root',
40
+ });
41
+ const json = (await (0, assertions_1.assertOk)(response));
42
+ (0, assertions_1.assertRequiredFields)(json, ['messageKey']);
43
+ });
44
+ (0, api_1.test)('publishes a message carrying variables', async ({ api }) => {
45
+ const response = await api.post('/messages/publication', {
46
+ body: {
47
+ name: (0, bpmn_1.uniqueId)('publish-vars'),
48
+ correlationKey: (0, bpmn_1.uniqueId)('key'),
49
+ timeToLive: 10000,
50
+ variables: { orderId: 'order-7' },
51
+ },
52
+ tenant: 'root',
53
+ });
54
+ await (0, assertions_1.assertOk)(response);
55
+ });
56
+ (0, api_1.test)('correlates a message to a waiting instance', async ({ api, resources, }) => {
57
+ const { messageName, correlationKey, processInstanceKey } = await startWaitingInstance(api, resources);
58
+ resources.track('processInstance', processInstanceKey);
59
+ // The subscription must exist before correlation, otherwise the API
60
+ // correctly reports that nothing is waiting.
61
+ await (0, eventually_1.eventually)(async () => {
62
+ const subscriptions = await api.post('/message-subscriptions/search', {
63
+ body: { filter: { processInstanceKey }, page: { limit: 10 } },
64
+ });
65
+ await (0, assertions_1.assertPaginated)(subscriptions, { minItems: 1 });
66
+ });
67
+ const response = await api.post('/messages/correlation', {
68
+ body: { name: messageName, correlationKey, variables: { answer: 42 } },
69
+ tenant: 'root',
70
+ });
71
+ const json = (await (0, assertions_1.assertOk)(response));
72
+ (0, assertions_1.assertRequiredFields)(json, ['messageKey', 'processInstanceKey']);
73
+ (0, test_1.expect)(String(json.processInstanceKey)).toBe(processInstanceKey);
74
+ // Correlation releases the catch event, so the instance runs to its end.
75
+ await (0, eventually_1.eventually)(async () => {
76
+ const search = await api.post('/process-instances/search', {
77
+ body: { filter: { processInstanceKey } },
78
+ });
79
+ const page = await (0, assertions_1.assertPaginated)(search, { exactItems: 1 });
80
+ (0, test_1.expect)(page.items[0].state).toBe('COMPLETED');
81
+ });
82
+ });
83
+ (0, api_1.test)('reports that no instance awaits an uncorrelatable message', async ({ api, }) => {
84
+ const response = await api.post('/messages/correlation', {
85
+ body: {
86
+ name: (0, bpmn_1.uniqueId)('nobody-waiting'),
87
+ correlationKey: (0, bpmn_1.uniqueId)('key'),
88
+ },
89
+ tenant: 'root',
90
+ });
91
+ // The shell suite accepted `200 404` here, which passes whether or not
92
+ // anything correlated.
93
+ await (0, assertions_1.assertNotFound)(response);
94
+ });
95
+ (0, api_1.test)('rejects publishing a message with no name', async ({ api }) => {
96
+ const response = await api.post('/messages/publication', {
97
+ body: { correlationKey: (0, bpmn_1.uniqueId)('key') },
98
+ tenant: 'root',
99
+ });
100
+ await (0, assertions_1.assertBadRequest)(response);
101
+ });
102
+ (0, api_1.test)('rejects correlating a message with no name', async ({ api }) => {
103
+ const response = await api.post('/messages/correlation', {
104
+ body: { correlationKey: (0, bpmn_1.uniqueId)('key') },
105
+ tenant: 'root',
106
+ });
107
+ await (0, assertions_1.assertBadRequest)(response);
108
+ });
109
+ (0, api_1.test)('rejects an unauthenticated publication', async ({ api }) => {
110
+ api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
111
+ ? ''
112
+ : capabilities_1.unauthenticatedAccessAllowed);
113
+ const response = await api.post('/messages/publication', {
114
+ body: { name: (0, bpmn_1.uniqueId)('unauth'), correlationKey: (0, bpmn_1.uniqueId)('key') },
115
+ tenant: 'root',
116
+ authorization: null,
117
+ });
118
+ await (0, assertions_1.assertUnauthorized)(response);
119
+ });
120
+ (0, api_1.test)('rejects an unauthenticated correlation', async ({ api }) => {
121
+ api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
122
+ ? ''
123
+ : capabilities_1.unauthenticatedAccessAllowed);
124
+ const response = await api.post('/messages/correlation', {
125
+ body: { name: (0, bpmn_1.uniqueId)('unauth'), correlationKey: (0, bpmn_1.uniqueId)('key') },
126
+ tenant: 'root',
127
+ authorization: null,
128
+ });
129
+ await (0, assertions_1.assertUnauthorized)(response);
130
+ });
131
+ });
132
+ api_1.test.describe('Message subscriptions', () => {
133
+ (0, api_1.test)('lists the subscription opened by a waiting instance', async ({ api, resources, }) => {
134
+ const { messageName, processInstanceKey } = await startWaitingInstance(api, resources);
135
+ resources.track('processInstance', processInstanceKey);
136
+ await (0, eventually_1.eventually)(async () => {
137
+ const response = await api.post('/message-subscriptions/search', {
138
+ body: { filter: { processInstanceKey }, page: { limit: 10 } },
139
+ });
140
+ const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
141
+ const item = page.items[0];
142
+ (0, test_1.expect)(item.messageName).toBe(messageName);
143
+ });
144
+ });
145
+ (0, api_1.test)('filters subscriptions by message name', async ({ api, resources }) => {
146
+ const { messageName, processInstanceKey } = await startWaitingInstance(api, resources);
147
+ resources.track('processInstance', processInstanceKey);
148
+ await (0, eventually_1.eventually)(async () => {
149
+ const response = await api.post('/message-subscriptions/search', {
150
+ body: { filter: { messageName }, page: { limit: 10 } },
151
+ });
152
+ const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
153
+ for (const item of page.items) {
154
+ (0, test_1.expect)(item.messageName).toBe(messageName);
155
+ }
156
+ });
157
+ });
158
+ (0, api_1.test)('records a correlated subscription after correlation', async ({ api, resources, }) => {
159
+ const { messageName, correlationKey, processInstanceKey } = await startWaitingInstance(api, resources);
160
+ resources.track('processInstance', processInstanceKey);
161
+ await (0, eventually_1.eventually)(async () => {
162
+ const subscriptions = await api.post('/message-subscriptions/search', {
163
+ body: { filter: { processInstanceKey }, page: { limit: 10 } },
164
+ });
165
+ await (0, assertions_1.assertPaginated)(subscriptions, { minItems: 1 });
166
+ });
167
+ await api.post('/messages/correlation', {
168
+ body: { name: messageName, correlationKey },
169
+ tenant: 'root',
170
+ });
171
+ await (0, eventually_1.eventually)(async () => {
172
+ const response = await api.post('/correlated-message-subscriptions/search', { body: { filter: { processInstanceKey }, page: { limit: 10 } } });
173
+ await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
174
+ });
175
+ });
176
+ });
177
+ api_1.test.describe('Signals', () => {
178
+ (0, api_1.test)('broadcasts a signal', async ({ api }) => {
179
+ const response = await api.post('/signals/broadcast', {
180
+ body: { signalName: (0, bpmn_1.uniqueId)('signal') },
181
+ tenant: 'root',
182
+ });
183
+ const json = (await (0, assertions_1.assertOk)(response));
184
+ (0, assertions_1.assertRequiredFields)(json, ['signalKey']);
185
+ });
186
+ (0, api_1.test)('broadcasts a signal carrying variables', async ({ api }) => {
187
+ const response = await api.post('/signals/broadcast', {
188
+ body: { signalName: (0, bpmn_1.uniqueId)('signal'), variables: { source: 'api-suite' } },
189
+ tenant: 'root',
190
+ });
191
+ await (0, assertions_1.assertOk)(response);
192
+ });
193
+ (0, api_1.test)('rejects a broadcast with no signal name', async ({ api }) => {
194
+ // `signalName` is `required` in the request schema, so omitting it is a
195
+ // contract violation and a documented 400.
196
+ //
197
+ // An *empty* name is not: the schema sets no `minLength` and the server
198
+ // adds no check, so `{signalName: ''}` is accepted and returns a signal
199
+ // key. That is arguably wrong — no catch event can ever match it — but it
200
+ // is what the API currently promises, and a test asserting otherwise
201
+ // would be asserting an inference rather than the contract.
202
+ const response = await api.post('/signals/broadcast', {
203
+ body: {},
204
+ tenant: 'root',
205
+ });
206
+ await (0, assertions_1.assertBadRequest)(response);
207
+ });
208
+ (0, api_1.test)('rejects an unauthenticated broadcast', async ({ api }) => {
209
+ api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
210
+ ? ''
211
+ : capabilities_1.unauthenticatedAccessAllowed);
212
+ const response = await api.post('/signals/broadcast', {
213
+ body: { signalName: (0, bpmn_1.uniqueId)('signal') },
214
+ tenant: 'root',
215
+ authorization: null,
216
+ });
217
+ await (0, assertions_1.assertUnauthorized)(response);
218
+ });
219
+ });
@@ -0,0 +1,118 @@
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
+ /**
11
+ * Deploys the definition under test for one test, and tracks it for deletion.
12
+ *
13
+ * The shell suite hunted the cluster for a pre-existing process named
14
+ * "benchmark" or "one_task" and fell back to "first available" with a printed
15
+ * warning, so the endpoints under test were exercised against whatever
16
+ * happened to be deployed. Deploying our own makes every assertion below about
17
+ * a known model.
18
+ *
19
+ * Per test rather than once per file: a definition deployed in `beforeAll`
20
+ * outlives the test that needs it, so no test can clean it up and the run
21
+ * leaves it on the cluster and in its indices for good.
22
+ */
23
+ async function deployDefinitionUnderTest(api, resources) {
24
+ const processId = (0, bpmn_1.uniqueId)('process-definition-api');
25
+ const deployed = await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job-type'))));
26
+ return { processId, deployed };
27
+ }
28
+ api_1.test.describe('Process definitions', () => {
29
+ (0, api_1.test)('finds the deployed definition by id', async ({ api, resources }) => {
30
+ const { processId, deployed } = await deployDefinitionUnderTest(api, resources);
31
+ await (0, eventually_1.eventually)(async () => {
32
+ const response = await api.post('/process-definitions/search', {
33
+ body: { filter: { processDefinitionId: processId } },
34
+ });
35
+ const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
36
+ const item = page.items[0];
37
+ (0, test_1.expect)(String(item.processDefinitionKey)).toBe(deployed.processDefinitionKey);
38
+ (0, test_1.expect)(item.version).toBe(deployed.version);
39
+ });
40
+ });
41
+ (0, api_1.test)('returns the definition by key', async ({ api, resources }) => {
42
+ const { processId, deployed } = await deployDefinitionUnderTest(api, resources);
43
+ // Marked `x-eventually-consistent: true` in the API description: the read
44
+ // is served from secondary storage, so it can answer 404 for a short
45
+ // window after the deployment command is written.
46
+ await (0, eventually_1.eventually)(async () => {
47
+ const response = await api.get('/process-definitions/{processDefinitionKey}', { params: { processDefinitionKey: deployed.processDefinitionKey } });
48
+ const definition = (await (0, assertions_1.assertOk)(response));
49
+ (0, assertions_1.assertRequiredFields)(definition, [
50
+ 'processDefinitionKey',
51
+ 'processDefinitionId',
52
+ 'version',
53
+ ]);
54
+ (0, test_1.expect)(definition.processDefinitionId).toBe(processId);
55
+ });
56
+ });
57
+ (0, api_1.test)('returns the definition XML by key', async ({ api, resources }) => {
58
+ const { processId, deployed } = await deployDefinitionUnderTest(api, resources);
59
+ // Also `x-eventually-consistent: true`. The earlier "XML retrieval is
60
+ // synchronous for a deployed definition" reasoning was wrong.
61
+ await (0, eventually_1.eventually)(async () => {
62
+ const response = await api.get('/process-definitions/{processDefinitionKey}/xml', { params: { processDefinitionKey: deployed.processDefinitionKey } });
63
+ await (0, assertions_1.assertStatus)(response, 200);
64
+ // The shell suite asserted only the 200. A definition whose XML came
65
+ // back empty or belonging to another process would have passed.
66
+ const xml = await response.text();
67
+ (0, test_1.expect)(xml).toContain(`id="${processId}"`);
68
+ (0, test_1.expect)(xml).toContain('bpmn:process');
69
+ });
70
+ });
71
+ (0, api_1.test)('reports element-instance statistics for the definition', async ({ api, resources, }) => {
72
+ const { deployed } = await deployDefinitionUnderTest(api, resources);
73
+ const response = await api.post('/process-definitions/{processDefinitionKey}/statistics/element-instances', {
74
+ params: { processDefinitionKey: deployed.processDefinitionKey },
75
+ body: { filter: {} },
76
+ });
77
+ const stats = (await (0, assertions_1.assertOk)(response));
78
+ (0, test_1.expect)(Array.isArray(stats.items)).toBe(true);
79
+ });
80
+ (0, api_1.test)('searches instances of the definition', async ({ api, resources }) => {
81
+ const { deployed } = await deployDefinitionUnderTest(api, resources);
82
+ // There is no /process-definitions/{key}/instances sub-resource in any
83
+ // supported version; instances of a definition are found by filtering the
84
+ // process-instance search. The nested path was read as "404 until the
85
+ // deployment reaches secondary storage", but it 404s because nothing
86
+ // serves it, so polling only spent the retry budget before failing.
87
+ const response = await api.post('/process-instances/search', {
88
+ body: { filter: { processDefinitionKey: deployed.processDefinitionKey } },
89
+ });
90
+ await (0, assertions_1.assertPaginated)(response);
91
+ });
92
+ (0, api_1.test)('reports no linked start form for a definition without one', async ({ api, resources, }) => {
93
+ const { deployed } = await deployDefinitionUnderTest(api, resources);
94
+ const response = await api.get('/process-definitions/{processDefinitionKey}/form', { params: { processDefinitionKey: deployed.processDefinitionKey } });
95
+ // The deployed model carries no start form, so the API reports "nothing
96
+ // linked". Which of these two it uses depends on the minor version.
97
+ await (0, assertions_1.assertStatusIn)(response, [204, 404], 'a definition with no linked start form reports 204 on 8.8+ and 404 on earlier minors');
98
+ });
99
+ (0, api_1.test)('rejects a lookup for an absent definition key', async ({ api }) => {
100
+ const response = await api.get('/process-definitions/{processDefinitionKey}', { params: { processDefinitionKey: keys_1.ABSENT_KEY } });
101
+ await (0, assertions_1.assertNotFound)(response);
102
+ });
103
+ (0, api_1.test)('rejects an XML lookup for an absent definition key', async ({ api }) => {
104
+ const response = await api.get('/process-definitions/{processDefinitionKey}/xml', { params: { processDefinitionKey: keys_1.ABSENT_KEY } });
105
+ await (0, assertions_1.assertNotFound)(response);
106
+ });
107
+ (0, api_1.test)('rejects a malformed definition key', async ({ api }) => {
108
+ const response = await api.get('/process-definitions/{processDefinitionKey}', { params: { processDefinitionKey: 'not-a-key' } });
109
+ await (0, assertions_1.assertBadRequest)(response);
110
+ });
111
+ (0, api_1.test)('honours the page size on search', async ({ api }) => {
112
+ const response = await api.post('/process-definitions/search', {
113
+ body: { filter: {}, page: { limit: 1 } },
114
+ });
115
+ const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
116
+ (0, test_1.expect)(page.items.length).toBe(1);
117
+ });
118
+ });