@camunda/e2e-test-suite 0.0.530 → 0.0.532
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.
|
@@ -5,6 +5,7 @@ const test_1 = require("@playwright/test");
|
|
|
5
5
|
const LoginPage_1 = require("./LoginPage");
|
|
6
6
|
const sleep_1 = require("../../utils/sleep");
|
|
7
7
|
const constants_1 = require("../../utils/constants");
|
|
8
|
+
const env_1 = require("../../utils/env");
|
|
8
9
|
const ORCHESTRATION_CONTEXT_PATH = process.env.ORCHESTRATION_CONTEXT_PATH ?? '/orchestration';
|
|
9
10
|
const NORMALIZED_ORCHESTRATION_CONTEXT_PATH = ORCHESTRATION_CONTEXT_PATH.startsWith('/')
|
|
10
11
|
? ORCHESTRATION_CONTEXT_PATH
|
|
@@ -147,7 +148,7 @@ class NavigationPage {
|
|
|
147
148
|
: new Error(`goToKeycloak failed after ${maxRetries} attempts`);
|
|
148
149
|
}
|
|
149
150
|
async goToOCAdmin(sleepTimeout, credentials) {
|
|
150
|
-
await this.goTo(`${NORMALIZED_ORCHESTRATION_CONTEXT_PATH}/admin`, this.identityPageBanner, sleepTimeout, credentials);
|
|
151
|
+
await this.goTo(`${NORMALIZED_ORCHESTRATION_CONTEXT_PATH}/admin`, this.identityPageBanner, sleepTimeout, credentials, env_1.isOpenSearch ? 10 : 5);
|
|
151
152
|
}
|
|
152
153
|
async goToConsole(sleepTimeout, credentials) {
|
|
153
154
|
await this.goTo(CONSOLE_CONTEXT_PATH, this.consolePageBanner, sleepTimeout, credentials);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// Auth0 smoke tests — HTTP-level integration check for the auth0 ci-test
|
|
4
|
+
// scenario. Each Camunda 8 component on the platform is fronted by an OIDC
|
|
5
|
+
// client; this suite asserts the chart wiring is correct end-to-end without
|
|
6
|
+
// needing a browser, an Auth0 user, or a token exchange.
|
|
7
|
+
//
|
|
8
|
+
// HTTP-only by design: the Auth0 ci-test scenario does not deploy Keycloak,
|
|
9
|
+
// so the browser-driven setupKeycloakUser flow used by the SM smokes is not
|
|
10
|
+
// applicable. This suite validates chart-to-IDP wiring only — it does not
|
|
11
|
+
// drive a real user session.
|
|
12
|
+
//
|
|
13
|
+
// Two flavours of test, one per component family:
|
|
14
|
+
//
|
|
15
|
+
// 1. Spring-server components (orchestration, identity, optimize) expose
|
|
16
|
+
// /<component>/oauth2/authorization/oidc as the canonical login entry.
|
|
17
|
+
// Hitting it unauthenticated walks a redirect chain that must terminate
|
|
18
|
+
// at AUTH0_ISSUER/authorize carrying the right client_id, redirect_uri,
|
|
19
|
+
// and response_type. We trace the chain ourselves so we can assert on
|
|
20
|
+
// every hop.
|
|
21
|
+
//
|
|
22
|
+
// 2. SPA components (webModeler, console) render a client-side login page
|
|
23
|
+
// and do the OIDC redirect from JS, so a server-side trace stops at the
|
|
24
|
+
// SPA shell. Instead, hit a backend API that demands a Bearer token —
|
|
25
|
+
// a 401 with a WWW-Authenticate header proves the chart wired the
|
|
26
|
+
// component to require an OIDC token at all.
|
|
27
|
+
//
|
|
28
|
+
// Plus a sanity test that AUTH0_ISSUER/.well-known/openid-configuration
|
|
29
|
+
// resolves and points at the right issuer; if that breaks, every other
|
|
30
|
+
// failure is downstream noise.
|
|
31
|
+
const test_1 = require("@playwright/test");
|
|
32
|
+
const ingressHost = process.env.TEST_INGRESS_HOST;
|
|
33
|
+
const auth0Issuer = (process.env.AUTH0_ISSUER_URL || '').replace(/\/+$/, '');
|
|
34
|
+
const MAX_HOPS = 10;
|
|
35
|
+
// Spring-server components: each redirect-chain terminates at Auth0 /authorize.
|
|
36
|
+
const springComponents = [
|
|
37
|
+
// [logical-name, oauth2-entry-path, expected client_id env var]
|
|
38
|
+
[
|
|
39
|
+
'orchestration',
|
|
40
|
+
'/orchestration/oauth2/authorization/oidc',
|
|
41
|
+
'AUTH0_ORCHESTRATION_CLIENT_ID',
|
|
42
|
+
],
|
|
43
|
+
['identity', '/identity/oauth2/authorization/oidc', 'AUTH0_IDENTITY_CLIENT_ID'],
|
|
44
|
+
['optimize', '/optimize/oauth2/authorization/oidc', 'AUTH0_OPTIMIZE_CLIENT_ID'],
|
|
45
|
+
];
|
|
46
|
+
// SPA components: assert a protected backend API returns 401.
|
|
47
|
+
const spaComponents = [
|
|
48
|
+
['webModeler', '/modeler/api/v1/info'],
|
|
49
|
+
['console', '/api/v1/clusters'],
|
|
50
|
+
];
|
|
51
|
+
test_1.test.describe('Auth0 OIDC smoke', () => {
|
|
52
|
+
test_1.test.beforeAll(() => {
|
|
53
|
+
(0, test_1.expect)(ingressHost, 'TEST_INGRESS_HOST must be set by the test runner').toBeTruthy();
|
|
54
|
+
(0, test_1.expect)(auth0Issuer, 'AUTH0_ISSUER_URL must be set by the test runner').toBeTruthy();
|
|
55
|
+
});
|
|
56
|
+
for (const [name, urlPath, clientIdVar] of springComponents) {
|
|
57
|
+
(0, test_1.test)(`${name}: ${urlPath} redirects to Auth0 /authorize`, async ({ request, }) => {
|
|
58
|
+
const expectedClientId = process.env[clientIdVar];
|
|
59
|
+
(0, test_1.expect)(expectedClientId, `${clientIdVar} must be set by the test runner`).toBeTruthy();
|
|
60
|
+
const start = `https://${ingressHost}${urlPath}`;
|
|
61
|
+
const chain = await traceRedirectChain(request, start);
|
|
62
|
+
const authorizeHop = chain.find((u) => u.origin === auth0Issuer && u.pathname === '/authorize');
|
|
63
|
+
(0, test_1.expect)(authorizeHop, `chain did not reach ${auth0Issuer}/authorize:\n${chain
|
|
64
|
+
.map((u) => ' → ' + u.toString())
|
|
65
|
+
.join('\n')}`).toBeTruthy();
|
|
66
|
+
const params = authorizeHop.searchParams;
|
|
67
|
+
(0, test_1.expect)(params.get('client_id'), `client_id must match ${expectedClientId}`).toBe(expectedClientId);
|
|
68
|
+
(0, test_1.expect)(params.get('response_type'), 'response_type must be code').toBe('code');
|
|
69
|
+
(0, test_1.expect)(params.get('redirect_uri'), 'redirect_uri must be present').toBeTruthy();
|
|
70
|
+
(0, test_1.expect)(params.get('scope'), 'scope must include openid').toContain('openid');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const [name, urlPath] of spaComponents) {
|
|
74
|
+
(0, test_1.test)(`${name}: ${urlPath} requires authentication (401)`, async ({ request, }) => {
|
|
75
|
+
const url = `https://${ingressHost}${urlPath}`;
|
|
76
|
+
const response = await request.get(url, { maxRedirects: 0 });
|
|
77
|
+
(0, test_1.expect)(response.status(), `${url} returned ${response.status()} ${response.statusText()}; expected 401 from a protected backend route`).toBe(401);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
(0, test_1.test)('Auth0 well-known OIDC config is reachable', async ({ request }) => {
|
|
81
|
+
const url = `${auth0Issuer}/.well-known/openid-configuration`;
|
|
82
|
+
const response = await request.get(url, { maxRedirects: 0 });
|
|
83
|
+
(0, test_1.expect)(response.status(), `${url} returned ${response.status()}`).toBe(200);
|
|
84
|
+
const body = await response.json();
|
|
85
|
+
(0, test_1.expect)(body.issuer, 'issuer claim must match AUTH0_ISSUER_URL').toBe(auth0Issuer + '/');
|
|
86
|
+
(0, test_1.expect)(body.authorization_endpoint, 'authorization_endpoint must be present').toMatch(/\/authorize$/);
|
|
87
|
+
(0, test_1.expect)(body.jwks_uri, 'jwks_uri must be present').toMatch(/\/jwks\.json$/);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
// traceRedirectChain follows Location headers manually so the test can
|
|
91
|
+
// inspect every URL in the chain — Playwright's APIRequestContext follows
|
|
92
|
+
// redirects transparently otherwise and we'd lose intermediate hops.
|
|
93
|
+
async function traceRedirectChain(request, startUrl) {
|
|
94
|
+
const chain = [new URL(startUrl)];
|
|
95
|
+
let current = startUrl;
|
|
96
|
+
for (let hop = 0; hop < MAX_HOPS; hop++) {
|
|
97
|
+
const response = await request.get(current, { maxRedirects: 0 });
|
|
98
|
+
const status = response.status();
|
|
99
|
+
if (![301, 302, 303, 307, 308].includes(status)) {
|
|
100
|
+
return chain;
|
|
101
|
+
}
|
|
102
|
+
const location = response.headers()['location'];
|
|
103
|
+
if (!location) {
|
|
104
|
+
return chain;
|
|
105
|
+
}
|
|
106
|
+
// Resolve relative redirects against the current URL.
|
|
107
|
+
const next = new URL(location, current);
|
|
108
|
+
chain.push(next);
|
|
109
|
+
current = next.toString();
|
|
110
|
+
}
|
|
111
|
+
return chain;
|
|
112
|
+
}
|
package/dist/utils/apiHelpers.js
CHANGED
|
@@ -338,8 +338,10 @@ async function createClusterVariableInternal(authToken, environment, variableNam
|
|
|
338
338
|
}
|
|
339
339
|
bodyText = await response.text();
|
|
340
340
|
lastErrorMessage = `HTTP ${status} - ${bodyText.slice(0, 200)}`;
|
|
341
|
-
// Retry on transient gateway / upstream errors
|
|
342
|
-
|
|
341
|
+
// Retry on transient gateway / upstream errors and on HTTP 500
|
|
342
|
+
// (search backend not ready yet — observed on fresh OpenSearch deploys).
|
|
343
|
+
if ((status === 500 || (status >= 502 && status <= 504)) &&
|
|
344
|
+
attempt < maxAttempts) {
|
|
343
345
|
console.warn(`createClusterVariable "${variableName}" attempt ${attempt}/${maxAttempts} got HTTP ${status}; retrying...`);
|
|
344
346
|
await new Promise((r) => setTimeout(r, baseDelayMs * attempt));
|
|
345
347
|
continue;
|