@camunda/e2e-test-suite 0.0.1241 → 0.0.1243
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fixtures/api.d.ts +15 -0
- package/dist/fixtures/api.js +27 -0
- package/dist/pages/SM-8.10/NavigationPage.d.ts +3 -2
- package/dist/pages/SM-8.10/NavigationPage.js +24 -34
- package/dist/pages/SM-8.10/ProcessApplicationPage.js +7 -0
- package/dist/tests/api/v2/authorization/authorization-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/authorization/authorization-api-tests.spec.js +247 -0
- package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.d.ts +18 -0
- package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.js +208 -0
- package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.js +264 -0
- package/dist/tests/api/v2/cluster/cluster-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/cluster/cluster-api-tests.spec.js +86 -0
- package/dist/tests/api/v2/decision/decision-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/decision/decision-api-tests.spec.js +233 -0
- package/dist/tests/api/v2/deployment/deployment-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/deployment/deployment-api-tests.spec.js +147 -0
- package/dist/tests/api/v2/document/document-api-tests.spec.d.ts +9 -0
- package/dist/tests/api/v2/document/document-api-tests.spec.js +111 -0
- package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.js +127 -0
- package/dist/tests/api/v2/flows/end-to-end-flows.spec.d.ts +9 -0
- package/dist/tests/api/v2/flows/end-to-end-flows.spec.js +204 -0
- package/dist/tests/api/v2/form/form-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/form/form-api-tests.spec.js +88 -0
- package/dist/tests/api/v2/identity/group-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/group-api-tests.spec.js +172 -0
- package/dist/tests/api/v2/identity/membership-api-tests.spec.d.ts +10 -0
- package/dist/tests/api/v2/identity/membership-api-tests.spec.js +164 -0
- package/dist/tests/api/v2/identity/role-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/role-api-tests.spec.js +212 -0
- package/dist/tests/api/v2/identity/tenant-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/tenant-api-tests.spec.js +319 -0
- package/dist/tests/api/v2/identity/user-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/user-api-tests.spec.js +148 -0
- package/dist/tests/api/v2/job/job-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/job/job-api-tests.spec.js +260 -0
- package/dist/tests/api/v2/message/message-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/message/message-api-tests.spec.js +219 -0
- package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.js +118 -0
- package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.js +335 -0
- package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.js +123 -0
- package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.js +174 -0
- package/dist/tests/api/v2/system/clock-api-tests.spec.d.ts +19 -0
- package/dist/tests/api/v2/system/clock-api-tests.spec.js +62 -0
- package/dist/tests/api/v2/system/system-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/system/system-api-tests.spec.js +183 -0
- package/dist/tests/api/v2/user-task/user-task-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/user-task/user-task-api-tests.spec.js +258 -0
- package/dist/utils/api/assertions.d.ts +50 -0
- package/dist/utils/api/assertions.js +142 -0
- package/dist/utils/api/auth.d.ts +27 -0
- package/dist/utils/api/auth.js +137 -0
- package/dist/utils/api/beans.d.ts +46 -0
- package/dist/utils/api/beans.js +87 -0
- package/dist/utils/api/bpmn.d.ts +50 -0
- package/dist/utils/api/bpmn.js +217 -0
- package/dist/utils/api/capabilities.d.ts +90 -0
- package/dist/utils/api/capabilities.js +198 -0
- package/dist/utils/api/cleanup.d.ts +24 -0
- package/dist/utils/api/cleanup.js +120 -0
- package/dist/utils/api/config.d.ts +103 -0
- package/dist/utils/api/config.js +62 -0
- package/dist/utils/api/deployments.d.ts +45 -0
- package/dist/utils/api/deployments.js +120 -0
- package/dist/utils/api/eventually.d.ts +36 -0
- package/dist/utils/api/eventually.js +54 -0
- package/dist/utils/api/http.d.ts +56 -0
- package/dist/utils/api/http.js +244 -0
- package/dist/utils/api/identity.d.ts +29 -0
- package/dist/utils/api/identity.js +43 -0
- package/dist/utils/api/keys.d.ts +37 -0
- package/dist/utils/api/keys.js +40 -0
- package/dist/utils/api/paths.d.ts +25 -0
- package/dist/utils/api/paths.js +167 -0
- package/dist/utils/api/tenancy.d.ts +31 -0
- package/dist/utils/api/tenancy.js +65 -0
- package/package.json +1 -1
- package/playwright.config.ts +14 -2
- package/dist/resources/test-api-v2-complete.sh +0 -12246
- package/resources/test-api-v2-complete.sh +0 -12246
|
@@ -0,0 +1,148 @@
|
|
|
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 beans_1 = require("../../../../utils/api/beans");
|
|
7
|
+
const eventually_1 = require("../../../../utils/api/eventually");
|
|
8
|
+
const bpmn_1 = require("../../../../utils/api/bpmn");
|
|
9
|
+
const capabilities_1 = require("../../../../utils/api/capabilities");
|
|
10
|
+
async function createUser(api, resources, overrides = {}) {
|
|
11
|
+
const user = (0, beans_1.newUser)(overrides);
|
|
12
|
+
await (0, assertions_1.assertCreated)(await api.post('/users', { body: user, tenant: 'none' }));
|
|
13
|
+
resources.track('user', user.username);
|
|
14
|
+
return user;
|
|
15
|
+
}
|
|
16
|
+
api_1.test.describe('Users', () => {
|
|
17
|
+
// Every test below creates or reads a local user. On an OIDC cluster the
|
|
18
|
+
// whole /v2/users/** tree is turned off, so none of them has a subject.
|
|
19
|
+
// `system-api-tests.spec.ts` asserts that the tree really is closed there,
|
|
20
|
+
// so this gate cannot quietly drop coverage if the mode is misread.
|
|
21
|
+
api_1.test.skip(capabilities_1.usersApiUnavailable !== false, capabilities_1.usersApiUnavailable === false ? '' : capabilities_1.usersApiUnavailable);
|
|
22
|
+
(0, api_1.test)('creates a user', async ({ api, resources }) => {
|
|
23
|
+
const user = (0, beans_1.newUser)();
|
|
24
|
+
const response = await api.post('/users', { body: user, tenant: 'none' });
|
|
25
|
+
const created = (await (0, assertions_1.assertCreated)(response));
|
|
26
|
+
(0, assertions_1.assertRequiredFields)(created, beans_1.userRequiredFields);
|
|
27
|
+
(0, test_1.expect)(created.username).toBe(user.username);
|
|
28
|
+
(0, test_1.expect)(created.email).toBe(user.email);
|
|
29
|
+
// A password must never be echoed back by the API.
|
|
30
|
+
(0, test_1.expect)(created.password).toBeUndefined();
|
|
31
|
+
resources.track('user', user.username);
|
|
32
|
+
});
|
|
33
|
+
(0, api_1.test)('returns a created user by username', async ({ api, resources }) => {
|
|
34
|
+
const user = await createUser(api, resources);
|
|
35
|
+
await (0, eventually_1.eventually)(async () => {
|
|
36
|
+
const response = await api.get('/users/{username}', {
|
|
37
|
+
params: { username: user.username },
|
|
38
|
+
tenant: 'none',
|
|
39
|
+
});
|
|
40
|
+
const json = (await (0, assertions_1.assertOk)(response));
|
|
41
|
+
(0, test_1.expect)(json.username).toBe(user.username);
|
|
42
|
+
(0, test_1.expect)(json.email).toBe(user.email);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
(0, api_1.test)('updates a user profile', async ({ api, resources }) => {
|
|
46
|
+
const user = await createUser(api, resources);
|
|
47
|
+
const updatedName = `Updated ${user.name}`;
|
|
48
|
+
const response = await api.put('/users/{username}', {
|
|
49
|
+
params: { username: user.username },
|
|
50
|
+
body: { name: updatedName, email: user.email },
|
|
51
|
+
tenant: 'none',
|
|
52
|
+
});
|
|
53
|
+
await (0, assertions_1.assertStatusIn)(response, [200, 204], 'the update returns the user on 8.8+ and an empty body on earlier minors');
|
|
54
|
+
await (0, eventually_1.eventually)(async () => {
|
|
55
|
+
const lookup = await api.get('/users/{username}', {
|
|
56
|
+
params: { username: user.username },
|
|
57
|
+
tenant: 'none',
|
|
58
|
+
});
|
|
59
|
+
const json = (await (0, assertions_1.assertOk)(lookup));
|
|
60
|
+
(0, test_1.expect)(json.name).toBe(updatedName);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
(0, api_1.test)('finds a created user through search', async ({ api, resources }) => {
|
|
64
|
+
const user = await createUser(api, resources);
|
|
65
|
+
await (0, eventually_1.eventually)(async () => {
|
|
66
|
+
const response = await api.post('/users/search', {
|
|
67
|
+
body: { filter: { username: user.username }, page: { limit: 10 } },
|
|
68
|
+
tenant: 'none',
|
|
69
|
+
});
|
|
70
|
+
await (0, assertions_1.assertPaginated)(response, { exactItems: 1 });
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
(0, api_1.test)('deletes a user', async ({ api, resources }) => {
|
|
74
|
+
const user = await createUser(api, resources);
|
|
75
|
+
const response = await api.delete('/users/{username}', {
|
|
76
|
+
params: { username: user.username },
|
|
77
|
+
tenant: 'none',
|
|
78
|
+
});
|
|
79
|
+
await (0, assertions_1.assertStatusIn)(response, [204], 'deletion is acknowledged');
|
|
80
|
+
resources.forget('user', user.username);
|
|
81
|
+
await (0, eventually_1.eventually)(async () => {
|
|
82
|
+
const lookup = await api.get('/users/{username}', {
|
|
83
|
+
params: { username: user.username },
|
|
84
|
+
tenant: 'none',
|
|
85
|
+
});
|
|
86
|
+
await (0, assertions_1.assertNotFound)(lookup);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// There is no /users/{username}/roles|groups|tenants sub-search on any
|
|
90
|
+
// supported version — membership is only queryable from the owning side.
|
|
91
|
+
// The two tests that stood here asserted `exactItems: 0` against a 404 from
|
|
92
|
+
// an unrouted path, so neither could report anything. Role, group and
|
|
93
|
+
// tenant membership is asserted in role-api-tests, group-api-tests and
|
|
94
|
+
// tenant-api-tests, each through the endpoint that exists.
|
|
95
|
+
(0, api_1.test)('rejects a username that already exists', async ({ api, resources }) => {
|
|
96
|
+
const user = await createUser(api, resources);
|
|
97
|
+
const response = await api.post('/users', {
|
|
98
|
+
body: (0, beans_1.newUser)({ username: user.username }),
|
|
99
|
+
tenant: 'none',
|
|
100
|
+
});
|
|
101
|
+
await (0, assertions_1.assertConflict)(response);
|
|
102
|
+
});
|
|
103
|
+
(0, api_1.test)('rejects a malformed email address', async ({ api }) => {
|
|
104
|
+
const response = await api.post('/users', {
|
|
105
|
+
body: (0, beans_1.newUser)({ email: 'not-an-email' }),
|
|
106
|
+
tenant: 'none',
|
|
107
|
+
});
|
|
108
|
+
await (0, assertions_1.assertBadRequest)(response, /email/i);
|
|
109
|
+
});
|
|
110
|
+
(0, api_1.test)('rejects a user with no password', async ({ api }) => {
|
|
111
|
+
const user = (0, beans_1.newUser)();
|
|
112
|
+
delete user.password;
|
|
113
|
+
const response = await api.post('/users', { body: user, tenant: 'none' });
|
|
114
|
+
await (0, assertions_1.assertBadRequest)(response, /password/i);
|
|
115
|
+
});
|
|
116
|
+
(0, api_1.test)('rejects updating an absent user', async ({ api }) => {
|
|
117
|
+
const response = await api.put('/users/{username}', {
|
|
118
|
+
params: { username: (0, bpmn_1.uniqueId)('absent-user') },
|
|
119
|
+
body: { name: 'does not exist', email: 'nobody@example.com' },
|
|
120
|
+
tenant: 'none',
|
|
121
|
+
});
|
|
122
|
+
await (0, assertions_1.assertNotFound)(response);
|
|
123
|
+
});
|
|
124
|
+
(0, api_1.test)('rejects a lookup for an absent user', async ({ api }) => {
|
|
125
|
+
const response = await api.get('/users/{username}', {
|
|
126
|
+
params: { username: (0, bpmn_1.uniqueId)('absent-user') },
|
|
127
|
+
tenant: 'none',
|
|
128
|
+
});
|
|
129
|
+
await (0, assertions_1.assertNotFound)(response);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
// Outside the capability gate above: the security filter chain answers before
|
|
133
|
+
// the OIDC endpoint filter does, so an unauthenticated call is rejected with
|
|
134
|
+
// 401 whether or not the cluster serves the Users API. That holds in both
|
|
135
|
+
// modes and is worth asserting in both.
|
|
136
|
+
api_1.test.describe('Users, unauthenticated', () => {
|
|
137
|
+
(0, api_1.test)('rejects an unauthenticated user creation', async ({ api }) => {
|
|
138
|
+
api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
|
|
139
|
+
? ''
|
|
140
|
+
: capabilities_1.unauthenticatedAccessAllowed);
|
|
141
|
+
const response = await api.post('/users', {
|
|
142
|
+
body: (0, beans_1.newUser)(),
|
|
143
|
+
tenant: 'none',
|
|
144
|
+
authorization: null,
|
|
145
|
+
});
|
|
146
|
+
await (0, assertions_1.assertUnauthorized)(response);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|