@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,62 @@
1
+ "use strict";
2
+ /**
3
+ * Cluster clock endpoints.
4
+ *
5
+ * In its own file because it is its own Playwright project: pinning the clock
6
+ * moves time for **every** caller on the cluster, so these tests cannot share
7
+ * the parallel window with the rest of the suite. See the `clock` project in
8
+ * `playwright.api.config.ts` for why a project is what isolates them and what
9
+ * that costs.
10
+ *
11
+ * `mode: 'serial'` is still needed, but only for the reason it can deliver:
12
+ * ordering the tests *within this file* so two of them do not pin and reset
13
+ * against each other.
14
+ *
15
+ * The shell suite ran these from inside its EXIT trap, after the process exit
16
+ * code had already been computed — so a clock assertion could not fail the
17
+ * run, and a crash before the trap left the cluster clock pinned for whatever
18
+ * ran next.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ const api_1 = require("../../../../fixtures/api");
22
+ const assertions_1 = require("../../../../utils/api/assertions");
23
+ const PINNED = Date.parse('2029-01-01T00:00:00Z');
24
+ api_1.test.describe('Clock', () => {
25
+ api_1.test.describe.configure({ mode: 'serial' });
26
+ // Asserted, not fire-and-forget: a reset that answers 4xx/5xx leaves the
27
+ // cluster pinned for everything that follows, and an unchecked response
28
+ // would let the run continue and fail somewhere with no connection to the
29
+ // clock.
30
+ api_1.test.afterEach(async ({ api }) => {
31
+ const response = await api.post('/clock/reset', {
32
+ body: {},
33
+ tenant: 'none',
34
+ });
35
+ await (0, assertions_1.assertNoContent)(response);
36
+ });
37
+ (0, api_1.test)('pins the cluster clock to a timestamp', async ({ api }) => {
38
+ const response = await api.put('/clock', {
39
+ body: { timestamp: PINNED },
40
+ tenant: 'none',
41
+ });
42
+ await (0, assertions_1.assertNoContent)(response);
43
+ });
44
+ (0, api_1.test)('resets the cluster clock', async ({ api }) => {
45
+ await api.put('/clock', {
46
+ body: { timestamp: PINNED },
47
+ tenant: 'none',
48
+ });
49
+ const response = await api.post('/clock/reset', {
50
+ body: {},
51
+ tenant: 'none',
52
+ });
53
+ await (0, assertions_1.assertNoContent)(response);
54
+ });
55
+ (0, api_1.test)('rejects a malformed timestamp', async ({ api }) => {
56
+ const response = await api.put('/clock', {
57
+ body: { timestamp: 'not-a-timestamp' },
58
+ tenant: 'none',
59
+ });
60
+ await (0, assertions_1.assertBadRequest)(response);
61
+ });
62
+ });
@@ -0,0 +1,183 @@
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 bpmn_1 = require("../../../../utils/api/bpmn");
7
+ const keys_1 = require("../../../../utils/api/keys");
8
+ const config_1 = require("../../../../utils/api/config");
9
+ const capabilities_1 = require("../../../../utils/api/capabilities");
10
+ api_1.test.describe('Usage metrics', () => {
11
+ const startTime = '2020-01-01T00:00:00.000Z';
12
+ const endTime = '2030-01-01T00:00:00.000Z';
13
+ (0, api_1.test)('reports usage metrics for a time range', async ({ api }) => {
14
+ const response = await api.get('/system/usage-metrics', {
15
+ tenant: 'none',
16
+ query: { startTime, endTime },
17
+ });
18
+ const json = (await (0, assertions_1.assertOk)(response));
19
+ (0, assertions_1.assertRequiredFields)(json, ['processInstances', 'decisionInstances']);
20
+ });
21
+ (0, api_1.test)('rejects a request with no time range', async ({ api }) => {
22
+ const response = await api.get('/system/usage-metrics', { tenant: 'none' });
23
+ await (0, assertions_1.assertBadRequest)(response);
24
+ });
25
+ (0, api_1.test)('rejects a malformed timestamp', async ({ api }) => {
26
+ const response = await api.get('/system/usage-metrics', {
27
+ tenant: 'none',
28
+ query: { startTime: 'not-a-date', endTime },
29
+ });
30
+ await (0, assertions_1.assertBadRequest)(response);
31
+ });
32
+ (0, api_1.test)('rejects a range whose end precedes its start', async ({ api }) => {
33
+ const response = await api.get('/system/usage-metrics', {
34
+ tenant: 'none',
35
+ query: { startTime: endTime, endTime: startTime },
36
+ });
37
+ await (0, assertions_1.assertBadRequest)(response);
38
+ });
39
+ });
40
+ // The Clock tests used to live here. They moved to `clock-api-tests.spec.ts`
41
+ // because they pin cluster-wide state and need their own Playwright project to
42
+ // stay out of the parallel window — `mode: 'serial'` here only ordered them
43
+ // against each other, never against the other files.
44
+ api_1.test.describe('Identity read models', () => {
45
+ (0, api_1.test)('lists tenants', async ({ api }) => {
46
+ const response = await api.post('/tenants/search', {
47
+ body: { filter: {}, page: { limit: 10 } },
48
+ tenant: 'none',
49
+ });
50
+ await (0, assertions_1.assertPaginated)(response);
51
+ });
52
+ (0, api_1.test)('lists roles', async ({ api }) => {
53
+ const response = await api.post('/roles/search', {
54
+ body: { filter: {}, page: { limit: 10 } },
55
+ tenant: 'none',
56
+ });
57
+ await (0, assertions_1.assertPaginated)(response);
58
+ });
59
+ (0, api_1.test)('lists groups', async ({ api }) => {
60
+ const response = await api.post('/groups/search', {
61
+ body: { filter: {}, page: { limit: 10 } },
62
+ tenant: 'none',
63
+ });
64
+ await (0, assertions_1.assertPaginated)(response);
65
+ });
66
+ (0, api_1.test)('lists mapping rules', async ({ api }) => {
67
+ const response = await api.post('/mapping-rules/search', {
68
+ body: { filter: {}, page: { limit: 10 } },
69
+ tenant: 'none',
70
+ });
71
+ await (0, assertions_1.assertPaginated)(response);
72
+ });
73
+ (0, api_1.test)('lists users', async ({ api }) => {
74
+ api_1.test.skip(capabilities_1.usersApiUnavailable !== false, capabilities_1.usersApiUnavailable === false ? '' : capabilities_1.usersApiUnavailable);
75
+ const response = await api.post('/users/search', {
76
+ body: { filter: {}, page: { limit: 10 } },
77
+ tenant: 'none',
78
+ });
79
+ await (0, assertions_1.assertPaginated)(response);
80
+ });
81
+ });
82
+ /**
83
+ * Holds the capability gate used across the identity specs to its word.
84
+ *
85
+ * Whichever way the cluster is configured, exactly one of these two runs and
86
+ * asserts what that configuration means for `/v2/users/**`. If the gate were
87
+ * ever wrong — a cluster that does serve the API but is read as OIDC — the
88
+ * matching test here fails, so a mis-set gate costs a red run rather than
89
+ * silently removing the user coverage it controls.
90
+ */
91
+ api_1.test.describe('Users API availability', () => {
92
+ (0, api_1.test)('is closed with a stated reason when the cluster is in OIDC mode', async ({ api, }) => {
93
+ api_1.test.skip(capabilities_1.usersApiUnavailable === false, 'the cluster serves the Users API');
94
+ const response = await api.post('/users/search', {
95
+ body: { filter: {}, page: { limit: 10 } },
96
+ tenant: 'none',
97
+ });
98
+ await (0, assertions_1.assertForbidden)(response, /users api is disabled/i);
99
+ });
100
+ (0, api_1.test)('is served when the cluster manages users locally', async ({ api }) => {
101
+ api_1.test.skip(capabilities_1.usersApiUnavailable !== false, 'the cluster is in OIDC mode');
102
+ const response = await api.post('/users/search', {
103
+ body: { filter: {}, page: { limit: 10 } },
104
+ tenant: 'none',
105
+ });
106
+ await (0, assertions_1.assertPaginated)(response);
107
+ });
108
+ });
109
+ /**
110
+ * Reports whether this cluster enforces authorization.
111
+ *
112
+ * The authorization *enforcement* spec needs two things: a second principal
113
+ * that holds no roles, and a cluster that actually checks permissions. It
114
+ * gates on the first. This records the second, because the SM QA clusters do
115
+ * not enforce — `test`, the client this suite authenticates as, is not one of
116
+ * the three admin mappings the chart values initialise, yet nothing refuses
117
+ * it. Without this test that fact lives only in a comment, and a cluster that
118
+ * silently started or stopped enforcing would look identical.
119
+ */
120
+ api_1.test.describe('Authorization enforcement', () => {
121
+ (0, api_1.test)('reports whether the cluster enforces authorization', async ({ api, }, testInfo) => {
122
+ const enabled = await (0, capabilities_1.readAuthorizationsEnabled)(api);
123
+ // `enabled` comes back undefined for two unrelated reasons, and naming
124
+ // the wrong one sends the reader somewhere there is nothing to find.
125
+ // Before 8.10 the endpoint is not published at all. From 8.10 it is, but
126
+ // it answers for the *calling principal*, so a cluster that serves /v2
127
+ // without credentials has no principal for it to answer for: on c8Run
128
+ // `/v2/authentication/me` refuses an anonymous caller even though the
129
+ // rest of the tree does not. Either way enforcement being unreadable is
130
+ // not the same as enforcement being off.
131
+ api_1.test.skip(enabled === undefined, (0, config_1.atLeastVersion)('8.10')
132
+ ? 'this cluster does not authenticate the caller, so ' +
133
+ '/v2/authentication/me/authorizations/search has no principal to ' +
134
+ 'report for — the enforcement state cannot be read here, which ' +
135
+ 'leaves this deployment without the safety net this test is (see ' +
136
+ 'tests/api/README.md)'
137
+ : 'the cluster does not publish ' +
138
+ '/v2/authentication/me/authorizations/search (pre-8.10)');
139
+ testInfo.annotations.push({
140
+ type: 'authorization enforcement',
141
+ description: enabled
142
+ ? 'enabled — an unprivileged principal would be refused, so the ' +
143
+ 'enforcement spec is meaningful once one is configured'
144
+ : 'DISABLED — every principal is permitted, so authorization ' +
145
+ 'enforcement cannot be observed on this cluster no matter what ' +
146
+ 'second principal is supplied (see tests/api/README.md)',
147
+ });
148
+ if (!enabled) {
149
+ console.warn('Authorization enforcement is DISABLED on this cluster: every ' +
150
+ 'principal is permitted, so the enforcement spec could not observe ' +
151
+ 'a refusal even with SECONDARY_CLIENT_ID set. See ' +
152
+ 'tests/api/README.md for what closing that gap requires.');
153
+ }
154
+ // Belt and braces: `readAuthorizationsEnabled` already throws on a 200
155
+ // whose `authorizationsEnabled` is not a boolean, and on any status that
156
+ // is neither the answer nor one of the two that mean the cluster cannot
157
+ // give it. This keeps the contract visible in the test that depends on it.
158
+ (0, test_1.expect)(typeof enabled).toBe('boolean');
159
+ });
160
+ });
161
+ api_1.test.describe('Ad-hoc sub-process activities', () => {
162
+ (0, api_1.test)('rejects activating activities on an absent sub-process', async ({ api, }) => {
163
+ const response = await api.post('/element-instances/ad-hoc-activities/{adHocSubProcessInstanceKey}/activation', {
164
+ params: { adHocSubProcessInstanceKey: keys_1.ABSENT_ROUTABLE_KEY },
165
+ body: { elements: [{ elementId: (0, bpmn_1.uniqueId)('activity') }] },
166
+ tenant: 'none',
167
+ });
168
+ await (0, assertions_1.assertStatusIn)(response, [404], 'an absent ad-hoc sub-process instance is reported as not found');
169
+ });
170
+ (0, api_1.test)('rejects an activation request carrying no elements', async ({ api }) => {
171
+ const response = await api.post('/element-instances/ad-hoc-activities/{adHocSubProcessInstanceKey}/activation', {
172
+ params: { adHocSubProcessInstanceKey: keys_1.ABSENT_ROUTABLE_KEY },
173
+ body: {},
174
+ tenant: 'none',
175
+ });
176
+ // Validation precedes the existence check, so this is a 400 rather than
177
+ // the 404 above.
178
+ (0, test_1.expect)([400, 404]).toContain(response.status());
179
+ if (response.status() === 400) {
180
+ await (0, assertions_1.assertBadRequest)(response);
181
+ }
182
+ });
183
+ });
@@ -0,0 +1,258 @@
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
+ /** Starts a process holding one Zeebe user task and returns that task's key. */
12
+ async function startUserTask(api, resources) {
13
+ const processId = (0, bpmn_1.uniqueId)('user-task-api');
14
+ await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.userTask()));
15
+ const start = await api.post('/process-instances', {
16
+ body: { processDefinitionId: processId },
17
+ tenant: 'root',
18
+ });
19
+ await (0, assertions_1.assertStatusIn)(start, [200], 'instance creation is synchronous');
20
+ const instance = (await start.json());
21
+ const processInstanceKey = String(instance.processInstanceKey);
22
+ let tasks;
23
+ try {
24
+ tasks = await (0, eventually_1.eventuallyGet)(async () => {
25
+ const response = await api.post('/user-tasks/search', {
26
+ body: { filter: { processInstanceKey }, page: { limit: 10 } },
27
+ });
28
+ const page = await (0, assertions_1.assertPaginated)(response);
29
+ return page.items;
30
+ }, (items) => items.length > 0, { description: `user tasks for instance ${processInstanceKey}` });
31
+ }
32
+ catch (error) {
33
+ // An empty result cannot say *why* it is empty, and that is the whole
34
+ // question when this fails: did the engine never create the task (our
35
+ // model is wrong), or did it create one that was never exported (the
36
+ // cluster is wrong)? The element instances answer it — they come from a
37
+ // different index, so a `task` element sitting in the instance while the
38
+ // user-task search stays empty means the record exists and only its
39
+ // export is missing.
40
+ throw new Error(`${error instanceof Error ? error.message : String(error)}\n` +
41
+ (await describeInstance(api, processInstanceKey, processId)));
42
+ }
43
+ return { userTaskKey: String(tasks[0].userTaskKey), processInstanceKey };
44
+ }
45
+ /** Where the instance actually got to, for the failure message above. */
46
+ async function describeInstance(api, processInstanceKey, processId) {
47
+ try {
48
+ const instances = await api.post('/process-instances/search', {
49
+ body: { filter: { processInstanceKey } },
50
+ });
51
+ const elements = await api.post('/element-instances/search', {
52
+ body: { filter: { processInstanceKey }, page: { limit: 20 } },
53
+ });
54
+ // The definition was deployed moments before the instance started, so it
55
+ // is the control: both travel the same exporter to the same secondary
56
+ // storage. A definition that arrived while its instance did not is a
57
+ // fault specific to this instance; neither arriving means export was
58
+ // behind or stopped for everything, which is a cluster fault rather than
59
+ // anything these tests can assert around. Run 35696766504 failed 12 tests
60
+ // without recording which of the two it was.
61
+ const definitions = await api.post('/process-definitions/search', {
62
+ body: { filter: { processDefinitionId: processId }, page: { limit: 1 } },
63
+ });
64
+ const instanceJson = (await instances.json());
65
+ const elementJson = (await elements.json());
66
+ const definitionJson = (await definitions.json());
67
+ const state = instanceJson.items?.[0]?.state ?? '<no instance found>';
68
+ const active = (elementJson.items ?? []).map((item) => `${item.elementId}:${item.type}=${item.state}`);
69
+ const exported = (definitionJson.items ?? []).length > 0;
70
+ return (`Instance ${processInstanceKey} state=${state}; ` +
71
+ `element instances: ${active.length > 0 ? active.join(', ') : '<none>'}; ` +
72
+ `its definition ${processId} ` +
73
+ `${exported
74
+ ? 'DID reach secondary storage (export is live, so this instance is the fault)'
75
+ : 'did NOT reach secondary storage either (export is behind or stopped cluster-wide)'}`);
76
+ }
77
+ catch (error) {
78
+ return `Could not describe instance ${processInstanceKey}: ${error instanceof Error ? error.message : String(error)}`;
79
+ }
80
+ }
81
+ api_1.test.describe('User tasks', () => {
82
+ (0, api_1.test)('finds the task created by a running instance', async ({ api, resources, }) => {
83
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
84
+ resources.track('processInstance', processInstanceKey);
85
+ const response = await api.post('/user-tasks/search', {
86
+ body: { filter: { processInstanceKey } },
87
+ });
88
+ const page = await (0, assertions_1.assertPaginated)(response, { exactItems: 1 });
89
+ const task = page.items[0];
90
+ (0, test_1.expect)(String(task.userTaskKey)).toBe(userTaskKey);
91
+ (0, test_1.expect)(task.state).toBe('CREATED');
92
+ });
93
+ (0, api_1.test)('returns a task by key', async ({ api, resources }) => {
94
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
95
+ resources.track('processInstance', processInstanceKey);
96
+ const response = await api.get('/user-tasks/{userTaskKey}', {
97
+ params: { userTaskKey },
98
+ });
99
+ const task = (await (0, assertions_1.assertOk)(response));
100
+ (0, assertions_1.assertRequiredFields)(task, ['userTaskKey', 'state', 'elementId']);
101
+ (0, test_1.expect)(task.elementId).toBe('task');
102
+ (0, test_1.expect)(task.state).toBe('CREATED');
103
+ });
104
+ (0, api_1.test)('assigns a task to a user', async ({ api, resources }) => {
105
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
106
+ resources.track('processInstance', processInstanceKey);
107
+ const response = await api.post('/user-tasks/{userTaskKey}/assignment', {
108
+ params: { userTaskKey },
109
+ body: { assignee: 'demo', allowOverride: true },
110
+ tenant: 'none',
111
+ });
112
+ await (0, assertions_1.assertStatusIn)(response, [204], 'assignment is acknowledged');
113
+ await (0, eventually_1.eventually)(async () => {
114
+ const lookup = await api.get('/user-tasks/{userTaskKey}', {
115
+ params: { userTaskKey },
116
+ });
117
+ const task = (await (0, assertions_1.assertOk)(lookup));
118
+ // The shell suite asserted the 204 and moved on, so an assignment that
119
+ // was accepted but never applied counted as a pass.
120
+ (0, test_1.expect)(task.assignee).toBe('demo');
121
+ });
122
+ });
123
+ (0, api_1.test)('unassigns an assigned task', async ({ api, resources }) => {
124
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
125
+ resources.track('processInstance', processInstanceKey);
126
+ await api.post('/user-tasks/{userTaskKey}/assignment', {
127
+ params: { userTaskKey },
128
+ body: { assignee: 'demo', allowOverride: true },
129
+ tenant: 'none',
130
+ });
131
+ const response = await api.delete('/user-tasks/{userTaskKey}/assignee', {
132
+ params: { userTaskKey },
133
+ tenant: 'none',
134
+ });
135
+ await (0, assertions_1.assertStatusIn)(response, [204], 'unassignment is acknowledged');
136
+ await (0, eventually_1.eventually)(async () => {
137
+ const lookup = await api.get('/user-tasks/{userTaskKey}', {
138
+ params: { userTaskKey },
139
+ });
140
+ const task = (await (0, assertions_1.assertOk)(lookup));
141
+ (0, test_1.expect)(task.assignee ?? null).toBeNull();
142
+ });
143
+ });
144
+ (0, api_1.test)('updates a task through a patch', async ({ api, resources }) => {
145
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
146
+ resources.track('processInstance', processInstanceKey);
147
+ const response = await api.patch('/user-tasks/{userTaskKey}', {
148
+ params: { userTaskKey },
149
+ body: { changeset: { priority: 75 } },
150
+ tenant: 'none',
151
+ });
152
+ await (0, assertions_1.assertStatusIn)(response, [204], 'the patch is acknowledged');
153
+ await (0, eventually_1.eventually)(async () => {
154
+ const lookup = await api.get('/user-tasks/{userTaskKey}', {
155
+ params: { userTaskKey },
156
+ });
157
+ const task = (await (0, assertions_1.assertOk)(lookup));
158
+ (0, test_1.expect)(task.priority).toBe(75);
159
+ });
160
+ });
161
+ (0, api_1.test)('completes a task and finishes the instance', async ({ api, resources, }) => {
162
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
163
+ const response = await api.post('/user-tasks/{userTaskKey}/completion', {
164
+ params: { userTaskKey },
165
+ body: { variables: { approved: true } },
166
+ tenant: 'none',
167
+ });
168
+ await (0, assertions_1.assertStatusIn)(response, [204], 'completion is acknowledged');
169
+ await (0, eventually_1.eventually)(async () => {
170
+ const search = await api.post('/process-instances/search', {
171
+ body: { filter: { processInstanceKey } },
172
+ });
173
+ const page = await (0, assertions_1.assertPaginated)(search, { exactItems: 1 });
174
+ (0, test_1.expect)(page.items[0].state).toBe('COMPLETED');
175
+ });
176
+ });
177
+ (0, api_1.test)('searches the variables visible to a task', async ({ api, resources }) => {
178
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
179
+ resources.track('processInstance', processInstanceKey);
180
+ const response = await api.post('/user-tasks/{userTaskKey}/variables/search', { params: { userTaskKey }, body: { filter: {} } });
181
+ await (0, assertions_1.assertPaginated)(response);
182
+ });
183
+ (0, api_1.test)('reports no linked form for a task without one', async ({ api, resources, }) => {
184
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
185
+ resources.track('processInstance', processInstanceKey);
186
+ const response = await api.get('/user-tasks/{userTaskKey}/form', {
187
+ params: { userTaskKey },
188
+ });
189
+ await (0, assertions_1.assertStatusIn)(response, [204, 404], 'a task with no linked form reports 204 on 8.8+ and 404 on earlier minors');
190
+ });
191
+ (0, api_1.test)('rejects assigning a completed task', async ({ api, resources }) => {
192
+ const { userTaskKey } = await startUserTask(api, resources);
193
+ await api.post('/user-tasks/{userTaskKey}/completion', {
194
+ params: { userTaskKey },
195
+ body: { variables: {} },
196
+ tenant: 'none',
197
+ });
198
+ await (0, eventually_1.eventually)(async () => {
199
+ const response = await api.post('/user-tasks/{userTaskKey}/assignment', {
200
+ params: { userTaskKey },
201
+ body: { assignee: 'demo', allowOverride: true },
202
+ tenant: 'none',
203
+ });
204
+ await (0, assertions_1.assertStatusIn)(response, [404, 409], 'a completed task is either gone or in an invalid state, depending on minor version');
205
+ });
206
+ });
207
+ (0, api_1.test)('rejects a lookup for an absent task key', async ({ api }) => {
208
+ const response = await api.get('/user-tasks/{userTaskKey}', {
209
+ params: { userTaskKey: keys_1.ABSENT_KEY },
210
+ });
211
+ await (0, assertions_1.assertNotFound)(response);
212
+ });
213
+ (0, api_1.test)('rejects completing an absent task', async ({ api }) => {
214
+ const response = await api.post('/user-tasks/{userTaskKey}/completion', {
215
+ params: { userTaskKey: keys_1.ABSENT_ROUTABLE_KEY },
216
+ body: { variables: {} },
217
+ tenant: 'none',
218
+ });
219
+ await (0, assertions_1.assertNotFound)(response);
220
+ });
221
+ (0, api_1.test)('rejects a patch carrying no changeset', async ({ api, resources }) => {
222
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
223
+ resources.track('processInstance', processInstanceKey);
224
+ const response = await api.patch('/user-tasks/{userTaskKey}', {
225
+ params: { userTaskKey },
226
+ body: {},
227
+ tenant: 'none',
228
+ });
229
+ await (0, assertions_1.assertBadRequest)(response);
230
+ });
231
+ (0, api_1.test)('rejects an unauthenticated task search', async ({ api }) => {
232
+ api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
233
+ ? ''
234
+ : capabilities_1.unauthenticatedAccessAllowed);
235
+ const response = await api.post('/user-tasks/search', {
236
+ body: { filter: {} },
237
+ authorization: null,
238
+ });
239
+ await (0, assertions_1.assertUnauthorized)(response);
240
+ });
241
+ (0, api_1.test)('rejects assigning a task that is already assigned without override', async ({ api, resources, }) => {
242
+ const { userTaskKey, processInstanceKey } = await startUserTask(api, resources);
243
+ resources.track('processInstance', processInstanceKey);
244
+ await api.post('/user-tasks/{userTaskKey}/assignment', {
245
+ params: { userTaskKey },
246
+ body: { assignee: 'demo', allowOverride: true },
247
+ tenant: 'none',
248
+ });
249
+ await (0, eventually_1.eventually)(async () => {
250
+ const response = await api.post('/user-tasks/{userTaskKey}/assignment', {
251
+ params: { userTaskKey },
252
+ body: { assignee: 'someone-else', allowOverride: false },
253
+ tenant: 'none',
254
+ });
255
+ await (0, assertions_1.assertConflict)(response);
256
+ });
257
+ });
258
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Assertion vocabulary for REST v2 responses.
3
+ *
4
+ * Each helper asserts the status *and* the problem body the API documents for
5
+ * it, so a response that is right by accident fails. The legacy shell suite
6
+ * asserted status codes only, and accepted lists such as `200 201 403` that
7
+ * passed whether the call succeeded or was rejected outright.
8
+ *
9
+ * Where a status genuinely varies — across versions, or because an operation is
10
+ * synchronous on one path and accepted-for-later on another — use
11
+ * `assertStatusIn`, which requires the reason in writing at the call site.
12
+ */
13
+ import { type APIResponse } from '@playwright/test';
14
+ /** Fields every paginated search response carries. */
15
+ export declare const PAGINATED_FIELDS: readonly ["items", "page"];
16
+ export declare function assertStatus(response: APIResponse, expected: number): Promise<void>;
17
+ /**
18
+ * Accepts any of `expected`. `reason` documents why the endpoint may answer
19
+ * with more than one status and is printed on failure — it is required so that
20
+ * widening an assertion is a deliberate, reviewable act.
21
+ */
22
+ export declare function assertStatusIn(response: APIResponse, expected: readonly number[], reason: string): Promise<void>;
23
+ export declare function assertRequiredFields(payload: unknown, required: readonly string[]): void;
24
+ export declare function assertEqualsForKeys(payload: unknown, expected: Record<string, unknown>, keys: readonly string[]): void;
25
+ export declare function assertOk(response: APIResponse): Promise<unknown>;
26
+ export declare function assertCreated(response: APIResponse): Promise<unknown>;
27
+ export declare function assertNoContent(response: APIResponse): Promise<void>;
28
+ export declare function assertUnauthorized(response: APIResponse): Promise<void>;
29
+ /**
30
+ * A 403 does not carry one fixed problem title. The authorization layer sends
31
+ * `FORBIDDEN`, while the filter that closes `/v2/users/**` on an OIDC cluster
32
+ * sends `Access issue` — both are the API refusing the call. The status and
33
+ * the detail carry the meaning, so the title is matched loosely and the
34
+ * caller pins the specific refusal through `detail`.
35
+ */
36
+ export declare function assertForbidden(response: APIResponse, detail?: string | RegExp): Promise<void>;
37
+ export declare function assertNotFound(response: APIResponse, detail?: string | RegExp): Promise<void>;
38
+ export declare function assertConflict(response: APIResponse, detail?: string | RegExp): Promise<void>;
39
+ export declare function assertBadRequest(response: APIResponse, detail?: string | RegExp, title?: string | RegExp): Promise<void>;
40
+ export declare function assertPaginated(response: APIResponse, options?: {
41
+ minItems?: number;
42
+ exactItems?: number;
43
+ minTotalItems?: number;
44
+ exactTotalItems?: number;
45
+ }): Promise<{
46
+ items: unknown[];
47
+ page: {
48
+ totalItems: number;
49
+ };
50
+ }>;