@camunda/e2e-test-suite 0.0.754 → 0.0.756
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/8.10.d.ts +2 -0
- package/dist/fixtures/8.10.js +4 -0
- package/dist/pages/8.10/AuditLogPage.d.ts +14 -0
- package/dist/pages/8.10/AuditLogPage.js +53 -0
- package/dist/pages/8.7/ModelerHomePage.d.ts +16 -9
- package/dist/pages/8.7/ModelerHomePage.js +33 -24
- package/dist/pages/8.7/UtilitiesPage.d.ts +1 -0
- package/dist/pages/8.7/UtilitiesPage.js +11 -1
- package/dist/tests/8.10/orchestration-cluster-mcp-server/api/mcp-processes-audit-log.spec.d.ts +1 -0
- package/dist/tests/8.10/orchestration-cluster-mcp-server/api/mcp-processes-audit-log.spec.js +146 -0
- package/dist/tests/8.10/test-setup.spec.js +3 -1
- package/dist/tests/8.7/rba-enabled-user-flows.spec.js +25 -0
- package/dist/tests/8.7/web-modeler-user-flows.spec.js +8 -3
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.js +7 -1
- package/package.json +1 -1
package/dist/fixtures/8.10.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { OCIdentityRolesPage } from '../pages/8.10/OCIdentityRolesPage';
|
|
|
34
34
|
import { OCIdentityAuthorizationsPage } from '../pages/8.10/OCIdentityAuthorizationsPage';
|
|
35
35
|
import { OCIdentityClusterVariablesPage } from '../pages/8.10/OCIdentityClusterVariablesPage';
|
|
36
36
|
import { ClientCredentialsDetailsPage } from '../pages/8.10/ClientCredentialsDetailsPage';
|
|
37
|
+
import { AuditLogPage } from '../pages/8.10/AuditLogPage';
|
|
37
38
|
type PlaywrightFixtures = {
|
|
38
39
|
makeAxeBuilder: () => AxeBuilder;
|
|
39
40
|
loginPage: LoginPage;
|
|
@@ -71,6 +72,7 @@ type PlaywrightFixtures = {
|
|
|
71
72
|
ocIdentityAuthorizationsPage: OCIdentityAuthorizationsPage;
|
|
72
73
|
ocIdentityClusterVariablesPage: OCIdentityClusterVariablesPage;
|
|
73
74
|
clientCredentialsDetailsPage: ClientCredentialsDetailsPage;
|
|
75
|
+
auditLogPage: AuditLogPage;
|
|
74
76
|
overrideTrackingScripts: void;
|
|
75
77
|
};
|
|
76
78
|
declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & PlaywrightFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
package/dist/fixtures/8.10.js
CHANGED
|
@@ -41,6 +41,7 @@ const OCIdentityRolesPage_1 = require("../pages/8.10/OCIdentityRolesPage");
|
|
|
41
41
|
const OCIdentityAuthorizationsPage_1 = require("../pages/8.10/OCIdentityAuthorizationsPage");
|
|
42
42
|
const OCIdentityClusterVariablesPage_1 = require("../pages/8.10/OCIdentityClusterVariablesPage");
|
|
43
43
|
const ClientCredentialsDetailsPage_1 = require("../pages/8.10/ClientCredentialsDetailsPage");
|
|
44
|
+
const AuditLogPage_1 = require("../pages/8.10/AuditLogPage");
|
|
44
45
|
const test = test_1.test.extend({
|
|
45
46
|
makeAxeBuilder: async ({ page }, use) => {
|
|
46
47
|
const makeAxeBuilder = () => new playwright_1.default({ page }).withTags([
|
|
@@ -158,6 +159,9 @@ const test = test_1.test.extend({
|
|
|
158
159
|
clientCredentialsDetailsPage: async ({ page }, use) => {
|
|
159
160
|
await use(new ClientCredentialsDetailsPage_1.ClientCredentialsDetailsPage(page));
|
|
160
161
|
},
|
|
162
|
+
auditLogPage: async ({ page }, use) => {
|
|
163
|
+
await use(new AuditLogPage_1.AuditLogPage(page));
|
|
164
|
+
},
|
|
161
165
|
overrideTrackingScripts: [
|
|
162
166
|
async ({ context }, use) => {
|
|
163
167
|
await context.route('https://cmp.osano.com/16CVvwSNKHi9t1grQ/9403708a-488b-4f3b-aea6-613825dec79f/osano.js', (route) => route.fulfill({
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
type AuditLogCategory = 'Administration' | 'Deployed resources' | 'User tasks';
|
|
3
|
+
declare class AuditLogPage {
|
|
4
|
+
readonly auditLogTab: Locator;
|
|
5
|
+
readonly updateButton: Locator;
|
|
6
|
+
readonly clientOperationsSection: Locator;
|
|
7
|
+
readonly clientCategoryCheckbox: (category: AuditLogCategory) => Locator;
|
|
8
|
+
readonly clientCategoryLabel: (category: AuditLogCategory) => Locator;
|
|
9
|
+
constructor(page: Page);
|
|
10
|
+
clickAuditLogTab(): Promise<void>;
|
|
11
|
+
private ensureChecked;
|
|
12
|
+
enableClientOperationLogging(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export { AuditLogPage };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuditLogPage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
class AuditLogPage {
|
|
6
|
+
auditLogTab;
|
|
7
|
+
updateButton;
|
|
8
|
+
clientOperationsSection;
|
|
9
|
+
clientCategoryCheckbox;
|
|
10
|
+
clientCategoryLabel;
|
|
11
|
+
constructor(page) {
|
|
12
|
+
this.auditLogTab = page.getByRole('tab', { name: 'Audit log' });
|
|
13
|
+
const auditLogPanel = page.getByRole('tabpanel', { name: 'Audit log' });
|
|
14
|
+
this.updateButton = auditLogPanel.getByRole('button', {
|
|
15
|
+
name: 'Update',
|
|
16
|
+
exact: true,
|
|
17
|
+
});
|
|
18
|
+
// Scope to the Client block: the category labels also exist under the User section.
|
|
19
|
+
this.clientOperationsSection = auditLogPanel
|
|
20
|
+
.getByText('Client operations to log', { exact: true })
|
|
21
|
+
.locator('..');
|
|
22
|
+
this.clientCategoryCheckbox = (category) => this.clientOperationsSection.getByRole('checkbox', { name: category });
|
|
23
|
+
// Carbon hides the real <input>; toggle via the visible label.
|
|
24
|
+
this.clientCategoryLabel = (category) => this.clientOperationsSection.locator('label').filter({ hasText: category });
|
|
25
|
+
}
|
|
26
|
+
async clickAuditLogTab() {
|
|
27
|
+
await (0, test_1.expect)(this.auditLogTab).toBeVisible({ timeout: 60000 });
|
|
28
|
+
await this.auditLogTab.click({ timeout: 60000 });
|
|
29
|
+
}
|
|
30
|
+
async ensureChecked(category) {
|
|
31
|
+
const checkbox = this.clientCategoryCheckbox(category);
|
|
32
|
+
await (0, test_1.expect)(checkbox).toBeAttached({ timeout: 30000 });
|
|
33
|
+
if (!(await checkbox.isChecked())) {
|
|
34
|
+
await this.clientCategoryLabel(category).click({ timeout: 30000 });
|
|
35
|
+
await (0, test_1.expect)(checkbox).toBeChecked();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async enableClientOperationLogging() {
|
|
39
|
+
const categories = [
|
|
40
|
+
'Administration',
|
|
41
|
+
'Deployed resources',
|
|
42
|
+
'User tasks',
|
|
43
|
+
];
|
|
44
|
+
for (const category of categories) {
|
|
45
|
+
await this.ensureChecked(category);
|
|
46
|
+
}
|
|
47
|
+
if (await this.updateButton.isEnabled()) {
|
|
48
|
+
await this.updateButton.click({ timeout: 60000 });
|
|
49
|
+
await (0, test_1.expect)(this.updateButton).toBeDisabled({ timeout: 60000 });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.AuditLogPage = AuditLogPage;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Page, Locator } from '@playwright/test';
|
|
2
2
|
import { LoginPage } from './LoginPage';
|
|
3
|
-
import { AppsPage } from './AppsPage';
|
|
4
3
|
declare class ModelerHomePage {
|
|
5
4
|
private defaultFolderName;
|
|
6
5
|
private page;
|
|
@@ -55,15 +54,23 @@ declare class ModelerHomePage {
|
|
|
55
54
|
assertFormBreadcrumbVisible(formName: string): Promise<void>;
|
|
56
55
|
/**
|
|
57
56
|
* Handles navigation after clicking an invitation link in email.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* the
|
|
61
|
-
*
|
|
62
|
-
* the shared project is
|
|
63
|
-
*
|
|
64
|
-
*
|
|
57
|
+
*
|
|
58
|
+
* The invited user follows the link while logged out, so the invitation flow
|
|
59
|
+
* presents the Auth0 login. Completing the login ON the invitation flow lets
|
|
60
|
+
* SaaS accept the invite and land the user directly in the SHARED org's
|
|
61
|
+
* Modeler, where the shared project is listed.
|
|
62
|
+
*
|
|
63
|
+
* We deliberately do NOT clear cookies and re-login via the app switcher:
|
|
64
|
+
* that path authenticates into the user's PERSONAL org, where the shared
|
|
65
|
+
* project does not exist and the folder can never appear — the previous
|
|
66
|
+
* fallback raced a 15s window and, whenever the (normal) login + invite-accept
|
|
67
|
+
* redirect chain took longer, dropped into that personal-org path and hung.
|
|
68
|
+
*
|
|
69
|
+
* If the link auto-authenticated (the banner is already up), no login is
|
|
70
|
+
* needed. The redirect chain routinely takes longer than a few seconds, so
|
|
71
|
+
* wait generously for the banner instead of racing a short window.
|
|
65
72
|
*/
|
|
66
|
-
navigateAfterInvitationLink(loginPage: LoginPage,
|
|
73
|
+
navigateAfterInvitationLink(loginPage: LoginPage, credentials: {
|
|
67
74
|
username: string;
|
|
68
75
|
password: string;
|
|
69
76
|
}): Promise<void>;
|
|
@@ -4,7 +4,6 @@ exports.ModelerHomePage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
|
|
7
|
-
const UtilitiesPage_1 = require("./UtilitiesPage");
|
|
8
7
|
class ModelerHomePage {
|
|
9
8
|
defaultFolderName = 'Cross Component Test Project';
|
|
10
9
|
page;
|
|
@@ -276,34 +275,44 @@ class ModelerHomePage {
|
|
|
276
275
|
}
|
|
277
276
|
/**
|
|
278
277
|
* Handles navigation after clicking an invitation link in email.
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* the
|
|
282
|
-
*
|
|
283
|
-
* the shared project is
|
|
284
|
-
*
|
|
285
|
-
*
|
|
278
|
+
*
|
|
279
|
+
* The invited user follows the link while logged out, so the invitation flow
|
|
280
|
+
* presents the Auth0 login. Completing the login ON the invitation flow lets
|
|
281
|
+
* SaaS accept the invite and land the user directly in the SHARED org's
|
|
282
|
+
* Modeler, where the shared project is listed.
|
|
283
|
+
*
|
|
284
|
+
* We deliberately do NOT clear cookies and re-login via the app switcher:
|
|
285
|
+
* that path authenticates into the user's PERSONAL org, where the shared
|
|
286
|
+
* project does not exist and the folder can never appear — the previous
|
|
287
|
+
* fallback raced a 15s window and, whenever the (normal) login + invite-accept
|
|
288
|
+
* redirect chain took longer, dropped into that personal-org path and hung.
|
|
289
|
+
*
|
|
290
|
+
* If the link auto-authenticated (the banner is already up), no login is
|
|
291
|
+
* needed. The redirect chain routinely takes longer than a few seconds, so
|
|
292
|
+
* wait generously for the banner instead of racing a short window.
|
|
286
293
|
*/
|
|
287
|
-
async navigateAfterInvitationLink(loginPage,
|
|
288
|
-
|
|
289
|
-
|
|
294
|
+
async navigateAfterInvitationLink(loginPage, credentials) {
|
|
295
|
+
// If the invitation flow shows the Auth0 login, authenticate in place so
|
|
296
|
+
// the invite is accepted in the shared-org context. If it doesn't appear,
|
|
297
|
+
// the link already auto-authenticated us and we skip straight to the banner.
|
|
298
|
+
const onLogin = await loginPage.usernameInput
|
|
299
|
+
.waitFor({ state: 'visible', timeout: 60000 })
|
|
290
300
|
.then(() => true)
|
|
291
301
|
.catch(() => false);
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// Already on the Modeler home page
|
|
298
|
-
}
|
|
302
|
+
if (onLogin) {
|
|
303
|
+
await loginPage.fillUsername(credentials.username);
|
|
304
|
+
await loginPage.clickContinueButton();
|
|
305
|
+
await loginPage.fillPassword(credentials.password);
|
|
306
|
+
await loginPage.clickLoginButton();
|
|
299
307
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
await
|
|
303
|
-
|
|
304
|
-
|
|
308
|
+
await (0, test_1.expect)(this.modelerPageBanner).toBeVisible({ timeout: 180000 });
|
|
309
|
+
try {
|
|
310
|
+
await this.clickHomeBreadcrumb();
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// Already on the Modeler home page
|
|
305
314
|
}
|
|
306
|
-
await (0, test_1.expect)(this.modelerPageBanner).toBeVisible({ timeout:
|
|
315
|
+
await (0, test_1.expect)(this.modelerPageBanner).toBeVisible({ timeout: 30000 });
|
|
307
316
|
}
|
|
308
317
|
}
|
|
309
318
|
exports.ModelerHomePage = ModelerHomePage;
|
|
@@ -29,6 +29,7 @@ export declare function runMultipleProcesses(clusterName: string, page: Page, mo
|
|
|
29
29
|
numberOfProcesses?: number;
|
|
30
30
|
processName?: string;
|
|
31
31
|
uploadFromFile?: string;
|
|
32
|
+
settleBetween?: (processId: string) => Promise<void>;
|
|
32
33
|
}): Promise<void>;
|
|
33
34
|
export declare function runProcess(clusterName: string, page: Page, modelerHomePage: ModelerHomePage, modelerCreatePage: ModelerCreatePage, processName: string, processId?: string, uploadFromFile?: string): Promise<void>;
|
|
34
35
|
export declare function modelDiagramFromFile(page: Page, modelerHomePage: ModelerHomePage, modelerCreatePage: ModelerCreatePage, processName: string, fileName?: string, nrOfRenamedUserTasks?: number, taskName?: string): Promise<void>;
|
|
@@ -219,10 +219,20 @@ async function completeTaskWithRetry(taskPanelPage, taskDetailsPage, taskName, t
|
|
|
219
219
|
}
|
|
220
220
|
exports.completeTaskWithRetry = completeTaskWithRetry;
|
|
221
221
|
async function runMultipleProcesses(clusterName, page, modelerHomePage, modelerCreatePage, options) {
|
|
222
|
-
const { processName = 'Camunda_User_Task', numberOfProcesses = 2, uploadFromFile = '', } = options || {};
|
|
222
|
+
const { processName = 'Camunda_User_Task', numberOfProcesses = 2, uploadFromFile = '', settleBetween, } = options || {};
|
|
223
223
|
for (let i = 1; i <= numberOfProcesses; i++) {
|
|
224
224
|
await runProcess(clusterName, page, modelerHomePage, modelerCreatePage, processName + i, processName + i, uploadFromFile);
|
|
225
225
|
await modelerHomePage.clickProjectBreadcrumb();
|
|
226
|
+
// When RBA is enabled, deploying a process auto-provisions a resource
|
|
227
|
+
// authorization for the deployer keyed on the process definition id. The
|
|
228
|
+
// identity service writes that grant with a read-modify-write, so deploying
|
|
229
|
+
// the next process before this grant lands clobbers it (lost update) and it
|
|
230
|
+
// is permanently missing. When a settleBetween hook is supplied, wait for
|
|
231
|
+
// the just-deployed grant to be durably provisioned before the next deploy —
|
|
232
|
+
// a deterministic wait, not a fixed sleep.
|
|
233
|
+
if (settleBetween && i < numberOfProcesses) {
|
|
234
|
+
await settleBetween(processName + i);
|
|
235
|
+
}
|
|
226
236
|
}
|
|
227
237
|
}
|
|
228
238
|
exports.runMultipleProcesses = runMultipleProcesses;
|
package/dist/tests/8.10/orchestration-cluster-mcp-server/api/mcp-processes-audit-log.spec.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
|
|
4
|
+
* one or more contributor license agreements. See the NOTICE file distributed
|
|
5
|
+
* with this work for additional information regarding copyright ownership.
|
|
6
|
+
* Licensed under the Camunda License 1.0. You may not use this file
|
|
7
|
+
* except in compliance with the Camunda License 1.0.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
/*
|
|
11
|
+
* MCP audit-log inbound channel provenance — camunda/camunda#56234
|
|
12
|
+
* (SC-AUD-01/02/03).
|
|
13
|
+
*/
|
|
14
|
+
const _8_10_1 = require("../../../../fixtures/8.10");
|
|
15
|
+
const test_1 = require("@playwright/test");
|
|
16
|
+
const apiHelpers_1 = require("../../../../utils/apiHelpers");
|
|
17
|
+
const mcpHelpers_1 = require("../../../../utils/mcpHelpers");
|
|
18
|
+
const constants_1 = require("../../../../utils/constants");
|
|
19
|
+
// Raw BPMN `io.camunda.tool:name` annotation — what the audit log stores in
|
|
20
|
+
// inboundChannelToolName (not the composite tools/list name).
|
|
21
|
+
const TOOL_NAME_RAW = 'mcp-as-tool-process';
|
|
22
|
+
const MCP_PROCESS_DEFINITION_ID = 'mcpAsToolProcess';
|
|
23
|
+
let authToken;
|
|
24
|
+
let mcpProcessesUrl;
|
|
25
|
+
let messageSubscriptionsSearchUrl;
|
|
26
|
+
let auditLogsSearchUrl;
|
|
27
|
+
let mcpSessionId = null;
|
|
28
|
+
let restProcessDefinitionKey;
|
|
29
|
+
let compositeToolName;
|
|
30
|
+
_8_10_1.test.describe.configure({ mode: 'parallel' });
|
|
31
|
+
_8_10_1.test.describe('Orchestration Cluster MCP Server — audit log inbound channel', () => {
|
|
32
|
+
_8_10_1.test.beforeAll(async () => {
|
|
33
|
+
authToken = await (0, apiHelpers_1.authSaasAPI)(undefined, 'agentic_cluster');
|
|
34
|
+
mcpProcessesUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/mcp/processes', 'saas', 'agentic_cluster');
|
|
35
|
+
messageSubscriptionsSearchUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/v2/message-subscriptions/search', 'saas', 'agentic_cluster');
|
|
36
|
+
auditLogsSearchUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/v2/audit-logs/search', 'saas', 'agentic_cluster');
|
|
37
|
+
const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
|
|
38
|
+
mcpSessionId = await (0, mcpHelpers_1.initializeMcpSession)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
|
|
39
|
+
const [mcpKey, restKey] = await Promise.all([
|
|
40
|
+
(0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_process.bpmn', authToken, 'saas', 'agentic_cluster'),
|
|
41
|
+
(0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_process_instance_test_file.bpmn', authToken, 'saas', 'agentic_cluster'),
|
|
42
|
+
]);
|
|
43
|
+
if (mcpKey == null) {
|
|
44
|
+
throw new Error('Failed to deploy mcp_as_tool_process.bpmn');
|
|
45
|
+
}
|
|
46
|
+
if (restKey == null) {
|
|
47
|
+
throw new Error('Failed to deploy mcp_process_instance_test_file.bpmn');
|
|
48
|
+
}
|
|
49
|
+
restProcessDefinitionKey = String(restKey);
|
|
50
|
+
// Composite tool name is `<toolName>_<messageSubscriptionKey>`.
|
|
51
|
+
await (0, test_1.expect)(async () => {
|
|
52
|
+
const res = await apiContext.post(messageSubscriptionsSearchUrl, {
|
|
53
|
+
headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
|
|
54
|
+
data: {
|
|
55
|
+
filter: {
|
|
56
|
+
processDefinitionId: MCP_PROCESS_DEFINITION_ID,
|
|
57
|
+
messageSubscriptionType: 'START_EVENT',
|
|
58
|
+
},
|
|
59
|
+
sort: [{ field: 'processDefinitionVersion', order: 'desc' }],
|
|
60
|
+
page: { limit: 1 },
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
|
|
64
|
+
const json = (await res.json());
|
|
65
|
+
(0, test_1.expect)(json.items.length).toBeGreaterThanOrEqual(1);
|
|
66
|
+
compositeToolName = `${TOOL_NAME_RAW}_${json.items[0].messageSubscriptionKey}`;
|
|
67
|
+
}).toPass(constants_1.deletionAssertionOptions);
|
|
68
|
+
});
|
|
69
|
+
(0, _8_10_1.test)('SC-AUD-01 — MCP tools/call produces audit entry with inboundChannelType=MCP', async () => {
|
|
70
|
+
const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
|
|
71
|
+
const callBody = await (0, mcpHelpers_1.callTool)(apiContext, mcpProcessesUrl, authToken, mcpSessionId, compositeToolName, {});
|
|
72
|
+
(0, test_1.expect)(callBody.result).toBeDefined();
|
|
73
|
+
(0, test_1.expect)(callBody.result.isError).not.toBe(true);
|
|
74
|
+
const pik = (0, mcpHelpers_1.extractProcessInstanceKey)(callBody.result.content[0]?.text ?? '');
|
|
75
|
+
(0, test_1.expect)(pik, 'Expected processInstanceKey from MCP tools/call').not.toBeNull();
|
|
76
|
+
await (0, test_1.expect)(async () => {
|
|
77
|
+
const res = await apiContext.post(auditLogsSearchUrl, {
|
|
78
|
+
headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
|
|
79
|
+
data: {
|
|
80
|
+
filter: {
|
|
81
|
+
processInstanceKey: pik,
|
|
82
|
+
operationType: 'CREATE',
|
|
83
|
+
},
|
|
84
|
+
page: { limit: 10 },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
|
|
88
|
+
const json = (await res.json());
|
|
89
|
+
const entry = json.items.find((i) => i.entityType === 'PROCESS_INSTANCE');
|
|
90
|
+
(0, test_1.expect)(entry, `Expected a PROCESS_INSTANCE CREATE audit entry for pik ${pik}`).toBeDefined();
|
|
91
|
+
(0, test_1.expect)(entry.inboundChannelType).toBe('MCP');
|
|
92
|
+
}).toPass(constants_1.auditLogAssertionOptions);
|
|
93
|
+
});
|
|
94
|
+
(0, _8_10_1.test)('SC-AUD-02 — MCP audit entry captures the raw tool name in inboundChannelToolName', async () => {
|
|
95
|
+
const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
|
|
96
|
+
const callBody = await (0, mcpHelpers_1.callTool)(apiContext, mcpProcessesUrl, authToken, mcpSessionId, compositeToolName, {});
|
|
97
|
+
(0, test_1.expect)(callBody.result).toBeDefined();
|
|
98
|
+
(0, test_1.expect)(callBody.result.isError).not.toBe(true);
|
|
99
|
+
const pik = (0, mcpHelpers_1.extractProcessInstanceKey)(callBody.result.content[0]?.text ?? '');
|
|
100
|
+
(0, test_1.expect)(pik).not.toBeNull();
|
|
101
|
+
await (0, test_1.expect)(async () => {
|
|
102
|
+
const res = await apiContext.post(auditLogsSearchUrl, {
|
|
103
|
+
headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
|
|
104
|
+
data: {
|
|
105
|
+
filter: {
|
|
106
|
+
processInstanceKey: pik,
|
|
107
|
+
operationType: 'CREATE',
|
|
108
|
+
},
|
|
109
|
+
page: { limit: 10 },
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
|
|
113
|
+
const json = (await res.json());
|
|
114
|
+
const entry = json.items.find((i) => i.entityType === 'PROCESS_INSTANCE');
|
|
115
|
+
(0, test_1.expect)(entry, `Expected a PROCESS_INSTANCE CREATE audit entry for pik ${pik}`).toBeDefined();
|
|
116
|
+
// Raw annotation value, not the composite tools/list name.
|
|
117
|
+
(0, test_1.expect)(entry.inboundChannelToolName).toBe(TOOL_NAME_RAW);
|
|
118
|
+
(0, test_1.expect)(entry.inboundChannelType).toBe('MCP');
|
|
119
|
+
}).toPass(constants_1.auditLogAssertionOptions);
|
|
120
|
+
});
|
|
121
|
+
(0, _8_10_1.test)('SC-AUD-03 — REST-triggered start is distinguishable from MCP (inboundChannelType and tool name both null)', async () => {
|
|
122
|
+
const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
|
|
123
|
+
// NONE-start BPMN started via REST — bypasses MCP entirely.
|
|
124
|
+
const restPik = await (0, apiHelpers_1.createProcessInstance)(restProcessDefinitionKey, authToken, 'saas', 'agentic_cluster');
|
|
125
|
+
(0, test_1.expect)(restPik).toBeTruthy();
|
|
126
|
+
await (0, test_1.expect)(async () => {
|
|
127
|
+
const res = await apiContext.post(auditLogsSearchUrl, {
|
|
128
|
+
headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
|
|
129
|
+
data: {
|
|
130
|
+
filter: {
|
|
131
|
+
processInstanceKey: restPik,
|
|
132
|
+
operationType: 'CREATE',
|
|
133
|
+
},
|
|
134
|
+
page: { limit: 10 },
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
|
|
138
|
+
const json = (await res.json());
|
|
139
|
+
const entry = json.items.find((i) => i.entityType === 'PROCESS_INSTANCE');
|
|
140
|
+
(0, test_1.expect)(entry, `Expected a PROCESS_INSTANCE CREATE audit entry for REST pik ${restPik}`).toBeDefined();
|
|
141
|
+
// REST-triggered entries have both fields null (verified on 8.10-SNAPSHOT).
|
|
142
|
+
(0, test_1.expect)(entry.inboundChannelType).toBeNull();
|
|
143
|
+
(0, test_1.expect)(entry.inboundChannelToolName).toBeNull();
|
|
144
|
+
}).toPass(constants_1.auditLogAssertionOptions);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -47,7 +47,7 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
|
|
|
47
47
|
await clusterSecretsPage.createSetOfSecrets(clusterName, connectorSecrets_1.bedrockCodeInterpreterSecretsValues);
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
|
-
(0, _8_10_1.test)('Create Agentic Orchestration Cluster', async ({ page, loginPage, homePage, clusterPage, clusterDetailsPage, clusterSecretsPage, }, testInfo) => {
|
|
50
|
+
(0, _8_10_1.test)('Create Agentic Orchestration Cluster', async ({ page, loginPage, homePage, clusterPage, clusterDetailsPage, clusterSecretsPage, auditLogPage, }, testInfo) => {
|
|
51
51
|
_8_10_1.test.skip(process.env.IS_PROD === 'true', 'Skipping test because not required on PROD test org');
|
|
52
52
|
_8_10_1.test.skip(process.env.IS_AG === 'true', 'Skipping test because not required when IS_AG is true');
|
|
53
53
|
_8_10_1.test.slow();
|
|
@@ -60,6 +60,8 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
|
|
|
60
60
|
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName, 300000, 1500000);
|
|
61
61
|
await clusterPage.clickClusterLink(clusterName);
|
|
62
62
|
await clusterDetailsPage.assertComponentsHealth();
|
|
63
|
+
await auditLogPage.clickAuditLogTab();
|
|
64
|
+
await auditLogPage.enableClientOperationLogging();
|
|
63
65
|
await clusterDetailsPage.clickSettingsTab();
|
|
64
66
|
await clusterDetailsPage.checkMcpSupportToggle();
|
|
65
67
|
await homePage.clickClusters();
|
|
@@ -236,6 +236,31 @@ _8_7_1.test.describe('RBA Enabled User Flows Test', () => {
|
|
|
236
236
|
await (0, UtilitiesPage_1.runMultipleProcesses)(clusterName, page, modelerHomePage, modelerCreatePage, {
|
|
237
237
|
processName: processName,
|
|
238
238
|
uploadFromFile: 'Camunda_Simple_User_Task_1',
|
|
239
|
+
// RBA is enabled before this deploy, so each process start
|
|
240
|
+
// auto-provisions the deployer's resource authorization via a
|
|
241
|
+
// read-modify-write. Deploying the second process before the first
|
|
242
|
+
// grant lands clobbers it (lost update), permanently dropping one of
|
|
243
|
+
// the two process ids — which no downstream wait can recover (see
|
|
244
|
+
// authorizedResourcesAssertion). Before deploying the next process,
|
|
245
|
+
// wait for the just-deployed grant to appear in Console, then return
|
|
246
|
+
// to the project. Deterministic, unlike a fixed sleep.
|
|
247
|
+
settleBetween: async (processId) => {
|
|
248
|
+
await appsPage.clickCamundaApps();
|
|
249
|
+
await appsPage.clickConsoleLink();
|
|
250
|
+
await homePage.clickOrganization();
|
|
251
|
+
await consoleOrganizationsPage.clickUsersTab();
|
|
252
|
+
await consoleOrganizationsPage.clickMainUser(mainUser.username);
|
|
253
|
+
await consoleOrganizationsPage.clickAuthorizations();
|
|
254
|
+
await consoleOrganizationsPage.authorizedResourcesAssertion([
|
|
255
|
+
processId,
|
|
256
|
+
]);
|
|
257
|
+
await appsPage.clickCamundaApps();
|
|
258
|
+
await appsPage.clickModeler();
|
|
259
|
+
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
260
|
+
timeout: 120000,
|
|
261
|
+
});
|
|
262
|
+
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
263
|
+
},
|
|
239
264
|
});
|
|
240
265
|
await (0, sleep_1.sleep)(60000);
|
|
241
266
|
});
|
|
@@ -514,12 +514,17 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
514
514
|
});
|
|
515
515
|
await _8_7_1.test.step('Log in with Second User and Navigate to Cross Component Test project as Collaborator', async () => {
|
|
516
516
|
await (0, UtilitiesPage_1.clickInvitationLinkInEmail)(page, id);
|
|
517
|
-
await (
|
|
518
|
-
await modelerHomePage.navigateAfterInvitationLink(loginPage, appsPage, {
|
|
517
|
+
await modelerHomePage.navigateAfterInvitationLink(loginPage, {
|
|
519
518
|
username: emailAddress,
|
|
520
519
|
password: password,
|
|
521
520
|
});
|
|
522
|
-
|
|
521
|
+
// navigateAfterInvitationLink lands the invited user in the SHARED org's
|
|
522
|
+
// Modeler home (it logs in on the invitation flow rather than
|
|
523
|
+
// re-authenticating into the personal org), so the shared project is
|
|
524
|
+
// present. Assert its visibility directly — no session-breaking reload.
|
|
525
|
+
await (0, test_1.expect)(modelerHomePage.crossComponentProjectFolder).toBeVisible({
|
|
526
|
+
timeout: 120000,
|
|
527
|
+
});
|
|
523
528
|
});
|
|
524
529
|
await _8_7_1.test.step('Log out from Second User', async () => {
|
|
525
530
|
await settingsPage.clickOpenSettingsButton();
|
|
@@ -10,6 +10,10 @@ export declare const deletionAssertionOptions: {
|
|
|
10
10
|
intervals: number[];
|
|
11
11
|
timeout: number;
|
|
12
12
|
};
|
|
13
|
+
export declare const auditLogAssertionOptions: {
|
|
14
|
+
intervals: number[];
|
|
15
|
+
timeout: number;
|
|
16
|
+
};
|
|
13
17
|
export declare const TIMEOUT: {
|
|
14
18
|
short: number;
|
|
15
19
|
medium: number;
|
package/dist/utils/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TIMEOUT = exports.deletionAssertionOptions = exports.defaultAssertionOptions = exports._3_MINUTES_IN_MS = exports._1_MINUTE_IN_MS = exports._1_SECOND_IN_MS = exports._100_MS = void 0;
|
|
3
|
+
exports.TIMEOUT = exports.auditLogAssertionOptions = exports.deletionAssertionOptions = exports.defaultAssertionOptions = exports._3_MINUTES_IN_MS = exports._1_MINUTE_IN_MS = exports._1_SECOND_IN_MS = exports._100_MS = void 0;
|
|
4
4
|
exports._100_MS = 100;
|
|
5
5
|
exports._1_SECOND_IN_MS = 1000;
|
|
6
6
|
exports._1_MINUTE_IN_MS = 60 * 1000;
|
|
@@ -14,6 +14,12 @@ exports.deletionAssertionOptions = {
|
|
|
14
14
|
intervals: [5000, 10000, 15000, 30000],
|
|
15
15
|
timeout: 60000,
|
|
16
16
|
};
|
|
17
|
+
// Longest options for audit-log assertions: entries land in secondary storage
|
|
18
|
+
// only after the audit config propagates to the exporters.
|
|
19
|
+
exports.auditLogAssertionOptions = {
|
|
20
|
+
intervals: [5000, 10000, 15000, 15000],
|
|
21
|
+
timeout: 90000,
|
|
22
|
+
};
|
|
17
23
|
exports.TIMEOUT = {
|
|
18
24
|
short: 15000,
|
|
19
25
|
medium: 30000,
|