@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,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright fixture for the Orchestration Cluster REST v2 API suite.
|
|
3
|
+
*
|
|
4
|
+
* Provides an authenticated `api` client and a `resources` tracker whose
|
|
5
|
+
* contents are deleted after the worker finishes, so a spec never has to write
|
|
6
|
+
* its own teardown loop.
|
|
7
|
+
*/
|
|
8
|
+
import { ApiClient } from '../utils/api/http';
|
|
9
|
+
import { ResourceTracker } from '../utils/api/cleanup';
|
|
10
|
+
interface ApiFixtures {
|
|
11
|
+
api: ApiClient;
|
|
12
|
+
resources: ResourceTracker;
|
|
13
|
+
}
|
|
14
|
+
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & ApiFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
15
|
+
export { expect } from '@playwright/test';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Playwright fixture for the Orchestration Cluster REST v2 API suite.
|
|
4
|
+
*
|
|
5
|
+
* Provides an authenticated `api` client and a `resources` tracker whose
|
|
6
|
+
* contents are deleted after the worker finishes, so a spec never has to write
|
|
7
|
+
* its own teardown loop.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.expect = exports.test = void 0;
|
|
11
|
+
const test_1 = require("@playwright/test");
|
|
12
|
+
const http_1 = require("../utils/api/http");
|
|
13
|
+
const cleanup_1 = require("../utils/api/cleanup");
|
|
14
|
+
// Configuration is read once at module load, so specs that need it import
|
|
15
|
+
// `apiConfig` directly rather than taking it as a fixture.
|
|
16
|
+
exports.test = test_1.test.extend({
|
|
17
|
+
api: async ({ request }, use) => {
|
|
18
|
+
await use(new http_1.ApiClient(request));
|
|
19
|
+
},
|
|
20
|
+
resources: async ({ api }, use) => {
|
|
21
|
+
const tracker = new cleanup_1.ResourceTracker();
|
|
22
|
+
await use(tracker);
|
|
23
|
+
await tracker.cleanup(api);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
var test_2 = require("@playwright/test");
|
|
27
|
+
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_2.expect; } });
|
|
@@ -8,18 +8,19 @@ declare class NavigationPage {
|
|
|
8
8
|
readonly identityPageBanner: Locator;
|
|
9
9
|
readonly keyboardPageBanner: Locator;
|
|
10
10
|
readonly managementIdentityPageBanner: Locator;
|
|
11
|
+
private onboardingTourSuppressed;
|
|
11
12
|
constructor(page: Page);
|
|
12
13
|
private isGatewayErrorPage;
|
|
13
14
|
private waitForBanner;
|
|
14
15
|
goTo(url: string, banner: Locator, sleepTimeout?: number, { username, password, }?: {
|
|
15
16
|
username?: string;
|
|
16
17
|
password?: string;
|
|
17
|
-
}, maxRetries?: number
|
|
18
|
+
}, maxRetries?: number): Promise<void>;
|
|
18
19
|
goToModeler(sleepTimeout?: number, credentials?: {
|
|
19
20
|
username: string;
|
|
20
21
|
password: string;
|
|
21
22
|
}): Promise<void>;
|
|
22
|
-
private
|
|
23
|
+
private suppressOnboardingTour;
|
|
23
24
|
goToTasklist(sleepTimeout?: number, credentials?: {
|
|
24
25
|
username: string;
|
|
25
26
|
password: string;
|
|
@@ -28,6 +28,7 @@ class NavigationPage {
|
|
|
28
28
|
identityPageBanner;
|
|
29
29
|
keyboardPageBanner;
|
|
30
30
|
managementIdentityPageBanner;
|
|
31
|
+
onboardingTourSuppressed = false;
|
|
31
32
|
constructor(page) {
|
|
32
33
|
this.page = page;
|
|
33
34
|
// Operate renders the Design System header (AppHeaderV2) whenever the
|
|
@@ -112,15 +113,12 @@ class NavigationPage {
|
|
|
112
113
|
// that is exactly the reported "Login (/modeler) failed after 8 attempts".
|
|
113
114
|
// The 60s budget is unchanged: a genuinely slow shell still gets all of it,
|
|
114
115
|
// and a sustained outage still fails.
|
|
115
|
-
async waitForBanner(url, banner, submitted
|
|
116
|
+
async waitForBanner(url, banner, submitted) {
|
|
116
117
|
const totalTimeout = constants_1._1_SECOND_IN_MS * 60;
|
|
117
118
|
const deadline = Date.now() + totalTimeout;
|
|
118
119
|
let lastState = 'no banner';
|
|
119
120
|
while (Date.now() < deadline) {
|
|
120
121
|
const round = Math.min(constants_1._1_SECOND_IN_MS * 20, deadline - Date.now());
|
|
121
|
-
if (beforeBannerCheck) {
|
|
122
|
-
await beforeBannerCheck();
|
|
123
|
-
}
|
|
124
122
|
try {
|
|
125
123
|
await (0, test_1.expect)(banner).toBeVisible({ timeout: round });
|
|
126
124
|
return;
|
|
@@ -148,7 +146,7 @@ class NavigationPage {
|
|
|
148
146
|
}
|
|
149
147
|
throw new Error(`${url} did not render its banner within ${totalTimeout}ms: ${lastState}`);
|
|
150
148
|
}
|
|
151
|
-
async goTo(url, banner, sleepTimeout, { username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}, maxRetries = 8
|
|
149
|
+
async goTo(url, banner, sleepTimeout, { username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}, maxRetries = 8) {
|
|
152
150
|
const startTime = Date.now();
|
|
153
151
|
let timeout = constants_1._1_SECOND_IN_MS * 10;
|
|
154
152
|
if (url === '/modeler') {
|
|
@@ -217,7 +215,7 @@ class NavigationPage {
|
|
|
217
215
|
else {
|
|
218
216
|
submitted = await loginPage.login(username, password);
|
|
219
217
|
}
|
|
220
|
-
await this.waitForBanner(url, banner, submitted
|
|
218
|
+
await this.waitForBanner(url, banner, submitted);
|
|
221
219
|
return;
|
|
222
220
|
}
|
|
223
221
|
catch (error) {
|
|
@@ -264,35 +262,27 @@ class NavigationPage {
|
|
|
264
262
|
}
|
|
265
263
|
}
|
|
266
264
|
async goToModeler(sleepTimeout, credentials) {
|
|
267
|
-
await this.
|
|
265
|
+
await this.suppressOnboardingTour();
|
|
266
|
+
await this.goTo(MODELER_CONTEXT_PATH, this.modelerPageBanner, sleepTimeout, credentials, 8);
|
|
268
267
|
}
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const dialog = this.page.locator('[role="dialog"]:visible').first();
|
|
288
|
-
const dismiss = dialog
|
|
289
|
-
.locator('[data-slot="dialog-close"]')
|
|
290
|
-
.or(dialog.getByRole('button', { name: /^(Got it|Close|Dismiss)/i }))
|
|
291
|
-
.first();
|
|
292
|
-
await dismiss
|
|
293
|
-
.waitFor({ state: 'visible', timeout: 3000 })
|
|
294
|
-
.then(() => dismiss.click())
|
|
295
|
-
.catch(() => { });
|
|
268
|
+
// Mirrors camunda-hub@452978f: pre-seed the onboarding tour's "seen" localStorage flag
|
|
269
|
+
// (SEEN_KEY_PREFIX in .../tour-dialog/use-onboarding-seen.ts) so it never auto-opens, rather
|
|
270
|
+
// than dismissing it reactively (the two prior fixes here -- both raced the dialog's mount
|
|
271
|
+
// and lost, since the user id needed for the real key isn't known until after that same
|
|
272
|
+
// race). addInitScript avoids the race by running before any page script, matching the
|
|
273
|
+
// flag by prefix since the user id isn't known up front.
|
|
274
|
+
async suppressOnboardingTour() {
|
|
275
|
+
if (this.onboardingTourSuppressed) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.onboardingTourSuppressed = true;
|
|
279
|
+
await this.page.addInitScript((prefixPattern) => {
|
|
280
|
+
const pattern = new RegExp(prefixPattern);
|
|
281
|
+
const originalGetItem = Storage.prototype.getItem;
|
|
282
|
+
Storage.prototype.getItem = function (key) {
|
|
283
|
+
return pattern.test(key) ? 'true' : originalGetItem.call(this, key);
|
|
284
|
+
};
|
|
285
|
+
}, '^hub\\.onboarding\\..*\\.seen\\.');
|
|
296
286
|
}
|
|
297
287
|
async goToTasklist(sleepTimeout, credentials) {
|
|
298
288
|
await this.goTo(`${NORMALIZED_ORCHESTRATION_CONTEXT_PATH}/tasklist`, this.tasklistPageBanner, sleepTimeout, credentials);
|
|
@@ -156,6 +156,12 @@ class ProcessApplicationPage {
|
|
|
156
156
|
await (0, test_1.expect)(this.defineStagesDialog).toBeVisible({ timeout: 60000 });
|
|
157
157
|
await this.developmentClusterSelect.click({ timeout: 30000 });
|
|
158
158
|
const escapedClusterName = clusterName?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
159
|
+
// The listbox leads with a "No cluster" entry that unassigns the stage; the
|
|
160
|
+
// placeholder "Select a development cluster" is only the trigger's text, not an
|
|
161
|
+
// option. Excluding just the placeholder leaves "No cluster" first, so an
|
|
162
|
+
// unnamed pick binds no cluster and saves defaultDevClusterId=null -- which
|
|
163
|
+
// makes the later Deploy dialog report "Clusters not configured for stages".
|
|
164
|
+
// Exclude "No cluster" too so the fallback lands on a real cluster.
|
|
159
165
|
const option = clusterName
|
|
160
166
|
? this.page
|
|
161
167
|
.getByRole('option', { name: new RegExp(`^${escapedClusterName}`) })
|
|
@@ -163,6 +169,7 @@ class ProcessApplicationPage {
|
|
|
163
169
|
: this.page
|
|
164
170
|
.getByRole('option')
|
|
165
171
|
.filter({ hasNotText: 'Select a development cluster' })
|
|
172
|
+
.filter({ hasNotText: 'No cluster' })
|
|
166
173
|
.first();
|
|
167
174
|
await option.click({ timeout: 30000 });
|
|
168
175
|
await this.defineStagesSaveButton.click({ timeout: 30000 });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,247 @@
|
|
|
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 deployments_1 = require("../../../../utils/api/deployments");
|
|
10
|
+
const identity_1 = require("../../../../utils/api/identity");
|
|
11
|
+
const keys_1 = require("../../../../utils/api/keys");
|
|
12
|
+
const capabilities_1 = require("../../../../utils/api/capabilities");
|
|
13
|
+
async function createAuthorization(api, resources, body) {
|
|
14
|
+
const response = await api.post('/authorizations', { body, tenant: 'none' });
|
|
15
|
+
// The shell suite expected `201 409` throughout this section, so a run
|
|
16
|
+
// where every authorization already existed — or where creation kept
|
|
17
|
+
// conflicting — was indistinguishable from one where it worked.
|
|
18
|
+
const created = (await (0, assertions_1.assertCreated)(response));
|
|
19
|
+
const key = String(created.authorizationKey);
|
|
20
|
+
resources.track('authorization', key);
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
api_1.test.describe('Authorizations', () => {
|
|
24
|
+
(0, api_1.test)('creates an authorization for a group owner', async ({ api, resources, }) => {
|
|
25
|
+
const group = (0, beans_1.newGroup)();
|
|
26
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
27
|
+
resources.track('group', group.groupId);
|
|
28
|
+
const processId = (0, bpmn_1.uniqueId)('authz-process');
|
|
29
|
+
const deployed = await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job'))));
|
|
30
|
+
const key = await createAuthorization(api, resources, {
|
|
31
|
+
ownerType: 'GROUP',
|
|
32
|
+
ownerId: group.groupId,
|
|
33
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
34
|
+
resourceId: deployed.processDefinitionId,
|
|
35
|
+
permissionTypes: ['READ_PROCESS_INSTANCE'],
|
|
36
|
+
});
|
|
37
|
+
await (0, eventually_1.eventually)(async () => {
|
|
38
|
+
const response = await api.get('/authorizations/{authorizationKey}', {
|
|
39
|
+
params: { authorizationKey: key },
|
|
40
|
+
tenant: 'none',
|
|
41
|
+
});
|
|
42
|
+
const json = (await (0, assertions_1.assertOk)(response));
|
|
43
|
+
(0, assertions_1.assertRequiredFields)(json, [
|
|
44
|
+
'authorizationKey',
|
|
45
|
+
'ownerId',
|
|
46
|
+
'ownerType',
|
|
47
|
+
'resourceType',
|
|
48
|
+
'permissionTypes',
|
|
49
|
+
]);
|
|
50
|
+
(0, test_1.expect)(json.ownerId).toBe(group.groupId);
|
|
51
|
+
(0, test_1.expect)(json.permissionTypes).toEqual(['READ_PROCESS_INSTANCE']);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
(0, api_1.test)('creates an authorization carrying several permissions', async ({ api, resources, }) => {
|
|
55
|
+
const group = (0, beans_1.newGroup)();
|
|
56
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
57
|
+
resources.track('group', group.groupId);
|
|
58
|
+
const key = await createAuthorization(api, resources, {
|
|
59
|
+
ownerType: 'GROUP',
|
|
60
|
+
ownerId: group.groupId,
|
|
61
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
62
|
+
resourceId: '*',
|
|
63
|
+
permissionTypes: ['READ_PROCESS_DEFINITION', 'READ_PROCESS_INSTANCE'],
|
|
64
|
+
});
|
|
65
|
+
await (0, eventually_1.eventually)(async () => {
|
|
66
|
+
const response = await api.get('/authorizations/{authorizationKey}', {
|
|
67
|
+
params: { authorizationKey: key },
|
|
68
|
+
tenant: 'none',
|
|
69
|
+
});
|
|
70
|
+
const json = (await (0, assertions_1.assertOk)(response));
|
|
71
|
+
(0, test_1.expect)(json.permissionTypes).toEqual(test_1.expect.arrayContaining([
|
|
72
|
+
'READ_PROCESS_DEFINITION',
|
|
73
|
+
'READ_PROCESS_INSTANCE',
|
|
74
|
+
]));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
(0, api_1.test)('creates an authorization for a user owner', async ({ api, resources, }) => {
|
|
78
|
+
const username = await (0, identity_1.provisionUsername)(api, resources);
|
|
79
|
+
const key = await createAuthorization(api, resources, {
|
|
80
|
+
ownerType: 'USER',
|
|
81
|
+
ownerId: username,
|
|
82
|
+
resourceType: 'DECISION_DEFINITION',
|
|
83
|
+
resourceId: '*',
|
|
84
|
+
permissionTypes: ['READ_DECISION_DEFINITION'],
|
|
85
|
+
});
|
|
86
|
+
(0, test_1.expect)(key).toBeTruthy();
|
|
87
|
+
});
|
|
88
|
+
(0, api_1.test)('finds an authorization by owner', async ({ api, resources }) => {
|
|
89
|
+
const group = (0, beans_1.newGroup)();
|
|
90
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
91
|
+
resources.track('group', group.groupId);
|
|
92
|
+
await createAuthorization(api, resources, {
|
|
93
|
+
ownerType: 'GROUP',
|
|
94
|
+
ownerId: group.groupId,
|
|
95
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
96
|
+
resourceId: '*',
|
|
97
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
98
|
+
});
|
|
99
|
+
await (0, eventually_1.eventually)(async () => {
|
|
100
|
+
const response = await api.post('/authorizations/search', {
|
|
101
|
+
body: { filter: { ownerId: group.groupId }, page: { limit: 10 } },
|
|
102
|
+
tenant: 'none',
|
|
103
|
+
});
|
|
104
|
+
const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
|
|
105
|
+
for (const item of page.items) {
|
|
106
|
+
(0, test_1.expect)(item.ownerId).toBe(group.groupId);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
(0, api_1.test)('filters an authorization search by owner type', async ({ api, resources, }) => {
|
|
111
|
+
const group = (0, beans_1.newGroup)();
|
|
112
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
113
|
+
resources.track('group', group.groupId);
|
|
114
|
+
await createAuthorization(api, resources, {
|
|
115
|
+
ownerType: 'GROUP',
|
|
116
|
+
ownerId: group.groupId,
|
|
117
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
118
|
+
resourceId: '*',
|
|
119
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
120
|
+
});
|
|
121
|
+
await (0, eventually_1.eventually)(async () => {
|
|
122
|
+
const response = await api.post('/authorizations/search', {
|
|
123
|
+
body: { filter: { ownerType: 'GROUP' }, page: { limit: 50 } },
|
|
124
|
+
tenant: 'none',
|
|
125
|
+
});
|
|
126
|
+
const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
|
|
127
|
+
for (const item of page.items) {
|
|
128
|
+
(0, test_1.expect)(item.ownerType).toBe('GROUP');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
(0, api_1.test)('updates the permissions on an authorization', async ({ api, resources, }) => {
|
|
133
|
+
const group = (0, beans_1.newGroup)();
|
|
134
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
135
|
+
resources.track('group', group.groupId);
|
|
136
|
+
const key = await createAuthorization(api, resources, {
|
|
137
|
+
ownerType: 'GROUP',
|
|
138
|
+
ownerId: group.groupId,
|
|
139
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
140
|
+
resourceId: '*',
|
|
141
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
142
|
+
});
|
|
143
|
+
const response = await api.put('/authorizations/{authorizationKey}', {
|
|
144
|
+
params: { authorizationKey: key },
|
|
145
|
+
body: {
|
|
146
|
+
ownerType: 'GROUP',
|
|
147
|
+
ownerId: group.groupId,
|
|
148
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
149
|
+
resourceId: '*',
|
|
150
|
+
permissionTypes: ['READ_PROCESS_DEFINITION', 'UPDATE_PROCESS_INSTANCE'],
|
|
151
|
+
},
|
|
152
|
+
tenant: 'none',
|
|
153
|
+
});
|
|
154
|
+
await (0, assertions_1.assertStatusIn)(response, [204], 'the update is acknowledged');
|
|
155
|
+
await (0, eventually_1.eventually)(async () => {
|
|
156
|
+
const lookup = await api.get('/authorizations/{authorizationKey}', {
|
|
157
|
+
params: { authorizationKey: key },
|
|
158
|
+
tenant: 'none',
|
|
159
|
+
});
|
|
160
|
+
const json = (await (0, assertions_1.assertOk)(lookup));
|
|
161
|
+
(0, test_1.expect)(json.permissionTypes).toEqual(test_1.expect.arrayContaining(['UPDATE_PROCESS_INSTANCE']));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
(0, api_1.test)('deletes an authorization', async ({ api, resources }) => {
|
|
165
|
+
const group = (0, beans_1.newGroup)();
|
|
166
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
167
|
+
resources.track('group', group.groupId);
|
|
168
|
+
const key = await createAuthorization(api, resources, {
|
|
169
|
+
ownerType: 'GROUP',
|
|
170
|
+
ownerId: group.groupId,
|
|
171
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
172
|
+
resourceId: '*',
|
|
173
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
174
|
+
});
|
|
175
|
+
const response = await api.delete('/authorizations/{authorizationKey}', {
|
|
176
|
+
params: { authorizationKey: key },
|
|
177
|
+
tenant: 'none',
|
|
178
|
+
});
|
|
179
|
+
await (0, assertions_1.assertStatusIn)(response, [204], 'deletion is acknowledged');
|
|
180
|
+
resources.forget('authorization', key);
|
|
181
|
+
await (0, eventually_1.eventually)(async () => {
|
|
182
|
+
const lookup = await api.get('/authorizations/{authorizationKey}', {
|
|
183
|
+
params: { authorizationKey: key },
|
|
184
|
+
tenant: 'none',
|
|
185
|
+
});
|
|
186
|
+
await (0, assertions_1.assertNotFound)(lookup);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
(0, api_1.test)('rejects an authorization with no permission types', async ({ api, resources, }) => {
|
|
190
|
+
const group = (0, beans_1.newGroup)();
|
|
191
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
192
|
+
resources.track('group', group.groupId);
|
|
193
|
+
const response = await api.post('/authorizations', {
|
|
194
|
+
body: {
|
|
195
|
+
ownerType: 'GROUP',
|
|
196
|
+
ownerId: group.groupId,
|
|
197
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
198
|
+
resourceId: '*',
|
|
199
|
+
permissionTypes: [],
|
|
200
|
+
},
|
|
201
|
+
tenant: 'none',
|
|
202
|
+
});
|
|
203
|
+
await (0, assertions_1.assertBadRequest)(response);
|
|
204
|
+
});
|
|
205
|
+
(0, api_1.test)('rejects an authorization with an unknown resource type', async ({ api, resources, }) => {
|
|
206
|
+
const group = (0, beans_1.newGroup)();
|
|
207
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
208
|
+
resources.track('group', group.groupId);
|
|
209
|
+
const response = await api.post('/authorizations', {
|
|
210
|
+
body: {
|
|
211
|
+
ownerType: 'GROUP',
|
|
212
|
+
ownerId: group.groupId,
|
|
213
|
+
resourceType: 'NOT_A_RESOURCE_TYPE',
|
|
214
|
+
resourceId: '*',
|
|
215
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
216
|
+
},
|
|
217
|
+
tenant: 'none',
|
|
218
|
+
});
|
|
219
|
+
await (0, assertions_1.assertBadRequest)(response);
|
|
220
|
+
});
|
|
221
|
+
(0, api_1.test)('rejects a lookup for an absent authorization', async ({ api }) => {
|
|
222
|
+
const response = await api.get('/authorizations/{authorizationKey}', {
|
|
223
|
+
params: { authorizationKey: keys_1.ABSENT_KEY },
|
|
224
|
+
tenant: 'none',
|
|
225
|
+
});
|
|
226
|
+
await (0, assertions_1.assertNotFound)(response);
|
|
227
|
+
});
|
|
228
|
+
(0, api_1.test)('rejects an unauthenticated authorization search', async ({ api }) => {
|
|
229
|
+
api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
|
|
230
|
+
? ''
|
|
231
|
+
: capabilities_1.unauthenticatedAccessAllowed);
|
|
232
|
+
const response = await api.post('/authorizations/search', {
|
|
233
|
+
body: { filter: {} },
|
|
234
|
+
tenant: 'none',
|
|
235
|
+
authorization: null,
|
|
236
|
+
});
|
|
237
|
+
await (0, assertions_1.assertUnauthorized)(response);
|
|
238
|
+
});
|
|
239
|
+
(0, api_1.test)('honours the page size on an authorization search', async ({ api }) => {
|
|
240
|
+
const response = await api.post('/authorizations/search', {
|
|
241
|
+
body: { filter: {}, page: { limit: 1 } },
|
|
242
|
+
tenant: 'none',
|
|
243
|
+
});
|
|
244
|
+
const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
|
|
245
|
+
(0, test_1.expect)(page.items.length).toBe(1);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization *enforcement* — that the cluster actually refuses an
|
|
3
|
+
* unprivileged principal, not merely that the authorization API accepts writes.
|
|
4
|
+
*
|
|
5
|
+
* This is the coverage a Helm-upgrade run most needs, because role bindings are
|
|
6
|
+
* what an upgrade is most likely to lose. In the shell suite it was 2,700 lines
|
|
7
|
+
* (9506-12224) inside a single `if [[ "$TEST_CLIENT_TOKEN_VALID" ]]` block: if
|
|
8
|
+
* the token fetch failed, the entire section silently did not run and the script
|
|
9
|
+
* still exited 0.
|
|
10
|
+
*
|
|
11
|
+
* Two things change here. The gate is a hard failure rather than a silent skip,
|
|
12
|
+
* so "enforcement was never checked" can no longer look like success. And the
|
|
13
|
+
* unprivileged principal is a user this spec creates, rather than the suite's own
|
|
14
|
+
* admin client with its permissions temporarily stripped and restored — that
|
|
15
|
+
* approach mutated cluster-wide admin state mid-run, so a failure between the
|
|
16
|
+
* strip and the restore left the rest of the suite running without permissions.
|
|
17
|
+
*/
|
|
18
|
+
export {};
|