@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.
- package/dist/fixtures/api.d.ts +15 -0
- package/dist/fixtures/api.js +27 -0
- package/dist/pages/SM-8.10/ModelerCreatePage.js +5 -4
- 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,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Assertion vocabulary for REST v2 responses.
|
|
4
|
+
*
|
|
5
|
+
* Each helper asserts the status *and* the problem body the API documents for
|
|
6
|
+
* it, so a response that is right by accident fails. The legacy shell suite
|
|
7
|
+
* asserted status codes only, and accepted lists such as `200 201 403` that
|
|
8
|
+
* passed whether the call succeeded or was rejected outright.
|
|
9
|
+
*
|
|
10
|
+
* Where a status genuinely varies — across versions, or because an operation is
|
|
11
|
+
* synchronous on one path and accepted-for-later on another — use
|
|
12
|
+
* `assertStatusIn`, which requires the reason in writing at the call site.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.assertPaginated = exports.assertBadRequest = exports.assertConflict = exports.assertNotFound = exports.assertForbidden = exports.assertUnauthorized = exports.assertNoContent = exports.assertCreated = exports.assertOk = exports.assertEqualsForKeys = exports.assertRequiredFields = exports.assertStatusIn = exports.assertStatus = exports.PAGINATED_FIELDS = void 0;
|
|
16
|
+
const test_1 = require("@playwright/test");
|
|
17
|
+
/** Fields every RFC 7807 problem body from the API carries. */
|
|
18
|
+
const PROBLEM_FIELDS = ['title', 'detail'];
|
|
19
|
+
/** Fields every paginated search response carries. */
|
|
20
|
+
exports.PAGINATED_FIELDS = ['items', 'page'];
|
|
21
|
+
async function describe(response) {
|
|
22
|
+
const body = await response.text().catch(() => '<unreadable body>');
|
|
23
|
+
return `${response.url()} -> ${response.status()}\n${body}`;
|
|
24
|
+
}
|
|
25
|
+
async function assertStatus(response, expected) {
|
|
26
|
+
if (response.status() !== expected) {
|
|
27
|
+
throw new Error(`Expected status ${expected}, got ${response.status()}.\n${await describe(response)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.assertStatus = assertStatus;
|
|
31
|
+
/**
|
|
32
|
+
* Accepts any of `expected`. `reason` documents why the endpoint may answer
|
|
33
|
+
* with more than one status and is printed on failure — it is required so that
|
|
34
|
+
* widening an assertion is a deliberate, reviewable act.
|
|
35
|
+
*/
|
|
36
|
+
async function assertStatusIn(response, expected, reason) {
|
|
37
|
+
if (!expected.includes(response.status())) {
|
|
38
|
+
throw new Error(`Expected one of [${expected.join(', ')}] (${reason}), got ${response.status()}.\n${await describe(response)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.assertStatusIn = assertStatusIn;
|
|
42
|
+
function assertRequiredFields(payload, required) {
|
|
43
|
+
(0, test_1.expect)(payload, 'response body should be an object').toBeTruthy();
|
|
44
|
+
const record = payload;
|
|
45
|
+
const missing = required.filter((field) => !Object.prototype.hasOwnProperty.call(record, field) ||
|
|
46
|
+
record[field] === undefined);
|
|
47
|
+
if (missing.length > 0) {
|
|
48
|
+
throw new Error(`Response is missing required field(s): ${missing.join(', ')}\n` +
|
|
49
|
+
`Body: ${JSON.stringify(payload, null, 2)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.assertRequiredFields = assertRequiredFields;
|
|
53
|
+
function assertEqualsForKeys(payload, expected, keys) {
|
|
54
|
+
const record = payload;
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
(0, test_1.expect)(record?.[key], `field "${key}"`).toEqual(expected[key]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.assertEqualsForKeys = assertEqualsForKeys;
|
|
60
|
+
async function assertProblem(response, status, title, detail) {
|
|
61
|
+
await assertStatus(response, status);
|
|
62
|
+
const json = await response.json();
|
|
63
|
+
assertRequiredFields(json, PROBLEM_FIELDS);
|
|
64
|
+
if (typeof title === 'string') {
|
|
65
|
+
(0, test_1.expect)(json.title, `problem title for ${response.url()}`).toBe(title);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
(0, test_1.expect)(String(json.title)).toMatch(title);
|
|
69
|
+
}
|
|
70
|
+
if (detail !== undefined) {
|
|
71
|
+
(0, test_1.expect)(String(json.detail)).toMatch(typeof detail === 'string'
|
|
72
|
+
? new RegExp(detail.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')
|
|
73
|
+
: detail);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function assertOk(response) {
|
|
77
|
+
await assertStatus(response, 200);
|
|
78
|
+
return response.json();
|
|
79
|
+
}
|
|
80
|
+
exports.assertOk = assertOk;
|
|
81
|
+
async function assertCreated(response) {
|
|
82
|
+
await assertStatus(response, 201);
|
|
83
|
+
return response.json();
|
|
84
|
+
}
|
|
85
|
+
exports.assertCreated = assertCreated;
|
|
86
|
+
async function assertNoContent(response) {
|
|
87
|
+
await assertStatus(response, 204);
|
|
88
|
+
}
|
|
89
|
+
exports.assertNoContent = assertNoContent;
|
|
90
|
+
async function assertUnauthorized(response) {
|
|
91
|
+
await assertStatus(response, 401);
|
|
92
|
+
const json = await response.json().catch(() => undefined);
|
|
93
|
+
// 401 may be produced by the ingress or the security filter chain before the
|
|
94
|
+
// API's problem-body writer runs, so only assert the body when there is one.
|
|
95
|
+
if (json) {
|
|
96
|
+
(0, test_1.expect)(String(json.title)).toMatch(/unauthorized/i);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.assertUnauthorized = assertUnauthorized;
|
|
100
|
+
/**
|
|
101
|
+
* A 403 does not carry one fixed problem title. The authorization layer sends
|
|
102
|
+
* `FORBIDDEN`, while the filter that closes `/v2/users/**` on an OIDC cluster
|
|
103
|
+
* sends `Access issue` — both are the API refusing the call. The status and
|
|
104
|
+
* the detail carry the meaning, so the title is matched loosely and the
|
|
105
|
+
* caller pins the specific refusal through `detail`.
|
|
106
|
+
*/
|
|
107
|
+
async function assertForbidden(response, detail) {
|
|
108
|
+
await assertProblem(response, 403, /forbidden|access issue/i, detail);
|
|
109
|
+
}
|
|
110
|
+
exports.assertForbidden = assertForbidden;
|
|
111
|
+
async function assertNotFound(response, detail) {
|
|
112
|
+
await assertProblem(response, 404, /not[_ ]?found/i, detail);
|
|
113
|
+
}
|
|
114
|
+
exports.assertNotFound = assertNotFound;
|
|
115
|
+
async function assertConflict(response, detail) {
|
|
116
|
+
await assertProblem(response, 409, /already[_ ]exists|invalid[_ ]state/i, detail);
|
|
117
|
+
}
|
|
118
|
+
exports.assertConflict = assertConflict;
|
|
119
|
+
async function assertBadRequest(response, detail, title = /invalid[_ ]argument|bad request/i) {
|
|
120
|
+
await assertProblem(response, 400, title, detail);
|
|
121
|
+
}
|
|
122
|
+
exports.assertBadRequest = assertBadRequest;
|
|
123
|
+
async function assertPaginated(response, options = {}) {
|
|
124
|
+
await assertStatus(response, 200);
|
|
125
|
+
const json = await response.json();
|
|
126
|
+
assertRequiredFields(json, exports.PAGINATED_FIELDS);
|
|
127
|
+
(0, test_1.expect)(Array.isArray(json.items), 'items should be an array').toBe(true);
|
|
128
|
+
if (options.minItems !== undefined) {
|
|
129
|
+
(0, test_1.expect)(json.items.length).toBeGreaterThanOrEqual(options.minItems);
|
|
130
|
+
}
|
|
131
|
+
if (options.exactItems !== undefined) {
|
|
132
|
+
(0, test_1.expect)(json.items.length).toBe(options.exactItems);
|
|
133
|
+
}
|
|
134
|
+
if (options.minTotalItems !== undefined) {
|
|
135
|
+
(0, test_1.expect)(json.page.totalItems).toBeGreaterThanOrEqual(options.minTotalItems);
|
|
136
|
+
}
|
|
137
|
+
if (options.exactTotalItems !== undefined) {
|
|
138
|
+
(0, test_1.expect)(json.page.totalItems).toBe(options.exactTotalItems);
|
|
139
|
+
}
|
|
140
|
+
return json;
|
|
141
|
+
}
|
|
142
|
+
exports.assertPaginated = assertPaginated;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication strategies for the Orchestration Cluster REST v2 API.
|
|
3
|
+
*
|
|
4
|
+
* The orchestration-cluster suite in `camunda/camunda` hardcodes Basic auth, so
|
|
5
|
+
* it cannot drive an OIDC-secured cluster. Helm-upgrade runs are OIDC, which is
|
|
6
|
+
* why this layer exists: it resolves an `Authorization` header from whichever
|
|
7
|
+
* strategy the environment selects, and refreshes bearer tokens before they
|
|
8
|
+
* expire so a long suite does not start 401-ing part way through.
|
|
9
|
+
*/
|
|
10
|
+
import type { APIRequestContext } from '@playwright/test';
|
|
11
|
+
import { type ApiConfig } from './config';
|
|
12
|
+
/**
|
|
13
|
+
* The `Authorization` header for the suite's principal, refreshed when stale.
|
|
14
|
+
* Throws rather than returning an unauthenticated header, so a broken token
|
|
15
|
+
* endpoint surfaces as a setup error instead of 300 misleading 401 assertions.
|
|
16
|
+
*/
|
|
17
|
+
export declare function authHeader(request: APIRequestContext, config?: ApiConfig): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* A bearer header for a different OAuth2 client, used by the authorization
|
|
20
|
+
* enforcement tests to act as an under-privileged principal. Returns undefined
|
|
21
|
+
* when the client cannot authenticate, which those tests assert on directly.
|
|
22
|
+
*/
|
|
23
|
+
export declare function clientAuthHeader(request: APIRequestContext, clientId: string, clientSecret: string, config?: ApiConfig): Promise<string | undefined>;
|
|
24
|
+
/** A bearer header obtained via the password grant, for per-user assertions. */
|
|
25
|
+
export declare function userAuthHeader(request: APIRequestContext, username: string, password: string, config?: ApiConfig): Promise<string | undefined>;
|
|
26
|
+
/** Drops cached tokens. Used by tests that deliberately invalidate a principal. */
|
|
27
|
+
export declare function clearTokenCache(): void;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Authentication strategies for the Orchestration Cluster REST v2 API.
|
|
4
|
+
*
|
|
5
|
+
* The orchestration-cluster suite in `camunda/camunda` hardcodes Basic auth, so
|
|
6
|
+
* it cannot drive an OIDC-secured cluster. Helm-upgrade runs are OIDC, which is
|
|
7
|
+
* why this layer exists: it resolves an `Authorization` header from whichever
|
|
8
|
+
* strategy the environment selects, and refreshes bearer tokens before they
|
|
9
|
+
* expire so a long suite does not start 401-ing part way through.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.clearTokenCache = exports.userAuthHeader = exports.clientAuthHeader = exports.authHeader = void 0;
|
|
13
|
+
const config_1 = require("./config");
|
|
14
|
+
/**
|
|
15
|
+
* Re-fetch this long before the server-stated expiry. A request issued at the
|
|
16
|
+
* very edge of a token's lifetime can still arrive expired, and the suite runs
|
|
17
|
+
* long enough for that to be a routine occurrence rather than a rare race.
|
|
18
|
+
*/
|
|
19
|
+
const EXPIRY_MARGIN_MS = 60000;
|
|
20
|
+
/** Fallback lifetime when the token endpoint omits `expires_in`. */
|
|
21
|
+
const DEFAULT_TOKEN_TTL_MS = 300000;
|
|
22
|
+
const tokenCache = new Map();
|
|
23
|
+
function cacheKey(clientId, subject = '') {
|
|
24
|
+
return `${clientId}::${subject}`;
|
|
25
|
+
}
|
|
26
|
+
function basicHeader(username, password) {
|
|
27
|
+
return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
28
|
+
}
|
|
29
|
+
async function fetchToken(request, tokenUrl, form) {
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await request.post(tokenUrl, {
|
|
33
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
34
|
+
form,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
// An unreachable token endpoint is the single most likely reason a whole
|
|
39
|
+
// run fails: on a Helm cluster the identity provider may not be serving
|
|
40
|
+
// yet. Reported raw, it surfaces as a Playwright stack inside auth.ts on
|
|
41
|
+
// every one of the suite's tests, which says nothing about the cause.
|
|
42
|
+
console.error(`Token endpoint ${tokenUrl} is unreachable: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (!response.ok()) {
|
|
46
|
+
const body = await response.text().catch(() => '<no-body>');
|
|
47
|
+
console.error(`Token request to ${tokenUrl} failed with ${response.status()}: ${body}`);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const json = (await response.json());
|
|
51
|
+
if (!json.access_token) {
|
|
52
|
+
console.error(`Token response from ${tokenUrl} carried no access_token`);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const ttl = json.expires_in ? json.expires_in * 1000 : DEFAULT_TOKEN_TTL_MS;
|
|
56
|
+
return {
|
|
57
|
+
header: `Bearer ${json.access_token}`,
|
|
58
|
+
expiresAt: Date.now() + Math.max(ttl - EXPIRY_MARGIN_MS, 0),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function clientCredentialsForm(oauth, clientId, clientSecret) {
|
|
62
|
+
const form = {
|
|
63
|
+
grant_type: 'client_credentials',
|
|
64
|
+
client_id: clientId,
|
|
65
|
+
client_secret: clientSecret,
|
|
66
|
+
};
|
|
67
|
+
if (oauth.audience)
|
|
68
|
+
form.audience = oauth.audience;
|
|
69
|
+
if (oauth.scope)
|
|
70
|
+
form.scope = oauth.scope;
|
|
71
|
+
return form;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The `Authorization` header for the suite's principal, refreshed when stale.
|
|
75
|
+
* Throws rather than returning an unauthenticated header, so a broken token
|
|
76
|
+
* endpoint surfaces as a setup error instead of 300 misleading 401 assertions.
|
|
77
|
+
*/
|
|
78
|
+
async function authHeader(request, config = config_1.apiConfig) {
|
|
79
|
+
if (config.authMethod === 'basic') {
|
|
80
|
+
return basicHeader(config.basic.username, config.basic.password);
|
|
81
|
+
}
|
|
82
|
+
const key = cacheKey(config.oauth.clientId);
|
|
83
|
+
const cached = tokenCache.get(key);
|
|
84
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
85
|
+
return cached.header;
|
|
86
|
+
}
|
|
87
|
+
const token = await fetchToken(request, config.oauth.tokenUrl, clientCredentialsForm(config.oauth, config.oauth.clientId, config.oauth.clientSecret));
|
|
88
|
+
if (!token) {
|
|
89
|
+
throw new Error(`Could not obtain an OAuth2 token for client "${config.oauth.clientId}" from ${config.oauth.tokenUrl}`);
|
|
90
|
+
}
|
|
91
|
+
tokenCache.set(key, token);
|
|
92
|
+
return token.header;
|
|
93
|
+
}
|
|
94
|
+
exports.authHeader = authHeader;
|
|
95
|
+
/**
|
|
96
|
+
* A bearer header for a different OAuth2 client, used by the authorization
|
|
97
|
+
* enforcement tests to act as an under-privileged principal. Returns undefined
|
|
98
|
+
* when the client cannot authenticate, which those tests assert on directly.
|
|
99
|
+
*/
|
|
100
|
+
async function clientAuthHeader(request, clientId, clientSecret, config = config_1.apiConfig) {
|
|
101
|
+
const key = cacheKey(clientId);
|
|
102
|
+
const cached = tokenCache.get(key);
|
|
103
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
104
|
+
return cached.header;
|
|
105
|
+
}
|
|
106
|
+
const token = await fetchToken(request, config.oauth.tokenUrl, clientCredentialsForm(config.oauth, clientId, clientSecret));
|
|
107
|
+
if (!token)
|
|
108
|
+
return undefined;
|
|
109
|
+
tokenCache.set(key, token);
|
|
110
|
+
return token.header;
|
|
111
|
+
}
|
|
112
|
+
exports.clientAuthHeader = clientAuthHeader;
|
|
113
|
+
/** A bearer header obtained via the password grant, for per-user assertions. */
|
|
114
|
+
async function userAuthHeader(request, username, password, config = config_1.apiConfig) {
|
|
115
|
+
const key = cacheKey(config.oauth.clientId, username);
|
|
116
|
+
const cached = tokenCache.get(key);
|
|
117
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
118
|
+
return cached.header;
|
|
119
|
+
}
|
|
120
|
+
const token = await fetchToken(request, config.oauth.tokenUrl, {
|
|
121
|
+
grant_type: 'password',
|
|
122
|
+
client_id: config.oauth.clientId,
|
|
123
|
+
client_secret: config.oauth.clientSecret,
|
|
124
|
+
username,
|
|
125
|
+
password,
|
|
126
|
+
});
|
|
127
|
+
if (!token)
|
|
128
|
+
return undefined;
|
|
129
|
+
tokenCache.set(key, token);
|
|
130
|
+
return token.header;
|
|
131
|
+
}
|
|
132
|
+
exports.userAuthHeader = userAuthHeader;
|
|
133
|
+
/** Drops cached tokens. Used by tests that deliberately invalidate a principal. */
|
|
134
|
+
function clearTokenCache() {
|
|
135
|
+
tokenCache.clear();
|
|
136
|
+
}
|
|
137
|
+
exports.clearTokenCache = clearTokenCache;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request bodies for identity resources.
|
|
3
|
+
*
|
|
4
|
+
* Every factory randomises its identifier. The shell suite seeded ids from
|
|
5
|
+
* `$(date +%s)-$$`, which collides between workers started in the same second
|
|
6
|
+
* and left tests fighting over the same role or group.
|
|
7
|
+
*/
|
|
8
|
+
export declare const roleRequiredFields: readonly ["roleId", "name", "description"];
|
|
9
|
+
export declare const groupRequiredFields: readonly ["groupId", "name"];
|
|
10
|
+
export declare const tenantRequiredFields: readonly ["tenantId", "name"];
|
|
11
|
+
export declare const userRequiredFields: readonly ["username", "name", "email"];
|
|
12
|
+
export declare const mappingRuleRequiredFields: readonly ["mappingRuleId", "claimName", "claimValue", "name"];
|
|
13
|
+
export declare function newRole(overrides?: Record<string, unknown>): {
|
|
14
|
+
roleId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function newGroup(overrides?: Record<string, unknown>): {
|
|
19
|
+
groupId: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function newTenant(overrides?: Record<string, unknown>): {
|
|
24
|
+
tenantId: string;
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function newUser(overrides?: Record<string, unknown>): {
|
|
29
|
+
username: string;
|
|
30
|
+
name: string;
|
|
31
|
+
email: string;
|
|
32
|
+
password: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function newMappingRule(overrides?: Record<string, unknown>): {
|
|
35
|
+
mappingRuleId: string;
|
|
36
|
+
claimName: string;
|
|
37
|
+
claimValue: string;
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
export declare function newAuthorization(overrides?: Record<string, unknown>): {
|
|
41
|
+
ownerId: string;
|
|
42
|
+
ownerType: string;
|
|
43
|
+
resourceId: string;
|
|
44
|
+
resourceType: string;
|
|
45
|
+
permissionTypes: string[];
|
|
46
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Request bodies for identity resources.
|
|
4
|
+
*
|
|
5
|
+
* Every factory randomises its identifier. The shell suite seeded ids from
|
|
6
|
+
* `$(date +%s)-$$`, which collides between workers started in the same second
|
|
7
|
+
* and left tests fighting over the same role or group.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.newAuthorization = exports.newMappingRule = exports.newUser = exports.newTenant = exports.newGroup = exports.newRole = exports.mappingRuleRequiredFields = exports.userRequiredFields = exports.tenantRequiredFields = exports.groupRequiredFields = exports.roleRequiredFields = void 0;
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
function suffix() {
|
|
13
|
+
return (0, node_crypto_1.randomUUID)().slice(0, 8);
|
|
14
|
+
}
|
|
15
|
+
exports.roleRequiredFields = ['roleId', 'name', 'description'];
|
|
16
|
+
exports.groupRequiredFields = ['groupId', 'name'];
|
|
17
|
+
exports.tenantRequiredFields = ['tenantId', 'name'];
|
|
18
|
+
exports.userRequiredFields = ['username', 'name', 'email'];
|
|
19
|
+
exports.mappingRuleRequiredFields = [
|
|
20
|
+
'mappingRuleId',
|
|
21
|
+
'claimName',
|
|
22
|
+
'claimValue',
|
|
23
|
+
'name',
|
|
24
|
+
];
|
|
25
|
+
function newRole(overrides = {}) {
|
|
26
|
+
const id = `role-${suffix()}`;
|
|
27
|
+
return {
|
|
28
|
+
roleId: id,
|
|
29
|
+
name: `Role ${id}`,
|
|
30
|
+
description: `Created by the REST v2 API suite`,
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
exports.newRole = newRole;
|
|
35
|
+
function newGroup(overrides = {}) {
|
|
36
|
+
const id = `group-${suffix()}`;
|
|
37
|
+
return {
|
|
38
|
+
groupId: id,
|
|
39
|
+
name: `Group ${id}`,
|
|
40
|
+
description: `Created by the REST v2 API suite`,
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
exports.newGroup = newGroup;
|
|
45
|
+
function newTenant(overrides = {}) {
|
|
46
|
+
const id = `tenant-${suffix()}`;
|
|
47
|
+
return {
|
|
48
|
+
tenantId: id,
|
|
49
|
+
name: `Tenant ${id}`,
|
|
50
|
+
description: `Created by the REST v2 API suite`,
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
exports.newTenant = newTenant;
|
|
55
|
+
function newUser(overrides = {}) {
|
|
56
|
+
const id = `user-${suffix()}`;
|
|
57
|
+
return {
|
|
58
|
+
username: id,
|
|
59
|
+
name: `User ${id}`,
|
|
60
|
+
email: `${id}@example.com`,
|
|
61
|
+
password: 'SecurePass123!',
|
|
62
|
+
...overrides,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
exports.newUser = newUser;
|
|
66
|
+
function newMappingRule(overrides = {}) {
|
|
67
|
+
const id = `rule-${suffix()}`;
|
|
68
|
+
return {
|
|
69
|
+
mappingRuleId: id,
|
|
70
|
+
claimName: `claim-${suffix()}`,
|
|
71
|
+
claimValue: `value-${suffix()}`,
|
|
72
|
+
name: `Mapping rule ${id}`,
|
|
73
|
+
...overrides,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
exports.newMappingRule = newMappingRule;
|
|
77
|
+
function newAuthorization(overrides = {}) {
|
|
78
|
+
return {
|
|
79
|
+
ownerId: `owner-${suffix()}`,
|
|
80
|
+
ownerType: 'GROUP',
|
|
81
|
+
resourceId: '*',
|
|
82
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
83
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
84
|
+
...overrides,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
exports.newAuthorization = newAuthorization;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal BPMN/DMN builders for API-level tests.
|
|
3
|
+
*
|
|
4
|
+
* The legacy shell suite pasted 18 near-identical BPMN documents inline as
|
|
5
|
+
* heredocs, several of them interpolating a process id. Building the XML gives
|
|
6
|
+
* every test a unique process id without a new file per shape, which is what
|
|
7
|
+
* the suite's isolation rules require — reused ids collide across parallel
|
|
8
|
+
* workers and produce cross-test flakiness.
|
|
9
|
+
*
|
|
10
|
+
* Only the shapes the API tests need are modelled. Anything requiring real
|
|
11
|
+
* diagram semantics belongs in `resources/` as a checked-in file.
|
|
12
|
+
*/
|
|
13
|
+
export interface ProcessElement {
|
|
14
|
+
xml: string;
|
|
15
|
+
id: string;
|
|
16
|
+
/**
|
|
17
|
+
* Fragments that must sit beside `<bpmn:process>` rather than inside it —
|
|
18
|
+
* `<bpmn:message>` is the only one the API tests need.
|
|
19
|
+
*/
|
|
20
|
+
definitionsFragment?: string;
|
|
21
|
+
}
|
|
22
|
+
/** A process/decision id that cannot collide with another worker's. */
|
|
23
|
+
export declare function uniqueId(prefix: string): string;
|
|
24
|
+
export declare const element: {
|
|
25
|
+
serviceTask(jobType: string, id?: string): ProcessElement;
|
|
26
|
+
userTask(id?: string, formId?: string): ProcessElement;
|
|
27
|
+
messageCatch(messageName: string, correlationKey?: string, id?: string): ProcessElement;
|
|
28
|
+
businessRuleTask(decisionId: string, id?: string): ProcessElement;
|
|
29
|
+
callActivity(calledProcessId: string, id?: string): ProcessElement;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A single-path process: start event -> the given element -> end event.
|
|
33
|
+
* No DI is emitted; the engine does not require it and no test reads it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function linearProcess(processId: string, body: ProcessElement, options?: {
|
|
36
|
+
name?: string;
|
|
37
|
+
}): string;
|
|
38
|
+
/** A process whose only content is a start and end event. */
|
|
39
|
+
export declare function emptyProcess(processId: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* A process whose start event links a deployed form, so that
|
|
42
|
+
* `GET /v2/process-definitions/{key}/form` has something to return. The form
|
|
43
|
+
* must already be deployed (or ride in the same deployment), otherwise the
|
|
44
|
+
* broker rejects the reference.
|
|
45
|
+
*/
|
|
46
|
+
export declare function startFormProcess(processId: string, formId: string): string;
|
|
47
|
+
/** A one-rule DMN decision that echoes its input, for evaluation tests. */
|
|
48
|
+
export declare function simpleDecision(decisionId: string, definitionsId?: string): string;
|
|
49
|
+
/** A minimal Camunda form with two text fields, for form submission tests. */
|
|
50
|
+
export declare function simpleForm(formId: string): string;
|