@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.
Files changed (85) hide show
  1. package/dist/fixtures/api.d.ts +15 -0
  2. package/dist/fixtures/api.js +27 -0
  3. package/dist/pages/SM-8.10/NavigationPage.d.ts +3 -2
  4. package/dist/pages/SM-8.10/NavigationPage.js +24 -34
  5. package/dist/pages/SM-8.10/ProcessApplicationPage.js +7 -0
  6. package/dist/tests/api/v2/authorization/authorization-api-tests.spec.d.ts +1 -0
  7. package/dist/tests/api/v2/authorization/authorization-api-tests.spec.js +247 -0
  8. package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.d.ts +18 -0
  9. package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.js +208 -0
  10. package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.d.ts +1 -0
  11. package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.js +264 -0
  12. package/dist/tests/api/v2/cluster/cluster-api-tests.spec.d.ts +1 -0
  13. package/dist/tests/api/v2/cluster/cluster-api-tests.spec.js +86 -0
  14. package/dist/tests/api/v2/decision/decision-api-tests.spec.d.ts +1 -0
  15. package/dist/tests/api/v2/decision/decision-api-tests.spec.js +233 -0
  16. package/dist/tests/api/v2/deployment/deployment-api-tests.spec.d.ts +1 -0
  17. package/dist/tests/api/v2/deployment/deployment-api-tests.spec.js +147 -0
  18. package/dist/tests/api/v2/document/document-api-tests.spec.d.ts +9 -0
  19. package/dist/tests/api/v2/document/document-api-tests.spec.js +111 -0
  20. package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.d.ts +1 -0
  21. package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.js +127 -0
  22. package/dist/tests/api/v2/flows/end-to-end-flows.spec.d.ts +9 -0
  23. package/dist/tests/api/v2/flows/end-to-end-flows.spec.js +204 -0
  24. package/dist/tests/api/v2/form/form-api-tests.spec.d.ts +1 -0
  25. package/dist/tests/api/v2/form/form-api-tests.spec.js +88 -0
  26. package/dist/tests/api/v2/identity/group-api-tests.spec.d.ts +1 -0
  27. package/dist/tests/api/v2/identity/group-api-tests.spec.js +172 -0
  28. package/dist/tests/api/v2/identity/membership-api-tests.spec.d.ts +10 -0
  29. package/dist/tests/api/v2/identity/membership-api-tests.spec.js +164 -0
  30. package/dist/tests/api/v2/identity/role-api-tests.spec.d.ts +1 -0
  31. package/dist/tests/api/v2/identity/role-api-tests.spec.js +212 -0
  32. package/dist/tests/api/v2/identity/tenant-api-tests.spec.d.ts +1 -0
  33. package/dist/tests/api/v2/identity/tenant-api-tests.spec.js +319 -0
  34. package/dist/tests/api/v2/identity/user-api-tests.spec.d.ts +1 -0
  35. package/dist/tests/api/v2/identity/user-api-tests.spec.js +148 -0
  36. package/dist/tests/api/v2/job/job-api-tests.spec.d.ts +1 -0
  37. package/dist/tests/api/v2/job/job-api-tests.spec.js +260 -0
  38. package/dist/tests/api/v2/message/message-api-tests.spec.d.ts +1 -0
  39. package/dist/tests/api/v2/message/message-api-tests.spec.js +219 -0
  40. package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.d.ts +1 -0
  41. package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.js +118 -0
  42. package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.d.ts +1 -0
  43. package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.js +335 -0
  44. package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.d.ts +1 -0
  45. package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.js +123 -0
  46. package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.d.ts +1 -0
  47. package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.js +174 -0
  48. package/dist/tests/api/v2/system/clock-api-tests.spec.d.ts +19 -0
  49. package/dist/tests/api/v2/system/clock-api-tests.spec.js +62 -0
  50. package/dist/tests/api/v2/system/system-api-tests.spec.d.ts +1 -0
  51. package/dist/tests/api/v2/system/system-api-tests.spec.js +183 -0
  52. package/dist/tests/api/v2/user-task/user-task-api-tests.spec.d.ts +1 -0
  53. package/dist/tests/api/v2/user-task/user-task-api-tests.spec.js +258 -0
  54. package/dist/utils/api/assertions.d.ts +50 -0
  55. package/dist/utils/api/assertions.js +142 -0
  56. package/dist/utils/api/auth.d.ts +27 -0
  57. package/dist/utils/api/auth.js +137 -0
  58. package/dist/utils/api/beans.d.ts +46 -0
  59. package/dist/utils/api/beans.js +87 -0
  60. package/dist/utils/api/bpmn.d.ts +50 -0
  61. package/dist/utils/api/bpmn.js +217 -0
  62. package/dist/utils/api/capabilities.d.ts +90 -0
  63. package/dist/utils/api/capabilities.js +198 -0
  64. package/dist/utils/api/cleanup.d.ts +24 -0
  65. package/dist/utils/api/cleanup.js +120 -0
  66. package/dist/utils/api/config.d.ts +103 -0
  67. package/dist/utils/api/config.js +62 -0
  68. package/dist/utils/api/deployments.d.ts +45 -0
  69. package/dist/utils/api/deployments.js +120 -0
  70. package/dist/utils/api/eventually.d.ts +36 -0
  71. package/dist/utils/api/eventually.js +54 -0
  72. package/dist/utils/api/http.d.ts +56 -0
  73. package/dist/utils/api/http.js +244 -0
  74. package/dist/utils/api/identity.d.ts +29 -0
  75. package/dist/utils/api/identity.js +43 -0
  76. package/dist/utils/api/keys.d.ts +37 -0
  77. package/dist/utils/api/keys.js +40 -0
  78. package/dist/utils/api/paths.d.ts +25 -0
  79. package/dist/utils/api/paths.js +167 -0
  80. package/dist/utils/api/tenancy.d.ts +31 -0
  81. package/dist/utils/api/tenancy.js +65 -0
  82. package/package.json +1 -1
  83. package/playwright.config.ts +14 -2
  84. package/dist/resources/test-api-v2-complete.sh +0 -12246
  85. 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, beforeBannerCheck?: () => Promise<void>): Promise<void>;
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 dismissOnboardingDialog;
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, beforeBannerCheck) {
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, beforeBannerCheck) {
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, beforeBannerCheck);
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.goTo(MODELER_CONTEXT_PATH, this.modelerPageBanner, sleepTimeout, credentials, 8, () => this.dismissOnboardingDialog());
265
+ await this.suppressOnboardingTour();
266
+ await this.goTo(MODELER_CONTEXT_PATH, this.modelerPageBanner, sleepTimeout, credentials, 8);
268
267
  }
269
- // After the Camunda Hub rebrand a first visit to Modeler home greets the user
270
- // with a multi-step "Welcome to Camunda Hub" onboarding dialog. It mounts over
271
- // the home and marks the app shell inert, so the "Camunda Modeler home" banner
272
- // link leaves the accessibility tree until the dialog is dismissed -- gating on
273
- // the banner alone then loops until the whole login budget is spent. Close it
274
- // through the dialog's own control before each banner check so the banner can
275
- // resolve; only unambiguous dismissals are matched (never "Cancel", which is a
276
- // form action on the dialogs other flows open on purpose).
277
- async dismissOnboardingDialog() {
278
- // On the Design System shell the onboarding is a multi-step wizard: its
279
- // footer button reads "Next" (an advance, not a dismissal) and its only
280
- // single-click close is the header control -- a ghost icon button
281
- // (data-slot="dialog-close") wrapping an aria-hidden "x" svg, so it exposes
282
- // no accessible name and getByRole({name}) cannot reach it. Target that
283
- // control by its stable data-slot; keep the named-button fallback for the
284
- // Carbon announcement modals other flows meet ("Got it"/"Close"/"Dismiss",
285
- // never "Cancel", which is a form action). Scope to the visible dialog: the
286
- // home keeps several hidden role="dialog" containers mounted.
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,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 {};