Files
computer-playbook/roles/web-app-nextcloud/files/playwright/addons/integration_openproject.spec.js
kevinveenbirkenbachandClaude Opus 4.8 4c91df5de7 test(e2e): forbid raw page.goto, migrate all specs to Tor-resilient gotoOnion
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:59:38 +02:00

95 lines
4.0 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { resolveTimeout } = require("../timeouts");
const { skipUnlessAddonEnabled } = require("../addon-gating");
const shared = require("../_shared");
const { gotoOnion } = require("../personas");
test("integration integration_openproject: two-way OAuth coupling provisioned and connectable to partner OpenProject", async ({ browser }) => {
skipUnlessAddonEnabled("integration_openproject");
test.setTimeout(resolveTimeout(120_000));
const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
try {
await shared.loginToStandaloneNextcloud(page);
await gotoOnion(page,
new URL("settings/admin/openproject", shared.env.nextcloudBaseUrl).toString(),
{ waitUntil: "domcontentloaded", timeout: resolveTimeout(60_000) }
);
await shared.dismissBlockingNextcloudModals(page, page);
const adminSection = page
.locator("#openproject_prefs, #openproject-server-host, #openproject-prefs")
.or(page.getByText(/openproject/i))
.first();
await expect(
adminSection,
"the OpenProject integration admin settings section must render when the addon is enabled"
).toBeVisible({ timeout: resolveTimeout(60_000) });
const oauthConfigured = page
.locator('input[id*="openproject-oauth-client-id"], input[id*="client-id"], input[id*="client-secret"]')
.or(page.getByText(/reset oauth|replace oauth|nextcloud oauth (client|values)/i))
.or(page.getByText(/oauth client id|client secret/i))
.first();
await expect(
oauthConfigured,
"the admin panel must show the provisioned OAuth client (proves the two-way OAuth pair is registered on BOTH sides). When integration_openproject is enabled but this is absent, the coupling failed to provision — the test MUST fail here, not skip."
).toBeVisible({ timeout: resolveTimeout(60_000) });
const nextcloudHost = new URL(shared.env.nextcloudBaseUrl).host;
const adminConfig = page.locator(
"#initial-state-integration_openproject-admin-settings-config"
);
let instanceHost = null;
let clientId = "";
let clientSecret = "";
let authMethod = "";
if (await adminConfig.count()) {
const raw =
(await adminConfig.inputValue().catch(() => "")) ||
(await adminConfig.getAttribute("value").catch(() => "")) ||
"";
let decoded;
try {
decoded = Buffer.from(raw, "base64").toString("utf8");
} catch {
decoded = raw;
}
try {
const cfg = JSON.parse(decoded);
const url = cfg.openproject_instance_url || "";
if (/^https?:\/\//.test(url)) instanceHost = new URL(url).host;
clientId = cfg.openproject_client_id || "";
clientSecret = cfg.openproject_client_secret || "";
authMethod = cfg.authorization_method || "";
} catch {
instanceHost = null;
}
}
expect(instanceHost, "the OpenProject instance URL must be configured on the admin panel (integration_openproject openproject_instance_url)").toBeTruthy();
expect(
instanceHost,
"the configured OpenProject instance URL must be the partner host, not the Nextcloud host"
).not.toBe(nextcloudHost);
expect(
clientId.length,
"the OpenProject OAuth application client_id must be provisioned on the partner and linked into integration_openproject (the NC->OP half of the two-way OAuth coupling)"
).toBeGreaterThan(0);
expect(
clientSecret.length,
"the OpenProject OAuth application client_secret must be linked into integration_openproject (proves the partner registered the OAuth app and returned its secret)"
).toBeGreaterThan(0);
expect(
authMethod,
"integration_openproject must be wired for the OAuth2 authorization method once the bidirectional coupling is provisioned"
).toBe("oauth2");
} finally {
await page.close().catch(() => {});
await context.close().catch(() => {});
}
});