1
0
mirror of https://github.com/pocket-id/pocket-id.git synced 2026-02-04 15:04:43 +00:00

refactor: add formatter to Playwright tests

This commit is contained in:
Elias Schneider
2025-06-27 23:33:26 +02:00
parent d070b9a778
commit 73e7e0b1c5
22 changed files with 1581 additions and 1764 deletions

6
tests/.prettierignore Normal file
View File

@@ -0,0 +1,6 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
.output/

6
tests/.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

View File

@@ -1,134 +1,134 @@
export const users = { export const users = {
tim: { tim: {
id: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e', id: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e',
firstname: 'Tim', firstname: 'Tim',
lastname: 'Cook', lastname: 'Cook',
email: 'tim.cook@test.com', email: 'tim.cook@test.com',
username: 'tim', username: 'tim'
}, },
craig: { craig: {
id: '1cd19686-f9a6-43f4-a41f-14a0bf5b4036', id: '1cd19686-f9a6-43f4-a41f-14a0bf5b4036',
firstname: 'Craig', firstname: 'Craig',
lastname: 'Federighi', lastname: 'Federighi',
email: 'craig.federighi@test.com', email: 'craig.federighi@test.com',
username: 'craig', username: 'craig'
}, },
steve: { steve: {
firstname: 'Steve', firstname: 'Steve',
lastname: 'Jobs', lastname: 'Jobs',
email: 'steve.jobs@test.com', email: 'steve.jobs@test.com',
username: 'steve', username: 'steve'
}, }
}; };
export const oidcClients = { export const oidcClients = {
nextcloud: { nextcloud: {
id: '3654a746-35d4-4321-ac61-0bdcff2b4055', id: '3654a746-35d4-4321-ac61-0bdcff2b4055',
name: 'Nextcloud', name: 'Nextcloud',
callbackUrl: 'http://nextcloud/auth/callback', callbackUrl: 'http://nextcloud/auth/callback',
logoutCallbackUrl: 'http://nextcloud/auth/logout/callback', logoutCallbackUrl: 'http://nextcloud/auth/logout/callback',
secret: 'w2mUeZISmEvIDMEDvpY0PnxQIpj1m3zY', secret: 'w2mUeZISmEvIDMEDvpY0PnxQIpj1m3zY'
}, },
immich: { immich: {
id: '606c7782-f2b1-49e5-8ea9-26eb1b06d018', id: '606c7782-f2b1-49e5-8ea9-26eb1b06d018',
name: 'Immich', name: 'Immich',
callbackUrl: 'http://immich/auth/callback', callbackUrl: 'http://immich/auth/callback',
secret: 'PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x', secret: 'PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x'
}, },
federated: { federated: {
id: 'c48232ff-ff65-45ed-ae96-7afa8a9b443b', id: 'c48232ff-ff65-45ed-ae96-7afa8a9b443b',
name: 'Federated', name: 'Federated',
callbackUrl: 'http://federated/auth/callback', callbackUrl: 'http://federated/auth/callback',
federatedJWT: { federatedJWT: {
issuer: 'https://external-idp.local', issuer: 'https://external-idp.local',
audience: 'api://PocketID', audience: 'api://PocketID',
subject: 'c48232ff-ff65-45ed-ae96-7afa8a9b443b', subject: 'c48232ff-ff65-45ed-ae96-7afa8a9b443b'
}, },
accessCodes: ['federated'], accessCodes: ['federated']
}, },
pingvinShare: { pingvinShare: {
name: 'Pingvin Share', name: 'Pingvin Share',
callbackUrl: 'http://pingvin.share/auth/callback', callbackUrl: 'http://pingvin.share/auth/callback',
secondCallbackUrl: 'http://pingvin.share/auth/callback2', secondCallbackUrl: 'http://pingvin.share/auth/callback2'
}, }
}; };
export const userGroups = { export const userGroups = {
developers: { developers: {
id: '4110f814-56f1-4b28-8998-752b69bc97c0e', id: '4110f814-56f1-4b28-8998-752b69bc97c0e',
friendlyName: 'Developers', friendlyName: 'Developers',
name: 'developers', name: 'developers'
}, },
designers: { designers: {
id: 'adab18bf-f89d-4087-9ee1-70ff15b48211', id: 'adab18bf-f89d-4087-9ee1-70ff15b48211',
friendlyName: 'Designers', friendlyName: 'Designers',
name: 'designers', name: 'designers'
}, },
humanResources: { humanResources: {
friendlyName: 'Human Resources', friendlyName: 'Human Resources',
name: 'human_resources', name: 'human_resources'
}, }
}; };
export const oneTimeAccessTokens = [ export const oneTimeAccessTokens = [
{ token: 'HPe6k6uiDRRVuAQV', expired: false }, { token: 'HPe6k6uiDRRVuAQV', expired: false },
{ token: 'YCGDtftvsvYWiXd0', expired: true }, { token: 'YCGDtftvsvYWiXd0', expired: true }
]; ];
export const apiKeys = [ export const apiKeys = [
{ {
id: '5f1fa856-c164-4295-961e-175a0d22d725', id: '5f1fa856-c164-4295-961e-175a0d22d725',
key: '6c34966f57ef2bb7857649aff0e7ab3ad67af93c846342ced3f5a07be8706c20', key: '6c34966f57ef2bb7857649aff0e7ab3ad67af93c846342ced3f5a07be8706c20',
name: 'Test API Key', name: 'Test API Key'
}, }
]; ];
export const refreshTokens = [ export const refreshTokens = [
{ {
token: 'ou87UDg249r1StBLYkMEqy9TXDbV5HmGuDpMcZDo', token: 'ou87UDg249r1StBLYkMEqy9TXDbV5HmGuDpMcZDo',
clientId: oidcClients.nextcloud.id, clientId: oidcClients.nextcloud.id,
userId: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e', userId: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e',
expired: false, expired: false
}, },
{ {
token: 'X4vqwtRyCUaq51UafHea4Fsg8Km6CAns6vp3tuX4', token: 'X4vqwtRyCUaq51UafHea4Fsg8Km6CAns6vp3tuX4',
clientId: oidcClients.nextcloud.id, clientId: oidcClients.nextcloud.id,
userId: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e', userId: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e',
expired: true, expired: true
}, }
]; ];
export const signupTokens = { export const signupTokens = {
valid: { valid: {
id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
token: 'VALID1234567890A', token: 'VALID1234567890A',
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
usageLimit: 1, usageLimit: 1,
usageCount: 0, usageCount: 0,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString()
}, },
partiallyUsed: { partiallyUsed: {
id: 'b2c3d4e5-f6g7-8901-bcde-f12345678901', id: 'b2c3d4e5-f6g7-8901-bcde-f12345678901',
token: 'PARTIAL567890ABC', token: 'PARTIAL567890ABC',
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
usageLimit: 5, usageLimit: 5,
usageCount: 2, usageCount: 2,
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
}, },
expired: { expired: {
id: 'c3d4e5f6-g7h8-9012-cdef-123456789012', id: 'c3d4e5f6-g7h8-9012-cdef-123456789012',
token: 'EXPIRED34567890B', token: 'EXPIRED34567890B',
expiresAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), expiresAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
usageLimit: 3, usageLimit: 3,
usageCount: 1, usageCount: 1,
createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString()
}, },
fullyUsed: { fullyUsed: {
id: 'd4e5f6g7-h8i9-0123-def0-234567890123', id: 'd4e5f6g7-h8i9-0123-def0-234567890123',
token: 'FULLYUSED567890C', token: 'FULLYUSED567890C',
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
usageLimit: 1, usageLimit: 1,
usageCount: 1, usageCount: 1,
createdAt: new Date(Date.now() - 1 * 60 * 60 * 1000).toISOString(), createdAt: new Date(Date.now() - 1 * 60 * 60 * 1000).toISOString()
}, }
}; };

230
tests/package-lock.json generated
View File

@@ -1,118 +1,116 @@
{ {
"name": "tests", "name": "tests",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.52.0", "@playwright/test": "^1.52.0",
"@types/node": "^22.15.21", "@types/node": "^22.15.21",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"jose": "^6.0.11" "jose": "^6.0.11",
} "prettier": "^3.6.2"
}, }
"node_modules/@playwright/test": { },
"version": "1.52.0", "node_modules/@playwright/test": {
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", "version": "1.52.0",
"integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", "dev": true,
"dev": true, "license": "Apache-2.0",
"license": "Apache-2.0", "dependencies": {
"dependencies": { "playwright": "1.52.0"
"playwright": "1.52.0" },
}, "bin": {
"bin": { "playwright": "cli.js"
"playwright": "cli.js" },
}, "engines": {
"engines": { "node": ">=18"
"node": ">=18" }
} },
}, "node_modules/@types/node": {
"node_modules/@types/node": { "version": "22.15.21",
"version": "22.15.21", "dev": true,
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", "license": "MIT",
"integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", "dependencies": {
"dev": true, "undici-types": "~6.21.0"
"license": "MIT", }
"dependencies": { },
"undici-types": "~6.21.0" "node_modules/dotenv": {
} "version": "16.5.0",
}, "dev": true,
"node_modules/dotenv": { "license": "BSD-2-Clause",
"version": "16.5.0", "engines": {
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "node": ">=12"
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", },
"dev": true, "funding": {
"license": "BSD-2-Clause", "url": "https://dotenvx.com"
"engines": { }
"node": ">=12" },
}, "node_modules/fsevents": {
"funding": { "version": "2.3.2",
"url": "https://dotenvx.com" "dev": true,
} "license": "MIT",
}, "optional": true,
"node_modules/fsevents": { "os": [
"version": "2.3.2", "darwin"
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", ],
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "engines": {
"dev": true, "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
"hasInstallScript": true, }
"license": "MIT", },
"optional": true, "node_modules/jose": {
"os": [ "version": "6.0.11",
"darwin" "dev": true,
], "license": "MIT",
"engines": { "funding": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "url": "https://github.com/sponsors/panva"
} }
}, },
"node_modules/jose": { "node_modules/playwright": {
"version": "6.0.11", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz", "dev": true,
"integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==", "license": "Apache-2.0",
"dev": true, "dependencies": {
"license": "MIT", "playwright-core": "1.52.0"
"funding": { },
"url": "https://github.com/sponsors/panva" "bin": {
} "playwright": "cli.js"
}, },
"node_modules/playwright": { "engines": {
"version": "1.52.0", "node": ">=18"
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", },
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", "optionalDependencies": {
"dev": true, "fsevents": "2.3.2"
"license": "Apache-2.0", }
"dependencies": { },
"playwright-core": "1.52.0" "node_modules/playwright-core": {
}, "version": "1.52.0",
"bin": { "dev": true,
"playwright": "cli.js" "license": "Apache-2.0",
}, "bin": {
"engines": { "playwright-core": "cli.js"
"node": ">=18" },
}, "engines": {
"optionalDependencies": { "node": ">=18"
"fsevents": "2.3.2" }
} },
}, "node_modules/prettier": {
"node_modules/playwright-core": { "version": "3.6.2",
"version": "1.52.0", "dev": true,
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", "license": "MIT",
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", "bin": {
"dev": true, "prettier": "bin/prettier.cjs"
"license": "Apache-2.0", },
"bin": { "engines": {
"playwright-core": "cli.js" "node": ">=14"
}, },
"engines": { "funding": {
"node": ">=18" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "dev": true,
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT"
"dev": true, }
"license": "MIT" }
}
}
} }

View File

@@ -1,9 +1,14 @@
{ {
"type": "module", "type": "module",
"devDependencies": { "scripts": {
"@playwright/test": "^1.52.0", "test": "playwright test",
"@types/node": "^22.15.21", "format": "prettier --write ."
"jose": "^6.0.11", },
"dotenv": "^16.5.0" "devDependencies": {
} "@playwright/test": "^1.52.0",
"@types/node": "^22.15.21",
"dotenv": "^16.5.0",
"jose": "^6.0.11",
"prettier": "^3.6.2"
}
} }

View File

@@ -1,31 +1,31 @@
import { defineConfig, devices } from "@playwright/test"; import { defineConfig, devices } from '@playwright/test';
import "dotenv/config"; import 'dotenv/config';
/** /**
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.
*/ */
export default defineConfig({ export default defineConfig({
outputDir: "./.output", outputDir: './.output',
timeout: 10000, timeout: 10000,
testDir: "./specs", testDir: './specs',
fullyParallel: false, fullyParallel: false,
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0, retries: process.env.CI ? 1 : 0,
workers: 1, workers: 1,
reporter: process.env.CI reporter: process.env.CI
? [["html", { outputFolder: ".report" }], ["github"]] ? [['html', { outputFolder: '.report' }], ['github']]
: [["line"], ["html", { open: "never", outputFolder: ".report" }]], : [['line'], ['html', { open: 'never', outputFolder: '.report' }]],
use: { use: {
baseURL: process.env.APP_URL ?? "http://localhost:1411", baseURL: process.env.APP_URL ?? 'http://localhost:1411',
video: "retain-on-failure", video: 'retain-on-failure',
trace: "on-first-retry", trace: 'on-first-retry'
}, },
projects: [ projects: [
{ name: "setup", testMatch: /.*\.setup\.ts/ }, { name: 'setup', testMatch: /.*\.setup\.ts/ },
{ {
name: "chromium", name: 'chromium',
use: { ...devices["Desktop Chrome"], storageState: ".auth/user.json" }, use: { ...devices['Desktop Chrome'], storageState: '.auth/user.json' },
dependencies: ["setup"], dependencies: ['setup']
}, }
], ]
}); });

View File

@@ -11,7 +11,7 @@ services:
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
- POSTGRES_DB=pocket-id - POSTGRES_DB=pocket-id
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"] test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -21,4 +21,4 @@ services:
service: pocket-id service: pocket-id
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy

View File

@@ -11,11 +11,11 @@ services:
pocket-id: pocket-id:
image: pocket-id:test image: pocket-id:test
ports: ports:
- "1411:1411" - '1411:1411'
environment: environment:
- APP_ENV=test - APP_ENV=test
build: build:
args: args:
- BUILD_TAGS=e2etest - BUILD_TAGS=e2etest
context: ../.. context: ../..
dockerfile: Dockerfile dockerfile: Dockerfile

View File

@@ -1,135 +1,116 @@
import test, { expect } from "@playwright/test"; import test, { expect } from '@playwright/test';
import { users } from "../data"; import { users } from '../data';
import authUtil from "../utils/auth.util"; import authUtil from '../utils/auth.util';
import { cleanupBackend } from "../utils/cleanup.util"; import { cleanupBackend } from '../utils/cleanup.util';
import passkeyUtil from "../utils/passkey.util"; import passkeyUtil from '../utils/passkey.util';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
test("Update account details", async ({ page }) => { test('Update account details', async ({ page }) => {
await page.goto("/settings/account"); await page.goto('/settings/account');
await page.getByLabel("First name").fill("Timothy"); await page.getByLabel('First name').fill('Timothy');
await page.getByLabel("Last name").fill("Apple"); await page.getByLabel('Last name').fill('Apple');
await page.getByLabel("Email").fill("timothy.apple@test.com"); await page.getByLabel('Email').fill('timothy.apple@test.com');
await page.getByLabel("Username").fill("timothy"); await page.getByLabel('Username').fill('timothy');
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"Account details updated successfully" 'Account details updated successfully'
); );
}); });
test("Update account details fails with already taken email", async ({ test('Update account details fails with already taken email', async ({ page }) => {
page, await page.goto('/settings/account');
}) => {
await page.goto("/settings/account");
await page.getByLabel("Email").fill(users.craig.email); await page.getByLabel('Email').fill(users.craig.email);
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
await expect(page.locator('[data-type="error"]')).toHaveText( await expect(page.locator('[data-type="error"]')).toHaveText('Email is already in use');
"Email is already in use"
);
}); });
test("Update account details fails with already taken username", async ({ test('Update account details fails with already taken username', async ({ page }) => {
page, await page.goto('/settings/account');
}) => {
await page.goto("/settings/account");
await page.getByLabel("Username").fill(users.craig.username); await page.getByLabel('Username').fill(users.craig.username);
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
await expect(page.locator('[data-type="error"]')).toHaveText( await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
"Username is already in use"
);
}); });
test("Change Locale", async ({ page }) => { test('Change Locale', async ({ page }) => {
await page.goto("/settings/account"); await page.goto('/settings/account');
await page.getByLabel("Select Locale").click(); await page.getByLabel('Select Locale').click();
await page.getByRole("option", { name: "Nederlands" }).click(); await page.getByRole('option', { name: 'Nederlands' }).click();
// Check if th language heading now says 'Taal' instead of 'Language' // Check if th language heading now says 'Taal' instead of 'Language'
await expect(page.getByText("Taal", { exact: true })).toBeVisible(); await expect(page.getByText('Taal', { exact: true })).toBeVisible();
// Check if the validation messages are translated because they are provided by Zod // Check if the validation messages are translated because they are provided by Zod
await page.getByRole("textbox", { name: "Voornaam" }).fill(""); await page.getByRole('textbox', { name: 'Voornaam' }).fill('');
await page.getByRole("button", { name: "Opslaan" }).click(); await page.getByRole('button', { name: 'Opslaan' }).click();
await expect(page.getByText("Te kort: verwacht dat string")).toBeVisible(); await expect(page.getByText('Te kort: verwacht dat string')).toBeVisible();
// Clear all cookies and sign in again to check if the language is still set to Dutch // Clear all cookies and sign in again to check if the language is still set to Dutch
await page.context().clearCookies(); await page.context().clearCookies();
await authUtil.authenticate(page); await authUtil.authenticate(page);
await expect(page.getByText("Taal", { exact: true })).toBeVisible(); await expect(page.getByText('Taal', { exact: true })).toBeVisible();
await page.getByRole("textbox", { name: "Voornaam" }).fill(""); await page.getByRole('textbox', { name: 'Voornaam' }).fill('');
await page.getByRole("button", { name: "Opslaan" }).click(); await page.getByRole('button', { name: 'Opslaan' }).click();
await expect(page.getByText("Te kort: verwacht dat string")).toBeVisible(); await expect(page.getByText('Te kort: verwacht dat string')).toBeVisible();
}); });
test("Add passkey to an account", async ({ page }) => { test('Add passkey to an account', async ({ page }) => {
await page.goto("/settings/account"); await page.goto('/settings/account');
await (await passkeyUtil.init(page)).addPasskey("timNew"); await (await passkeyUtil.init(page)).addPasskey('timNew');
await page.getByRole("button", { name: "Add Passkey" }).click(); await page.getByRole('button', { name: 'Add Passkey' }).click();
await page.getByLabel("Name", { exact: true }).fill("Test Passkey"); await page.getByLabel('Name', { exact: true }).fill('Test Passkey');
await page await page.getByLabel('Name Passkey').getByRole('button', { name: 'Save' }).click();
.getByLabel("Name Passkey")
.getByRole("button", { name: "Save" })
.click();
await expect(page.getByText("Test Passkey")).toBeVisible(); await expect(page.getByText('Test Passkey')).toBeVisible();
}); });
test("Rename passkey", async ({ page }) => { test('Rename passkey', async ({ page }) => {
await page.goto("/settings/account"); await page.goto('/settings/account');
await page.getByLabel("Rename").first().click(); await page.getByLabel('Rename').first().click();
await page.getByLabel("Name", { exact: true }).fill("Renamed Passkey"); await page.getByLabel('Name', { exact: true }).fill('Renamed Passkey');
await page await page.getByLabel('Name Passkey').getByRole('button', { name: 'Save' }).click();
.getByLabel("Name Passkey")
.getByRole("button", { name: "Save" })
.click();
await expect(page.getByText("Renamed Passkey")).toBeVisible(); await expect(page.getByText('Renamed Passkey')).toBeVisible();
}); });
test("Delete passkey from account", async ({ page }) => { test('Delete passkey from account', async ({ page }) => {
await page.goto("/settings/account"); await page.goto('/settings/account');
await page.getByLabel("Delete").first().click(); await page.getByLabel('Delete').first().click();
await page.getByText("Delete", { exact: true }).click(); await page.getByText('Delete', { exact: true }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('Passkey deleted successfully');
"Passkey deleted successfully"
);
}); });
test("Generate own one time access token as non admin", async ({ test('Generate own one time access token as non admin', async ({ page, context }) => {
page, await context.clearCookies();
context, await page.goto('/login');
}) => { await (await passkeyUtil.init(page)).addPasskey('craig');
await context.clearCookies();
await page.goto("/login");
await (await passkeyUtil.init(page)).addPasskey("craig");
await page.getByRole("button", { name: "Authenticate" }).click(); await page.getByRole('button', { name: 'Authenticate' }).click();
await page.waitForURL("/settings/account"); await page.waitForURL('/settings/account');
await page.getByRole("button", { name: "Create" }).click(); await page.getByRole('button', { name: 'Create' }).click();
const link = await page.getByTestId("login-code-link").textContent(); const link = await page.getByTestId('login-code-link').textContent();
await context.clearCookies(); await context.clearCookies();
await page.goto(link!); await page.goto(link!);
await page.waitForURL("/settings/account"); await page.waitForURL('/settings/account');
}); });

View File

@@ -1,79 +1,70 @@
// frontend/tests/api-key.spec.ts // frontend/tests/api-key.spec.ts
import { expect, test } from "@playwright/test"; import { expect, test } from '@playwright/test';
import { apiKeys } from "../data"; import { apiKeys } from '../data';
import { cleanupBackend } from "../utils/cleanup.util"; import { cleanupBackend } from '../utils/cleanup.util';
test.describe("API Key Management", () => { test.describe('API Key Management', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await cleanupBackend(); await cleanupBackend();
await page.goto("/settings/admin/api-keys"); await page.goto('/settings/admin/api-keys');
}); });
test("Create new API key", async ({ page }) => { test('Create new API key', async ({ page }) => {
await page.getByRole("button", { name: "Add API Key" }).click(); await page.getByRole('button', { name: 'Add API Key' }).click();
// Fill out the API key form // Fill out the API key form
const name = "New Test API Key"; const name = 'New Test API Key';
await page.getByLabel("Name").fill(name); await page.getByLabel('Name').fill(name);
await page.getByLabel("Description").fill("Created by automated test"); await page.getByLabel('Description').fill('Created by automated test');
// Choose the date // Choose the date
const currentDate = new Date(); const currentDate = new Date();
await page.getByRole("button", { name: "Select a date" }).click(); await page.getByRole('button', { name: 'Select a date' }).click();
await page.getByLabel("Select year").click(); await page.getByLabel('Select year').click();
// Select the next year // Select the next year
await page.getByText((currentDate.getFullYear() + 1).toString()).click(); await page.getByText((currentDate.getFullYear() + 1).toString()).click();
// Select the first day of the month // Select the first day of the month
await page await page
.getByRole("button", { name: /([A-Z][a-z]+), ([A-Z][a-z]+) 1, (\d{4})/ }) .getByRole('button', { name: /([A-Z][a-z]+), ([A-Z][a-z]+) 1, (\d{4})/ })
.first() .first()
.click(); .click();
// Submit the form // Submit the form
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
// Verify the success dialog appears // Verify the success dialog appears
await expect( await expect(page.getByRole('heading', { name: 'API Key Created' })).toBeVisible();
page.getByRole("heading", { name: "API Key Created" })
).toBeVisible();
// Verify the key details are shown // Verify the key details are shown
await expect(page.getByRole("cell", { name })).toBeVisible(); await expect(page.getByRole('cell', { name })).toBeVisible();
// Verify the token is displayed (should be 32 characters) // Verify the token is displayed (should be 32 characters)
const token = await page.locator(".font-mono").textContent(); const token = await page.locator('.font-mono').textContent();
expect(token?.length).toBe(32); expect(token?.length).toBe(32);
// Close the dialog // Close the dialog
await page await page.getByRole('button', { name: 'Close', exact: true }).nth(1).click();
.getByRole("button", { name: "Close", exact: true })
.nth(1)
.click();
await page.reload(); await page.reload();
// Verify the key appears in the list // Verify the key appears in the list
await expect(page.getByRole("cell", { name }).first()).toContainText(name); await expect(page.getByRole('cell', { name }).first()).toContainText(name);
}); });
test("Revoke API key", async ({ page }) => { test('Revoke API key', async ({ page }) => {
const apiKey = apiKeys[0]; const apiKey = apiKeys[0];
await page await page
.getByRole("row", { name: apiKey.name }) .getByRole('row', { name: apiKey.name })
.getByRole("button", { name: "Revoke" }) .getByRole('button', { name: 'Revoke' })
.click(); .click();
await page.getByText("Revoke", { exact: true }).click(); await page.getByText('Revoke', { exact: true }).click();
// Verify success message // Verify success message
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('API key revoked successfully');
"API key revoked successfully"
);
// Verify key is no longer in the list // Verify key is no longer in the list
await expect( await expect(page.getByRole('cell', { name: apiKey.name })).not.toBeVisible();
page.getByRole("cell", { name: apiKey.name }) });
).not.toBeVisible();
});
}); });

View File

@@ -1,99 +1,83 @@
import test, { expect } from "@playwright/test"; import test, { expect } from '@playwright/test';
import { cleanupBackend } from "../utils/cleanup.util"; import { cleanupBackend } from '../utils/cleanup.util';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
test("Update general configuration", async ({ page }) => { test('Update general configuration', async ({ page }) => {
await page.goto("/settings/admin/application-configuration"); await page.goto('/settings/admin/application-configuration');
await page await page.getByLabel('Application Name', { exact: true }).fill('Updated Name');
.getByLabel("Application Name", { exact: true }) await page.getByLabel('Session Duration').fill('30');
.fill("Updated Name"); await page.getByRole('button', { name: 'Save' }).first().click();
await page.getByLabel("Session Duration").fill("30");
await page.getByRole("button", { name: "Save" }).first().click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"Application configuration updated successfully" 'Application configuration updated successfully'
); );
await expect(page.getByTestId("application-name")).toHaveText("Updated Name"); await expect(page.getByTestId('application-name')).toHaveText('Updated Name');
await page.reload(); await page.reload();
await expect( await expect(page.getByLabel('Application Name', { exact: true })).toHaveValue('Updated Name');
page.getByLabel("Application Name", { exact: true }) await expect(page.getByLabel('Session Duration')).toHaveValue('30');
).toHaveValue("Updated Name");
await expect(page.getByLabel("Session Duration")).toHaveValue("30");
}); });
test("Update email configuration", async ({ page }) => { test('Update email configuration', async ({ page }) => {
await page.goto("/settings/admin/application-configuration"); await page.goto('/settings/admin/application-configuration');
await page.getByRole("button", { name: "Expand card" }).nth(1).click(); await page.getByRole('button', { name: 'Expand card' }).nth(1).click();
await page.getByLabel("SMTP Host").fill("smtp.gmail.com"); await page.getByLabel('SMTP Host').fill('smtp.gmail.com');
await page.getByLabel("SMTP Port").fill("587"); await page.getByLabel('SMTP Port').fill('587');
await page.getByLabel("SMTP User").fill("test@gmail.com"); await page.getByLabel('SMTP User').fill('test@gmail.com');
await page.getByLabel("SMTP Password").fill("password"); await page.getByLabel('SMTP Password').fill('password');
await page.getByLabel("SMTP From").fill("test@gmail.com"); await page.getByLabel('SMTP From').fill('test@gmail.com');
await page.getByLabel("Email Login Notification").click(); await page.getByLabel('Email Login Notification').click();
await page.getByLabel("Email Login Code Requested by User").click(); await page.getByLabel('Email Login Code Requested by User').click();
await page.getByLabel("Email Login Code from Admin").click(); await page.getByLabel('Email Login Code from Admin').click();
await page.getByLabel("API Key Expiration").click(); await page.getByLabel('API Key Expiration').click();
await page.getByRole("button", { name: "Save" }).nth(1).click(); await page.getByRole('button', { name: 'Save' }).nth(1).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"Email configuration updated successfully" 'Email configuration updated successfully'
); );
await page.reload(); await page.reload();
await expect(page.getByLabel("SMTP Host")).toHaveValue("smtp.gmail.com"); await expect(page.getByLabel('SMTP Host')).toHaveValue('smtp.gmail.com');
await expect(page.getByLabel("SMTP Port")).toHaveValue("587"); await expect(page.getByLabel('SMTP Port')).toHaveValue('587');
await expect(page.getByLabel("SMTP User")).toHaveValue("test@gmail.com"); await expect(page.getByLabel('SMTP User')).toHaveValue('test@gmail.com');
await expect(page.getByLabel("SMTP Password")).toHaveValue("password"); await expect(page.getByLabel('SMTP Password')).toHaveValue('password');
await expect(page.getByLabel("SMTP From")).toHaveValue("test@gmail.com"); await expect(page.getByLabel('SMTP From')).toHaveValue('test@gmail.com');
await expect(page.getByLabel("Email Login Notification")).toBeChecked(); await expect(page.getByLabel('Email Login Notification')).toBeChecked();
await expect( await expect(page.getByLabel('Email Login Code Requested by User')).toBeChecked();
page.getByLabel("Email Login Code Requested by User") await expect(page.getByLabel('Email Login Code from Admin')).toBeChecked();
).toBeChecked(); await expect(page.getByLabel('API Key Expiration')).toBeChecked();
await expect(page.getByLabel("Email Login Code from Admin")).toBeChecked();
await expect(page.getByLabel("API Key Expiration")).toBeChecked();
}); });
test("Update application images", async ({ page }) => { test('Update application images', async ({ page }) => {
await page.goto("/settings/admin/application-configuration"); await page.goto('/settings/admin/application-configuration');
await page.getByRole("button", { name: "Expand card" }).nth(3).click(); await page.getByRole('button', { name: 'Expand card' }).nth(3).click();
await page await page.getByLabel('Favicon').setInputFiles('assets/w3-schools-favicon.ico');
.getByLabel("Favicon") await page.getByLabel('Light Mode Logo').setInputFiles('assets/pingvin-share-logo.png');
.setInputFiles("assets/w3-schools-favicon.ico"); await page.getByLabel('Dark Mode Logo').setInputFiles('assets/nextcloud-logo.png');
await page await page.getByLabel('Background Image').setInputFiles('assets/clouds.jpg');
.getByLabel("Light Mode Logo") await page.getByRole('button', { name: 'Save' }).nth(1).click();
.setInputFiles("assets/pingvin-share-logo.png");
await page
.getByLabel("Dark Mode Logo")
.setInputFiles("assets/nextcloud-logo.png");
await page
.getByLabel("Background Image")
.setInputFiles("assets/clouds.jpg");
await page.getByRole("button", { name: "Save" }).nth(1).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('Images updated successfully');
"Images updated successfully"
);
await page.request await page.request
.get("/api/application-configuration/favicon") .get('/api/application-configuration/favicon')
.then((res) => expect.soft(res.status()).toBe(200)); .then((res) => expect.soft(res.status()).toBe(200));
await page.request await page.request
.get("/api/application-configuration/logo?light=true") .get('/api/application-configuration/logo?light=true')
.then((res) => expect.soft(res.status()).toBe(200)); .then((res) => expect.soft(res.status()).toBe(200));
await page.request await page.request
.get("/api/application-configuration/logo?light=false") .get('/api/application-configuration/logo?light=false')
.then((res) => expect.soft(res.status()).toBe(200)); .then((res) => expect.soft(res.status()).toBe(200));
await page.request await page.request
.get("/api/application-configuration/background-image") .get('/api/application-configuration/background-image')
.then((res) => expect.soft(res.status()).toBe(200)); .then((res) => expect.soft(res.status()).toBe(200));
}); });

View File

@@ -4,85 +4,94 @@ import { cleanupBackend } from '../utils/cleanup.util';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
test.describe('LDAP Integration', () => { test.describe('LDAP Integration', () => {
test.skip(process.env.SKIP_LDAP_TESTS === 'true', 'Skipping LDAP tests due to SKIP_LDAP_TESTS environment variable'); test.skip(
process.env.SKIP_LDAP_TESTS === 'true',
'Skipping LDAP tests due to SKIP_LDAP_TESTS environment variable'
);
test('LDAP configuration is working properly', async ({ page }) => { test('LDAP configuration is working properly', async ({ page }) => {
await page.goto('/settings/admin/application-configuration'); await page.goto('/settings/admin/application-configuration');
await page.getByRole('button', { name: 'Expand card' }).nth(2).click(); await page.getByRole('button', { name: 'Expand card' }).nth(2).click();
await expect(page.getByRole('button', { name: 'Disable', exact: true })).toBeVisible(); await expect(page.getByRole('button', { name: 'Disable', exact: true })).toBeVisible();
await expect(page.getByLabel('LDAP URL')).toHaveValue(/ldap:\/\/.*/); await expect(page.getByLabel('LDAP URL')).toHaveValue(/ldap:\/\/.*/);
await expect(page.getByLabel('LDAP Base DN')).not.toBeEmpty(); await expect(page.getByLabel('LDAP Base DN')).not.toBeEmpty();
await expect(page.getByLabel('User Unique Identifier Attribute')).not.toBeEmpty(); await expect(page.getByLabel('User Unique Identifier Attribute')).not.toBeEmpty();
await expect(page.getByLabel('Username Attribute')).not.toBeEmpty(); await expect(page.getByLabel('Username Attribute')).not.toBeEmpty();
await expect(page.getByLabel('User Mail Attribute')).not.toBeEmpty(); await expect(page.getByLabel('User Mail Attribute')).not.toBeEmpty();
await expect(page.getByLabel('Group Name Attribute')).not.toBeEmpty(); await expect(page.getByLabel('Group Name Attribute')).not.toBeEmpty();
const syncButton = page.getByRole('button', { name: 'Sync now' }); const syncButton = page.getByRole('button', { name: 'Sync now' });
await syncButton.click(); await syncButton.click();
await expect(page.locator('[data-type="success"]')).toHaveText('LDAP sync finished'); await expect(page.locator('[data-type="success"]')).toHaveText('LDAP sync finished');
}); });
test('LDAP users are synced into PocketID', async ({ page }) => { test('LDAP users are synced into PocketID', async ({ page }) => {
// Navigate to user management // Navigate to user management
await page.goto('/settings/admin/users'); await page.goto('/settings/admin/users');
// Verify the LDAP users exist // Verify the LDAP users exist
await expect(page.getByText('testuser1@pocket-id.org')).toBeVisible(); await expect(page.getByText('testuser1@pocket-id.org')).toBeVisible();
await expect(page.getByText('testuser2@pocket-id.org')).toBeVisible(); await expect(page.getByText('testuser2@pocket-id.org')).toBeVisible();
// Check LDAP user details // Check LDAP user details
await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click(); await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click();
await page.getByRole('menuitem', { name: 'Edit' }).click(); await page.getByRole('menuitem', { name: 'Edit' }).click();
// Verify user source is LDAP // Verify user source is LDAP
await expect(page.getByText('LDAP').first()).toBeVisible(); await expect(page.getByText('LDAP').first()).toBeVisible();
// Verify essential fields are filled // Verify essential fields are filled
await expect(page.getByLabel('Username')).not.toBeEmpty(); await expect(page.getByLabel('Username')).not.toBeEmpty();
await expect(page.getByLabel('Email')).not.toBeEmpty(); await expect(page.getByLabel('Email')).not.toBeEmpty();
}); });
test('LDAP groups are synced into PocketID', async ({ page }) => { test('LDAP groups are synced into PocketID', async ({ page }) => {
// Navigate to user groups // Navigate to user groups
await page.goto('/settings/admin/user-groups'); await page.goto('/settings/admin/user-groups');
// Verify LDAP groups exist // Verify LDAP groups exist
await expect(page.getByRole('cell', { name: 'test_group' }).first()).toBeVisible(); await expect(page.getByRole('cell', { name: 'test_group' }).first()).toBeVisible();
await expect(page.getByRole('cell', { name: 'admin_group' }).first()).toBeVisible(); await expect(page.getByRole('cell', { name: 'admin_group' }).first()).toBeVisible();
await page.getByRole('row', { name: 'test_group' }).getByRole('button', { name: 'Toggle menu' }).click(); await page
await page.getByRole('menuitem', { name: 'Edit' }).click(); .getByRole('row', { name: 'test_group' })
.getByRole('button', { name: 'Toggle menu' })
.click();
await page.getByRole('menuitem', { name: 'Edit' }).click();
// Verify group source is LDAP // Verify group source is LDAP
await expect(page.getByText('LDAP').first()).toBeVisible(); await expect(page.getByText('LDAP').first()).toBeVisible();
}); });
test('LDAP users cannot be modified in PocketID', async ({ page }) => { test('LDAP users cannot be modified in PocketID', async ({ page }) => {
// Navigate to LDAP user details // Navigate to LDAP user details
await page.goto('/settings/admin/users'); await page.goto('/settings/admin/users');
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click(); await page.getByRole('row', { name: 'testuser1' }).getByRole('button').click();
await page.getByRole('menuitem', { name: 'Edit' }).click(); await page.getByRole('menuitem', { name: 'Edit' }).click();
// Verify key fields are disabled // Verify key fields are disabled
const usernameInput = page.getByLabel('Username'); const usernameInput = page.getByLabel('Username');
await expect(usernameInput).toBeDisabled(); await expect(usernameInput).toBeDisabled();
}); });
test('LDAP groups cannot be modified in PocketID', async ({ page }) => { test('LDAP groups cannot be modified in PocketID', async ({ page }) => {
// Navigate to LDAP group details // Navigate to LDAP group details
await page.goto('/settings/admin/user-groups'); await page.goto('/settings/admin/user-groups');
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await page.getByRole('row', { name: 'test_group' }).getByRole('button', { name: 'Toggle menu' }).click(); await page
await page.getByRole('menuitem', { name: 'Edit' }).click(); .getByRole('row', { name: 'test_group' })
.getByRole('button', { name: 'Toggle menu' })
.click();
await page.getByRole('menuitem', { name: 'Edit' }).click();
// Verify key fields are disabled // Verify key fields are disabled
const nameInput = page.getByLabel('Name', { exact: true }); const nameInput = page.getByLabel('Name', { exact: true });
await expect(nameInput).toBeDisabled(); await expect(nameInput).toBeDisabled();
}); });
}); });

View File

@@ -1,100 +1,80 @@
import test, { expect } from "@playwright/test"; import test, { expect } from '@playwright/test';
import { oidcClients } from "../data"; import { oidcClients } from '../data';
import { cleanupBackend } from "../utils/cleanup.util"; import { cleanupBackend } from '../utils/cleanup.util';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
test("Create OIDC client", async ({ page }) => { test('Create OIDC client', async ({ page }) => {
await page.goto("/settings/admin/oidc-clients"); await page.goto('/settings/admin/oidc-clients');
const oidcClient = oidcClients.pingvinShare; const oidcClient = oidcClients.pingvinShare;
await page.getByRole("button", { name: "Add OIDC Client" }).click(); await page.getByRole('button', { name: 'Add OIDC Client' }).click();
await page.getByLabel("Name").fill(oidcClient.name); await page.getByLabel('Name').fill(oidcClient.name);
await page.getByRole("button", { name: "Add" }).nth(1).click(); await page.getByRole('button', { name: 'Add' }).nth(1).click();
await page.getByTestId("callback-url-1").fill(oidcClient.callbackUrl); await page.getByTestId('callback-url-1').fill(oidcClient.callbackUrl);
await page.getByRole("button", { name: "Add another" }).click(); await page.getByRole('button', { name: 'Add another' }).click();
await page.getByTestId("callback-url-2").fill(oidcClient.secondCallbackUrl!); await page.getByTestId('callback-url-2').fill(oidcClient.secondCallbackUrl!);
await page.getByLabel("logo").setInputFiles("assets/pingvin-share-logo.png"); await page.getByLabel('logo').setInputFiles('assets/pingvin-share-logo.png');
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
const clientId = await page.getByTestId("client-id").textContent(); const clientId = await page.getByTestId('client-id').textContent();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"OIDC client created successfully" 'OIDC client created successfully'
); );
expect(clientId?.length).toBe(36); expect(clientId?.length).toBe(36);
expect((await page.getByTestId("client-secret").textContent())?.length).toBe( expect((await page.getByTestId('client-secret').textContent())?.length).toBe(32);
32 await expect(page.getByLabel('Name')).toHaveValue(oidcClient.name);
); await expect(page.getByTestId('callback-url-1')).toHaveValue(oidcClient.callbackUrl);
await expect(page.getByLabel("Name")).toHaveValue(oidcClient.name); await expect(page.getByTestId('callback-url-2')).toHaveValue(oidcClient.secondCallbackUrl!);
await expect(page.getByTestId("callback-url-1")).toHaveValue( await expect(page.getByRole('img', { name: `${oidcClient.name} logo` })).toBeVisible();
oidcClient.callbackUrl await page.request
); .get(`/api/oidc/clients/${clientId}/logo`)
await expect(page.getByTestId("callback-url-2")).toHaveValue( .then((res) => expect.soft(res.status()).toBe(200));
oidcClient.secondCallbackUrl!
);
await expect(
page.getByRole("img", { name: `${oidcClient.name} logo` })
).toBeVisible();
await page.request
.get(`/api/oidc/clients/${clientId}/logo`)
.then((res) => expect.soft(res.status()).toBe(200));
}); });
test("Edit OIDC client", async ({ page }) => { test('Edit OIDC client', async ({ page }) => {
const oidcClient = oidcClients.nextcloud; const oidcClient = oidcClients.nextcloud;
await page.goto(`/settings/admin/oidc-clients/${oidcClient.id}`); await page.goto(`/settings/admin/oidc-clients/${oidcClient.id}`);
await page.getByLabel("Name").fill("Nextcloud updated"); await page.getByLabel('Name').fill('Nextcloud updated');
await page await page.getByTestId('callback-url-1').first().fill('http://nextcloud-updated/auth/callback');
.getByTestId("callback-url-1") await page.getByLabel('logo').setInputFiles('assets/nextcloud-logo.png');
.first() await page.getByRole('button', { name: 'Save' }).click();
.fill("http://nextcloud-updated/auth/callback");
await page.getByLabel("logo").setInputFiles("assets/nextcloud-logo.png");
await page.getByRole("button", { name: "Save" }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"OIDC client updated successfully" 'OIDC client updated successfully'
); );
await expect( await expect(page.getByRole('img', { name: 'Nextcloud updated logo' })).toBeVisible();
page.getByRole("img", { name: "Nextcloud updated logo" }) await page.request
).toBeVisible(); .get(`/api/oidc/clients/${oidcClient.id}/logo`)
await page.request .then((res) => expect.soft(res.status()).toBe(200));
.get(`/api/oidc/clients/${oidcClient.id}/logo`)
.then((res) => expect.soft(res.status()).toBe(200));
}); });
test("Create new OIDC client secret", async ({ page }) => { test('Create new OIDC client secret', async ({ page }) => {
const oidcClient = oidcClients.nextcloud; const oidcClient = oidcClients.nextcloud;
await page.goto(`/settings/admin/oidc-clients/${oidcClient.id}`); await page.goto(`/settings/admin/oidc-clients/${oidcClient.id}`);
await page.getByLabel("Create new client secret").click(); await page.getByLabel('Create new client secret').click();
await page.getByRole("button", { name: "Generate" }).click(); await page.getByRole('button', { name: 'Generate' }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"New client secret created successfully" 'New client secret created successfully'
); );
expect((await page.getByTestId("client-secret").textContent())?.length).toBe( expect((await page.getByTestId('client-secret').textContent())?.length).toBe(32);
32
);
}); });
test("Delete OIDC client", async ({ page }) => { test('Delete OIDC client', async ({ page }) => {
const oidcClient = oidcClients.nextcloud; const oidcClient = oidcClients.nextcloud;
await page.goto("/settings/admin/oidc-clients"); await page.goto('/settings/admin/oidc-clients');
await page await page.getByRole('row', { name: oidcClient.name }).getByLabel('Delete').click();
.getByRole("row", { name: oidcClient.name }) await page.getByText('Delete', { exact: true }).click();
.getByLabel("Delete")
.click();
await page.getByText("Delete", { exact: true }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"OIDC client deleted successfully" 'OIDC client deleted successfully'
); );
await expect( await expect(page.getByRole('row', { name: oidcClient.name })).not.toBeVisible();
page.getByRole("row", { name: oidcClient.name })
).not.toBeVisible();
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,48 @@
import test, { expect } from "@playwright/test"; import test, { expect } from '@playwright/test';
import { oneTimeAccessTokens } from "../data"; import { oneTimeAccessTokens } from '../data';
import { cleanupBackend } from "../utils/cleanup.util"; import { cleanupBackend } from '../utils/cleanup.util';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
// Disable authentication for these tests // Disable authentication for these tests
test.use({ storageState: { cookies: [], origins: [] } }); test.use({ storageState: { cookies: [], origins: [] } });
test("Sign in with login code", async ({ page }) => { test('Sign in with login code', async ({ page }) => {
const token = oneTimeAccessTokens.filter((t) => !t.expired)[0]; const token = oneTimeAccessTokens.filter((t) => !t.expired)[0];
await page.goto(`/lc/${token.token}`); await page.goto(`/lc/${token.token}`);
await page.waitForURL("/settings/account"); await page.waitForURL('/settings/account');
}); });
test("Sign in with login code entered manually", async ({ page }) => { test('Sign in with login code entered manually', async ({ page }) => {
const token = oneTimeAccessTokens.filter((t) => !t.expired)[0]; const token = oneTimeAccessTokens.filter((t) => !t.expired)[0];
await page.goto("/lc"); await page.goto('/lc');
await page.getByPlaceholder("Code").first().fill(token.token); await page.getByPlaceholder('Code').first().fill(token.token);
await page.getByText("Submit").first().click(); await page.getByText('Submit').first().click();
await page.waitForURL("/settings/account"); await page.waitForURL('/settings/account');
}); });
test("Sign in with expired login code fails", async ({ page }) => { test('Sign in with expired login code fails', async ({ page }) => {
const token = oneTimeAccessTokens.filter((t) => t.expired)[0]; const token = oneTimeAccessTokens.filter((t) => t.expired)[0];
await page.goto(`/lc/${token.token}`); await page.goto(`/lc/${token.token}`);
await expect(page.getByRole("paragraph")).toHaveText( await expect(page.getByRole('paragraph')).toHaveText(
"Token is invalid or expired. Please try again." 'Token is invalid or expired. Please try again.'
); );
}); });
test("Sign in with login code entered manually fails", async ({ page }) => { test('Sign in with login code entered manually fails', async ({ page }) => {
const token = oneTimeAccessTokens.filter((t) => t.expired)[0]; const token = oneTimeAccessTokens.filter((t) => t.expired)[0];
await page.goto("/lc"); await page.goto('/lc');
await page.getByPlaceholder("Code").first().fill(token.token); await page.getByPlaceholder('Code').first().fill(token.token);
await page.getByText("Submit").first().click(); await page.getByText('Submit').first().click();
await expect(page.getByRole("paragraph")).toHaveText( await expect(page.getByRole('paragraph')).toHaveText(
"Token is invalid or expired. Please try again." 'Token is invalid or expired. Please try again.'
); );
}); });

View File

@@ -1,152 +1,115 @@
import test, { expect } from "@playwright/test"; import test, { expect } from '@playwright/test';
import { userGroups, users } from "../data"; import { userGroups, users } from '../data';
import { cleanupBackend } from "../utils/cleanup.util"; import { cleanupBackend } from '../utils/cleanup.util';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
test("Create user group", async ({ page }) => { test('Create user group', async ({ page }) => {
await page.goto("/settings/admin/user-groups"); await page.goto('/settings/admin/user-groups');
const group = userGroups.humanResources; const group = userGroups.humanResources;
await page.getByRole("button", { name: "Add Group" }).click(); await page.getByRole('button', { name: 'Add Group' }).click();
await page.getByLabel("Friendly Name").fill(group.friendlyName); await page.getByLabel('Friendly Name').fill(group.friendlyName);
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('User group created successfully');
"User group created successfully"
);
await page.waitForURL("/settings/admin/user-groups/*"); await page.waitForURL('/settings/admin/user-groups/*');
await expect(page.getByLabel("Friendly Name")).toHaveValue( await expect(page.getByLabel('Friendly Name')).toHaveValue(group.friendlyName);
group.friendlyName await expect(page.getByLabel('Name', { exact: true })).toHaveValue(group.name);
);
await expect(page.getByLabel("Name", { exact: true })).toHaveValue(
group.name
);
}); });
test("Edit user group", async ({ page }) => { test('Edit user group', async ({ page }) => {
await page.goto("/settings/admin/user-groups"); await page.goto('/settings/admin/user-groups');
const group = userGroups.developers; const group = userGroups.developers;
await page await page.getByRole('row', { name: group.name }).getByRole('button').click();
.getByRole("row", { name: group.name }) await page.getByRole('menuitem', { name: 'Edit' }).click();
.getByRole("button")
.click();
await page.getByRole("menuitem", { name: "Edit" }).click();
await page.getByLabel("Friendly Name").fill("Developers updated"); await page.getByLabel('Friendly Name').fill('Developers updated');
await expect(page.getByLabel("Name", { exact: true })).toHaveValue( await expect(page.getByLabel('Name', { exact: true })).toHaveValue(group.name);
group.name
);
await page.getByLabel("Name", { exact: true }).fill("developers_updated"); await page.getByLabel('Name', { exact: true }).fill('developers_updated');
await page.getByRole("button", { name: "Save" }).nth(0).click(); await page.getByRole('button', { name: 'Save' }).nth(0).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('User group updated successfully');
"User group updated successfully" await expect(page.getByLabel('Friendly Name')).toHaveValue('Developers updated');
); await expect(page.getByLabel('Name', { exact: true })).toHaveValue('developers_updated');
await expect(page.getByLabel("Friendly Name")).toHaveValue(
"Developers updated"
);
await expect(page.getByLabel("Name", { exact: true })).toHaveValue(
"developers_updated"
);
}); });
test("Update user group users", async ({ page }) => { test('Update user group users', async ({ page }) => {
const group = userGroups.designers; const group = userGroups.designers;
await page.goto(`/settings/admin/user-groups/${group.id}`); await page.goto(`/settings/admin/user-groups/${group.id}`);
await page await page.getByRole('row', { name: users.tim.email }).getByRole('checkbox').click();
.getByRole("row", { name: users.tim.email }) await page.getByRole('row', { name: users.craig.email }).getByRole('checkbox').click();
.getByRole("checkbox")
.click();
await page
.getByRole("row", { name: users.craig.email })
.getByRole("checkbox")
.click();
await page.getByRole("button", { name: "Save" }).nth(1).click(); await page.getByRole('button', { name: 'Save' }).nth(1).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('Users updated successfully');
"Users updated successfully"
);
await page.reload(); await page.reload();
await expect( await expect(
page.getByRole("row", { name: users.tim.email }).getByRole("checkbox") page.getByRole('row', { name: users.tim.email }).getByRole('checkbox')
).toHaveAttribute("data-state", "unchecked"); ).toHaveAttribute('data-state', 'unchecked');
await expect( await expect(
page.getByRole("row", { name: users.craig.email }).getByRole("checkbox") page.getByRole('row', { name: users.craig.email }).getByRole('checkbox')
).toHaveAttribute("data-state", "checked"); ).toHaveAttribute('data-state', 'checked');
}); });
test("Delete user group", async ({ page }) => { test('Delete user group', async ({ page }) => {
const group = userGroups.developers; const group = userGroups.developers;
await page.goto("/settings/admin/user-groups"); await page.goto('/settings/admin/user-groups');
await page.getByRole("row", { name: group.name }).getByRole("button").click(); await page.getByRole('row', { name: group.name }).getByRole('button').click();
await page.getByRole("menuitem", { name: "Delete" }).click(); await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole("button", { name: "Delete" }).click(); await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('User group deleted successfully');
"User group deleted successfully" await expect(page.getByRole('row', { name: group.name })).not.toBeVisible();
);
await expect(page.getByRole("row", { name: group.name })).not.toBeVisible();
}); });
test("Update user group custom claims", async ({ page }) => { test('Update user group custom claims', async ({ page }) => {
await page.goto(`/settings/admin/user-groups/${userGroups.designers.id}`); await page.goto(`/settings/admin/user-groups/${userGroups.designers.id}`);
await page.getByRole("button", { name: "Expand card" }).click(); await page.getByRole('button', { name: 'Expand card' }).click();
// Add two custom claims // Add two custom claims
await page.getByRole("button", { name: "Add custom claim" }).click(); await page.getByRole('button', { name: 'Add custom claim' }).click();
await page.getByPlaceholder("Key").fill("customClaim1"); await page.getByPlaceholder('Key').fill('customClaim1');
await page.getByPlaceholder("Value").fill("customClaim1_value"); await page.getByPlaceholder('Value').fill('customClaim1_value');
await page.getByRole("button", { name: "Add another" }).click(); await page.getByRole('button', { name: 'Add another' }).click();
await page.getByPlaceholder("Key").nth(1).fill("customClaim2"); await page.getByPlaceholder('Key').nth(1).fill('customClaim2');
await page.getByPlaceholder("Value").nth(1).fill("customClaim2_value"); await page.getByPlaceholder('Value').nth(1).fill('customClaim2_value');
await page.getByRole("button", { name: "Save" }).nth(2).click(); await page.getByRole('button', { name: 'Save' }).nth(2).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"Custom claims updated successfully" 'Custom claims updated successfully'
); );
await page.reload(); await page.reload();
// Check if custom claims are saved // Check if custom claims are saved
await expect(page.getByPlaceholder("Key").first()).toHaveValue( await expect(page.getByPlaceholder('Key').first()).toHaveValue('customClaim1');
"customClaim1" await expect(page.getByPlaceholder('Value').first()).toHaveValue('customClaim1_value');
); await expect(page.getByPlaceholder('Key').nth(1)).toHaveValue('customClaim2');
await expect(page.getByPlaceholder("Value").first()).toHaveValue( await expect(page.getByPlaceholder('Value').nth(1)).toHaveValue('customClaim2_value');
"customClaim1_value"
);
await expect(page.getByPlaceholder("Key").nth(1)).toHaveValue("customClaim2");
await expect(page.getByPlaceholder("Value").nth(1)).toHaveValue(
"customClaim2_value"
);
// Remove one custom claim // Remove one custom claim
await page.getByLabel("Remove custom claim").first().click(); await page.getByLabel('Remove custom claim').first().click();
await page.getByRole("button", { name: "Save" }).nth(2).click(); await page.getByRole('button', { name: 'Save' }).nth(2).click();
await page.reload(); await page.reload();
// Check if custom claim is removed // Check if custom claim is removed
await expect(page.getByPlaceholder("Key").first()).toHaveValue( await expect(page.getByPlaceholder('Key').first()).toHaveValue('customClaim2');
"customClaim2" await expect(page.getByPlaceholder('Value').first()).toHaveValue('customClaim2_value');
);
await expect(page.getByPlaceholder("Value").first()).toHaveValue(
"customClaim2_value"
);
}); });

View File

@@ -1,253 +1,217 @@
import test, { expect } from "@playwright/test"; import test, { expect } from '@playwright/test';
import { userGroups, users } from "../data"; import { userGroups, users } from '../data';
import { cleanupBackend } from "../utils/cleanup.util"; import { cleanupBackend } from '../utils/cleanup.util';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
test("Create user", async ({ page }) => { test('Create user', async ({ page }) => {
const user = users.steve; const user = users.steve;
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page.getByRole("button", { name: "Add User" }).click(); await page.getByRole('button', { name: 'Add User' }).click();
await page.getByLabel("First name").fill(user.firstname); await page.getByLabel('First name').fill(user.firstname);
await page.getByLabel("Last name").fill(user.lastname); await page.getByLabel('Last name').fill(user.lastname);
await page.getByLabel("Email").fill(user.email); await page.getByLabel('Email').fill(user.email);
await page.getByLabel("Username").fill(user.username); await page.getByLabel('Username').fill(user.username);
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
await expect( await expect(page.getByRole('row', { name: `${user.firstname} ${user.lastname}` })).toBeVisible();
page.getByRole("row", { name: `${user.firstname} ${user.lastname}` }) await expect(page.locator('[data-type="success"]')).toHaveText('User created successfully');
).toBeVisible();
await expect(page.locator('[data-type="success"]')).toHaveText(
"User created successfully"
);
}); });
test("Create user fails with already taken email", async ({ page }) => { test('Create user fails with already taken email', async ({ page }) => {
const user = users.steve; const user = users.steve;
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page.getByRole("button", { name: "Add User" }).click(); await page.getByRole('button', { name: 'Add User' }).click();
await page.getByLabel("First name").fill(user.firstname); await page.getByLabel('First name').fill(user.firstname);
await page.getByLabel("Last name").fill(user.lastname); await page.getByLabel('Last name').fill(user.lastname);
await page.getByLabel("Email").fill(users.tim.email); await page.getByLabel('Email').fill(users.tim.email);
await page.getByLabel("Username").fill(user.username); await page.getByLabel('Username').fill(user.username);
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
await expect(page.locator('[data-type="error"]')).toHaveText( await expect(page.locator('[data-type="error"]')).toHaveText('Email is already in use');
"Email is already in use"
);
}); });
test("Create user fails with already taken username", async ({ page }) => { test('Create user fails with already taken username', async ({ page }) => {
const user = users.steve; const user = users.steve;
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page.getByRole("button", { name: "Add User" }).click(); await page.getByRole('button', { name: 'Add User' }).click();
await page.getByLabel("First name").fill(user.firstname); await page.getByLabel('First name').fill(user.firstname);
await page.getByLabel("Last name").fill(user.lastname); await page.getByLabel('Last name').fill(user.lastname);
await page.getByLabel("Email").fill(user.email); await page.getByLabel('Email').fill(user.email);
await page.getByLabel("Username").fill(users.tim.username); await page.getByLabel('Username').fill(users.tim.username);
await page.getByRole("button", { name: "Save" }).click(); await page.getByRole('button', { name: 'Save' }).click();
await expect(page.locator('[data-type="error"]')).toHaveText( await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
"Username is already in use"
);
}); });
test("Create one time access token", async ({ page, context }) => { test('Create one time access token', async ({ page, context }) => {
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page await page
.getByRole("row", { .getByRole('row', {
name: `${users.craig.firstname} ${users.craig.lastname}`, name: `${users.craig.firstname} ${users.craig.lastname}`
}) })
.getByRole("button") .getByRole('button')
.click(); .click();
await page.getByRole("menuitem", { name: "Login Code" }).click(); await page.getByRole('menuitem', { name: 'Login Code' }).click();
await page.getByLabel("Expiration").click(); await page.getByLabel('Expiration').click();
await page.getByRole("option", { name: "12 hours" }).click(); await page.getByRole('option', { name: '12 hours' }).click();
await page.getByRole("button", { name: "Show Code" }).click(); await page.getByRole('button', { name: 'Show Code' }).click();
const link = await page.getByTestId("login-code-link").textContent(); const link = await page.getByTestId('login-code-link').textContent();
await context.clearCookies(); await context.clearCookies();
await page.goto(link!); await page.goto(link!);
await page.waitForURL("/settings/account"); await page.waitForURL('/settings/account');
}); });
test("Delete user", async ({ page }) => { test('Delete user', async ({ page }) => {
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page await page
.getByRole("row", { .getByRole('row', {
name: `${users.craig.firstname} ${users.craig.lastname}`, name: `${users.craig.firstname} ${users.craig.lastname}`
}) })
.getByRole("button") .getByRole('button')
.click(); .click();
await page.getByRole("menuitem", { name: "Delete" }).click(); await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole("button", { name: "Delete" }).click(); await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('User deleted successfully');
"User deleted successfully" await expect(
); page.getByRole('row', {
await expect( name: `${users.craig.firstname} ${users.craig.lastname}`
page.getByRole("row", { })
name: `${users.craig.firstname} ${users.craig.lastname}`, ).not.toBeVisible();
})
).not.toBeVisible();
}); });
test("Update user", async ({ page }) => { test('Update user', async ({ page }) => {
const user = users.craig; const user = users.craig;
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page await page
.getByRole("row", { name: `${user.firstname} ${user.lastname}` }) .getByRole('row', { name: `${user.firstname} ${user.lastname}` })
.getByRole("button") .getByRole('button')
.click(); .click();
await page.getByRole("menuitem", { name: "Edit" }).click(); await page.getByRole('menuitem', { name: 'Edit' }).click();
await page.getByLabel("First name").fill("Crack"); await page.getByLabel('First name').fill('Crack');
await page.getByLabel("Last name").fill("Apple"); await page.getByLabel('Last name').fill('Apple');
await page.getByLabel("Email").fill("crack.apple@test.com"); await page.getByLabel('Email').fill('crack.apple@test.com');
await page.getByLabel("Username").fill("crack"); await page.getByLabel('Username').fill('crack');
await page.getByRole("button", { name: "Save" }).first().click(); await page.getByRole('button', { name: 'Save' }).first().click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText('User updated successfully');
"User updated successfully"
);
}); });
test("Update user fails with already taken email", async ({ page }) => { test('Update user fails with already taken email', async ({ page }) => {
const user = users.craig; const user = users.craig;
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page await page
.getByRole("row", { name: `${user.firstname} ${user.lastname}` }) .getByRole('row', { name: `${user.firstname} ${user.lastname}` })
.getByRole("button") .getByRole('button')
.click(); .click();
await page.getByRole("menuitem", { name: "Edit" }).click(); await page.getByRole('menuitem', { name: 'Edit' }).click();
await page.getByLabel("Email").fill(users.tim.email); await page.getByLabel('Email').fill(users.tim.email);
await page.getByRole("button", { name: "Save" }).first().click(); await page.getByRole('button', { name: 'Save' }).first().click();
await expect(page.locator('[data-type="error"]')).toHaveText( await expect(page.locator('[data-type="error"]')).toHaveText('Email is already in use');
"Email is already in use"
);
}); });
test("Update user fails with already taken username", async ({ page }) => { test('Update user fails with already taken username', async ({ page }) => {
const user = users.craig; const user = users.craig;
await page.goto("/settings/admin/users"); await page.goto('/settings/admin/users');
await page await page
.getByRole("row", { name: `${user.firstname} ${user.lastname}` }) .getByRole('row', { name: `${user.firstname} ${user.lastname}` })
.getByRole("button") .getByRole('button')
.click(); .click();
await page.getByRole("menuitem", { name: "Edit" }).click(); await page.getByRole('menuitem', { name: 'Edit' }).click();
await page.getByLabel("Username").fill(users.tim.username); await page.getByLabel('Username').fill(users.tim.username);
await page.getByRole("button", { name: "Save" }).first().click(); await page.getByRole('button', { name: 'Save' }).first().click();
await expect(page.locator('[data-type="error"]')).toHaveText( await expect(page.locator('[data-type="error"]')).toHaveText('Username is already in use');
"Username is already in use"
);
}); });
test("Update user custom claims", async ({ page }) => { test('Update user custom claims', async ({ page }) => {
await page.goto(`/settings/admin/users/${users.craig.id}`); await page.goto(`/settings/admin/users/${users.craig.id}`);
await page.getByRole("button", { name: "Expand card" }).nth(1).click(); await page.getByRole('button', { name: 'Expand card' }).nth(1).click();
// Add two custom claims // Add two custom claims
await page.getByRole("button", { name: "Add custom claim" }).click(); await page.getByRole('button', { name: 'Add custom claim' }).click();
await page.getByPlaceholder("Key").fill("customClaim1"); await page.getByPlaceholder('Key').fill('customClaim1');
await page.getByPlaceholder("Value").fill("customClaim1_value"); await page.getByPlaceholder('Value').fill('customClaim1_value');
await page.getByRole("button", { name: "Add another" }).click(); await page.getByRole('button', { name: 'Add another' }).click();
await page.getByPlaceholder("Key").nth(1).fill("customClaim2"); await page.getByPlaceholder('Key').nth(1).fill('customClaim2');
await page.getByPlaceholder("Value").nth(1).fill("customClaim2_value"); await page.getByPlaceholder('Value').nth(1).fill('customClaim2_value');
await page.getByRole("button", { name: "Save" }).nth(1).click(); await page.getByRole('button', { name: 'Save' }).nth(1).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"Custom claims updated successfully" 'Custom claims updated successfully'
); );
await page.reload(); await page.reload();
// Check if custom claims are saved // Check if custom claims are saved
await expect(page.getByPlaceholder("Key").first()).toHaveValue( await expect(page.getByPlaceholder('Key').first()).toHaveValue('customClaim1');
"customClaim1" await expect(page.getByPlaceholder('Value').first()).toHaveValue('customClaim1_value');
); await expect(page.getByPlaceholder('Key').nth(1)).toHaveValue('customClaim2');
await expect(page.getByPlaceholder("Value").first()).toHaveValue( await expect(page.getByPlaceholder('Value').nth(1)).toHaveValue('customClaim2_value');
"customClaim1_value"
);
await expect(page.getByPlaceholder("Key").nth(1)).toHaveValue("customClaim2");
await expect(page.getByPlaceholder("Value").nth(1)).toHaveValue(
"customClaim2_value"
);
// Remove one custom claim // Remove one custom claim
await page.getByLabel("Remove custom claim").first().click(); await page.getByLabel('Remove custom claim').first().click();
await page.getByRole("button", { name: "Save" }).nth(1).click(); await page.getByRole('button', { name: 'Save' }).nth(1).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"Custom claims updated successfully" 'Custom claims updated successfully'
); );
await page.reload(); await page.reload();
// Check if custom claim is removed // Check if custom claim is removed
await expect(page.getByPlaceholder("Key").first()).toHaveValue( await expect(page.getByPlaceholder('Key').first()).toHaveValue('customClaim2');
"customClaim2" await expect(page.getByPlaceholder('Value').first()).toHaveValue('customClaim2_value');
);
await expect(page.getByPlaceholder("Value").first()).toHaveValue(
"customClaim2_value"
);
}); });
test("Update user group assignments", async ({ page }) => { test('Update user group assignments', async ({ page }) => {
const user = users.craig; const user = users.craig;
await page.goto(`/settings/admin/users/${user.id}`); await page.goto(`/settings/admin/users/${user.id}`);
page.getByRole("button", { name: "Expand card" }).first().click(); page.getByRole('button', { name: 'Expand card' }).first().click();
await page await page.getByRole('row', { name: userGroups.developers.name }).getByRole('checkbox').click();
.getByRole("row", { name: userGroups.developers.name }) await page.getByRole('row', { name: userGroups.designers.name }).getByRole('checkbox').click();
.getByRole("checkbox")
.click();
await page
.getByRole("row", { name: userGroups.designers.name })
.getByRole("checkbox")
.click();
await page.getByRole("button", { name: "Save" }).nth(1).click(); await page.getByRole('button', { name: 'Save' }).nth(1).click();
await expect(page.locator('[data-type="success"]')).toHaveText( await expect(page.locator('[data-type="success"]')).toHaveText(
"User groups updated successfully" 'User groups updated successfully'
); );
await page.reload(); await page.reload();
await expect( await expect(
page page.getByRole('row', { name: userGroups.designers.name }).getByRole('checkbox')
.getByRole("row", { name: userGroups.designers.name }) ).toHaveAttribute('data-state', 'checked');
.getByRole("checkbox") await expect(
).toHaveAttribute("data-state", "checked"); page.getByRole('row', { name: userGroups.developers.name }).getByRole('checkbox')
await expect( ).toHaveAttribute('data-state', 'unchecked');
page
.getByRole("row", { name: userGroups.developers.name })
.getByRole("checkbox")
).toHaveAttribute("data-state", "unchecked");
}); });

View File

@@ -6,170 +6,174 @@ import { users, signupTokens } from 'data';
test.beforeEach(cleanupBackend); test.beforeEach(cleanupBackend);
test.describe('User Signup', () => { test.describe('User Signup', () => {
async function setSignupMode(page: any, mode: 'Disabled' | 'Signup with token' | 'Open Signup') { async function setSignupMode(page: any, mode: 'Disabled' | 'Signup with token' | 'Open Signup') {
await page.goto('/settings/admin/application-configuration'); await page.goto('/settings/admin/application-configuration');
await page.getByLabel('Enable user signups').click(); await page.getByLabel('Enable user signups').click();
await page.getByRole('option', { name: mode }).click(); await page.getByRole('option', { name: mode }).click();
await page.getByRole('button', { name: 'Save' }).first().click(); await page.getByRole('button', { name: 'Save' }).first().click();
await expect(page.locator('[data-type="success"]')).toHaveText('Application configuration updated successfully'); await expect(page.locator('[data-type="success"]')).toHaveText(
await page.waitForLoadState('networkidle'); 'Application configuration updated successfully'
);
await page.waitForLoadState('networkidle');
await page.context().clearCookies(); await page.context().clearCookies();
await page.goto('/login'); await page.goto('/login');
} }
test('Signup is disabled - shows error message', async ({ page }) => { test('Signup is disabled - shows error message', async ({ page }) => {
await setSignupMode(page, 'Disabled'); await setSignupMode(page, 'Disabled');
await page.goto('/signup'); await page.goto('/signup');
await expect(page.getByText('User signups are currently disabled')).toBeVisible(); await expect(page.getByText('User signups are currently disabled')).toBeVisible();
}); });
test('Signup with token - success flow', async ({ page }) => { test('Signup with token - success flow', async ({ page }) => {
await setSignupMode(page, 'Signup with token'); await setSignupMode(page, 'Signup with token');
await page.goto(`/st/${signupTokens.valid.token}`); await page.goto(`/st/${signupTokens.valid.token}`);
await page.getByLabel('First name').fill('John'); await page.getByLabel('First name').fill('John');
await page.getByLabel('Last name').fill('Doe'); await page.getByLabel('Last name').fill('Doe');
await page.getByLabel('Username').fill('johndoe'); await page.getByLabel('Username').fill('johndoe');
await page.getByLabel('Email').fill('john.doe@test.com'); await page.getByLabel('Email').fill('john.doe@test.com');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await page.waitForURL('/signup/add-passkey'); await page.waitForURL('/signup/add-passkey');
await expect(page.getByText('Set up your passkey')).toBeVisible(); await expect(page.getByText('Set up your passkey')).toBeVisible();
}); });
test('Signup with token - invalid token shows error', async ({ page }) => { test('Signup with token - invalid token shows error', async ({ page }) => {
await setSignupMode(page, 'Signup with token'); await setSignupMode(page, 'Signup with token');
await page.goto('/st/invalid-token-123'); await page.goto('/st/invalid-token-123');
await page.getByLabel('First name').fill('Complete'); await page.getByLabel('First name').fill('Complete');
await page.getByLabel('Last name').fill('User'); await page.getByLabel('Last name').fill('User');
await page.getByLabel('Username').fill('completeuser'); await page.getByLabel('Username').fill('completeuser');
await page.getByLabel('Email').fill('complete.user@test.com'); await page.getByLabel('Email').fill('complete.user@test.com');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText('Token is invalid or expired.')).toBeVisible(); await expect(page.getByText('Token is invalid or expired.')).toBeVisible();
}); });
test('Signup with token - no token in URL shows error', async ({ page }) => { test('Signup with token - no token in URL shows error', async ({ page }) => {
await setSignupMode(page, 'Signup with token'); await setSignupMode(page, 'Signup with token');
await page.goto('/signup'); await page.goto('/signup');
await expect(page.getByText('A valid signup token is required to create an account.')).toBeVisible(); await expect(
}); page.getByText('A valid signup token is required to create an account.')
).toBeVisible();
});
test('Open signup - success flow', async ({ page }) => { test('Open signup - success flow', async ({ page }) => {
await setSignupMode(page, 'Open Signup'); await setSignupMode(page, 'Open Signup');
await page.goto('/signup'); await page.goto('/signup');
await expect(page.getByText('Create your account to get started')).toBeVisible(); await expect(page.getByText('Create your account to get started')).toBeVisible();
await page.getByLabel('First name').fill('Jane'); await page.getByLabel('First name').fill('Jane');
await page.getByLabel('Last name').fill('Smith'); await page.getByLabel('Last name').fill('Smith');
await page.getByLabel('Username').fill('janesmith'); await page.getByLabel('Username').fill('janesmith');
await page.getByLabel('Email').fill('jane.smith@test.com'); await page.getByLabel('Email').fill('jane.smith@test.com');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await page.waitForURL('/signup/add-passkey'); await page.waitForURL('/signup/add-passkey');
await expect(page.getByText('Set up your passkey')).toBeVisible(); await expect(page.getByText('Set up your passkey')).toBeVisible();
}); });
test('Open signup - validation errors', async ({ page }) => { test('Open signup - validation errors', async ({ page }) => {
await setSignupMode(page, 'Open Signup'); await setSignupMode(page, 'Open Signup');
await page.goto('/signup'); await page.goto('/signup');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText('Invalid input').first()).toBeVisible(); await expect(page.getByText('Invalid input').first()).toBeVisible();
}); });
test('Open signup - duplicate email shows error', async ({ page }) => { test('Open signup - duplicate email shows error', async ({ page }) => {
await setSignupMode(page, 'Open Signup'); await setSignupMode(page, 'Open Signup');
await page.goto('/signup'); await page.goto('/signup');
await page.getByLabel('First name').fill('Test'); await page.getByLabel('First name').fill('Test');
await page.getByLabel('Last name').fill('User'); await page.getByLabel('Last name').fill('User');
await page.getByLabel('Username').fill('testuser123'); await page.getByLabel('Username').fill('testuser123');
await page.getByLabel('Email').fill(users.tim.email); await page.getByLabel('Email').fill(users.tim.email);
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText('Email is already in use.')).toBeVisible(); await expect(page.getByText('Email is already in use.')).toBeVisible();
}); });
test('Open signup - duplicate username shows error', async ({ page }) => { test('Open signup - duplicate username shows error', async ({ page }) => {
await setSignupMode(page, 'Open Signup'); await setSignupMode(page, 'Open Signup');
await page.goto('/signup'); await page.goto('/signup');
await page.getByLabel('First name').fill('Test'); await page.getByLabel('First name').fill('Test');
await page.getByLabel('Last name').fill('User'); await page.getByLabel('Last name').fill('User');
await page.getByLabel('Username').fill(users.tim.username); await page.getByLabel('Username').fill(users.tim.username);
await page.getByLabel('Email').fill('newuser@test.com'); await page.getByLabel('Email').fill('newuser@test.com');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText('Username is already in use.')).toBeVisible(); await expect(page.getByText('Username is already in use.')).toBeVisible();
}); });
test('Complete signup flow with passkey creation', async ({ page }) => { test('Complete signup flow with passkey creation', async ({ page }) => {
await setSignupMode(page, 'Open Signup'); await setSignupMode(page, 'Open Signup');
await page.goto('/signup'); await page.goto('/signup');
await page.getByLabel('First name').fill('Complete'); await page.getByLabel('First name').fill('Complete');
await page.getByLabel('Last name').fill('User'); await page.getByLabel('Last name').fill('User');
await page.getByLabel('Username').fill('completeuser'); await page.getByLabel('Username').fill('completeuser');
await page.getByLabel('Email').fill('complete.user@test.com'); await page.getByLabel('Email').fill('complete.user@test.com');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await page.waitForURL('/signup/add-passkey'); await page.waitForURL('/signup/add-passkey');
await (await passkeyUtil.init(page)).addPasskey('timNew'); await (await passkeyUtil.init(page)).addPasskey('timNew');
await page.getByRole('button', { name: 'Add Passkey' }).click(); await page.getByRole('button', { name: 'Add Passkey' }).click();
await page.waitForURL('/settings/account'); await page.waitForURL('/settings/account');
await expect(page.getByText('Single Passkey Configured')).toBeVisible(); await expect(page.getByText('Single Passkey Configured')).toBeVisible();
}); });
test('Skip passkey creation during signup', async ({ page }) => { test('Skip passkey creation during signup', async ({ page }) => {
await setSignupMode(page, 'Open Signup'); await setSignupMode(page, 'Open Signup');
await page.goto('/signup'); await page.goto('/signup');
await page.getByLabel('First name').fill('Skip'); await page.getByLabel('First name').fill('Skip');
await page.getByLabel('Last name').fill('User'); await page.getByLabel('Last name').fill('User');
await page.getByLabel('Username').fill('skipuser'); await page.getByLabel('Username').fill('skipuser');
await page.getByLabel('Email').fill('skip.user@test.com'); await page.getByLabel('Email').fill('skip.user@test.com');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await page.waitForURL('/signup/add-passkey'); await page.waitForURL('/signup/add-passkey');
await page.getByRole('button', { name: 'Skip for now' }).click(); await page.getByRole('button', { name: 'Skip for now' }).click();
await page.waitForURL('/settings/account'); await page.waitForURL('/settings/account');
await expect(page.getByText('Passkey missing')).toBeVisible(); await expect(page.getByText('Passkey missing')).toBeVisible();
}); });
test('Token usage limit is enforced', async ({ page }) => { test('Token usage limit is enforced', async ({ page }) => {
await setSignupMode(page, 'Signup with token'); await setSignupMode(page, 'Signup with token');
await page.goto(`/st/${signupTokens.fullyUsed.token}`); await page.goto(`/st/${signupTokens.fullyUsed.token}`);
await page.getByLabel('First name').fill('Complete'); await page.getByLabel('First name').fill('Complete');
await page.getByLabel('Last name').fill('User'); await page.getByLabel('Last name').fill('User');
await page.getByLabel('Username').fill('completeuser'); await page.getByLabel('Username').fill('completeuser');
await page.getByLabel('Email').fill('complete.user@test.com'); await page.getByLabel('Email').fill('complete.user@test.com');
await page.getByRole('button', { name: 'Sign Up' }).click(); await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText('Token is invalid or expired.')).toBeVisible(); await expect(page.getByText('Token is invalid or expired.')).toBeVisible();
}); });
}); });

View File

@@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"lib": ["ES2022"] "lib": ["ES2022"]
} }
} }

View File

@@ -1,19 +1,17 @@
import playwrightConfig from "../playwright.config"; import playwrightConfig from '../playwright.config';
export async function cleanupBackend() { export async function cleanupBackend() {
const url = new URL("/api/test/reset", playwrightConfig.use!.baseURL); const url = new URL('/api/test/reset', playwrightConfig.use!.baseURL);
if (process.env.SKIP_LDAP_TESTS === "true") { if (process.env.SKIP_LDAP_TESTS === 'true') {
url.searchParams.append("skip-ldap", "true"); url.searchParams.append('skip-ldap', 'true');
} }
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: 'POST'
}); });
if (!response.ok) { if (!response.ok) {
throw new Error( throw new Error(`Failed to reset backend: ${response.status} ${response.statusText}`);
`Failed to reset backend: ${response.status} ${response.statusText}` }
);
}
} }

View File

@@ -1,64 +1,56 @@
import * as jose from "jose"; import * as jose from 'jose';
import playwrightConfig from "../playwright.config"; import playwrightConfig from '../playwright.config';
const PRIVATE_KEY_STRING = `{"alg":"RS256","d":"mvMDWSdPPvcum0c0iEHE2gbqtV2NKMmLwrl9E6K7g8lTV95SePLnW_bwyMPV7EGp7PQk3l17I5XRhFjze7GqTnFIOgKzMianPs7jv2ELtBMGK0xOPATgu1iGb70xZ6vcvuEfRyY3dJ0zr4jpUdVuXwKmx9rK4IdZn2dFCKfvSuspqIpz11RhF1ALrqDLkxGVv7ZwNh0_VhJZU9hcjG5l6xc7rQEKpPRkZp0IdjkGS8Z0FskoVaiRIWAbZuiVFB9WCW8k1czC4HQTPLpII01bUQx2ludbm0UlXRgVU9ptUUbU7GAImQqTOW8LfPGklEvcgzlIlR_oqw4P9yBxLi-yMQ","dp":"pvNCSnnhbo8Igw9psPR-DicxFnkXlu_ix4gpy6efTrxA-z1VDFDioJ814vKQNioYDzpyAP1gfMPhRkvG_q0hRZsJah3Sb9dfA-WkhSWY7lURQP4yIBTMU0PF_rEATuS7lRciYk1SOx5fqXZd3m_LP0vpBC4Ujlq6NAq6CIjCnms","dq":"TtUVGCCkPNgfOLmkYXu7dxxUCV5kB01-xAEK2OY0n0pG8vfDophH4_D_ZC7nvJ8J9uDhs_3JStexq1lIvaWtG99RNTChIEDzpdn6GH9yaVcb_eB4uJjrNm64FhF8PGCCwxA-xMCZMaARKwhMB2_IOMkxUbWboL3gnhJ2rDO_QO0","e":"AQAB","kid":"8uHDw3M6rf8","kty":"RSA","n":"yaeEL0VKoPBXIAaWXsUgmu05lAvEIIdJn0FX9lHh4JE5UY9B83C5sCNdhs9iSWzpeP11EVjWp8i3Yv2CF7c7u50BXnVBGtxpZpFC-585UXacoJ0chUmarL9GRFJcM1nPHBTFu68aRrn1rIKNHUkNaaxFo0NFGl_4EDDTO8HwawTjwkPoQlRzeByhlvGPVvwgB3Fn93B8QJ_cZhXKxJvjjrC_8Pk76heC_ntEMru71Ix77BoC3j2TuyiN7m9RNBW8BU5q6lKoIdvIeZfTFLzi37iufyfvMrJTixp9zhNB1NxlLCeOZl2MXegtiGqd2H3cbAyqoOiv9ihUWTfXj7SxJw","p":"_Yylc9e07CKdqNRD2EosMC2mrhrEa9j5oY_l00Qyy4-jmCA59Q9viyqvveRo0U7cRvFA5BWgWN6GGLh1DG3X-QBqVr0dnk3uzbobb55RYUXyPLuBZI2q6w2oasbiDwPdY7KpkVv_H-bpITQlyDvO8hhucA6rUV7F6KTQVz8M3Ms","q":"y5p3hch-7jJ21TkAhp_Vk1fLCAuD4tbErwQs2of9ja8sB4iJOs5Wn6HD3P7Mc8Plye7qaLHvzc8I5g0tPKWvC0DPd_FLPXiWwMVAzee3NUX_oGeJNOQp11y1w_KqdO9qZqHSEPZ3NcFL_SZMFgggxhM1uzRiPzsVN0lnD_6prZU","qi":"2Grt6uXHm61ji3xSdkBWNtUnj19vS1-7rFJp5SoYztVQVThf_W52BAiXKBdYZDRVoItC_VS2NvAOjeJjhYO_xQ_q3hK7MdtuXfEPpLnyXKkmWo3lrJ26wbeF6l05LexCkI7ShsOuSt-dsyaTJTszuKDIA6YOfWvfo3aVZmlWRaI","use":"sig"}`; const PRIVATE_KEY_STRING = `{"alg":"RS256","d":"mvMDWSdPPvcum0c0iEHE2gbqtV2NKMmLwrl9E6K7g8lTV95SePLnW_bwyMPV7EGp7PQk3l17I5XRhFjze7GqTnFIOgKzMianPs7jv2ELtBMGK0xOPATgu1iGb70xZ6vcvuEfRyY3dJ0zr4jpUdVuXwKmx9rK4IdZn2dFCKfvSuspqIpz11RhF1ALrqDLkxGVv7ZwNh0_VhJZU9hcjG5l6xc7rQEKpPRkZp0IdjkGS8Z0FskoVaiRIWAbZuiVFB9WCW8k1czC4HQTPLpII01bUQx2ludbm0UlXRgVU9ptUUbU7GAImQqTOW8LfPGklEvcgzlIlR_oqw4P9yBxLi-yMQ","dp":"pvNCSnnhbo8Igw9psPR-DicxFnkXlu_ix4gpy6efTrxA-z1VDFDioJ814vKQNioYDzpyAP1gfMPhRkvG_q0hRZsJah3Sb9dfA-WkhSWY7lURQP4yIBTMU0PF_rEATuS7lRciYk1SOx5fqXZd3m_LP0vpBC4Ujlq6NAq6CIjCnms","dq":"TtUVGCCkPNgfOLmkYXu7dxxUCV5kB01-xAEK2OY0n0pG8vfDophH4_D_ZC7nvJ8J9uDhs_3JStexq1lIvaWtG99RNTChIEDzpdn6GH9yaVcb_eB4uJjrNm64FhF8PGCCwxA-xMCZMaARKwhMB2_IOMkxUbWboL3gnhJ2rDO_QO0","e":"AQAB","kid":"8uHDw3M6rf8","kty":"RSA","n":"yaeEL0VKoPBXIAaWXsUgmu05lAvEIIdJn0FX9lHh4JE5UY9B83C5sCNdhs9iSWzpeP11EVjWp8i3Yv2CF7c7u50BXnVBGtxpZpFC-585UXacoJ0chUmarL9GRFJcM1nPHBTFu68aRrn1rIKNHUkNaaxFo0NFGl_4EDDTO8HwawTjwkPoQlRzeByhlvGPVvwgB3Fn93B8QJ_cZhXKxJvjjrC_8Pk76heC_ntEMru71Ix77BoC3j2TuyiN7m9RNBW8BU5q6lKoIdvIeZfTFLzi37iufyfvMrJTixp9zhNB1NxlLCeOZl2MXegtiGqd2H3cbAyqoOiv9ihUWTfXj7SxJw","p":"_Yylc9e07CKdqNRD2EosMC2mrhrEa9j5oY_l00Qyy4-jmCA59Q9viyqvveRo0U7cRvFA5BWgWN6GGLh1DG3X-QBqVr0dnk3uzbobb55RYUXyPLuBZI2q6w2oasbiDwPdY7KpkVv_H-bpITQlyDvO8hhucA6rUV7F6KTQVz8M3Ms","q":"y5p3hch-7jJ21TkAhp_Vk1fLCAuD4tbErwQs2of9ja8sB4iJOs5Wn6HD3P7Mc8Plye7qaLHvzc8I5g0tPKWvC0DPd_FLPXiWwMVAzee3NUX_oGeJNOQp11y1w_KqdO9qZqHSEPZ3NcFL_SZMFgggxhM1uzRiPzsVN0lnD_6prZU","qi":"2Grt6uXHm61ji3xSdkBWNtUnj19vS1-7rFJp5SoYztVQVThf_W52BAiXKBdYZDRVoItC_VS2NvAOjeJjhYO_xQ_q3hK7MdtuXfEPpLnyXKkmWo3lrJ26wbeF6l05LexCkI7ShsOuSt-dsyaTJTszuKDIA6YOfWvfo3aVZmlWRaI","use":"sig"}`;
type User = { type User = {
id: string; id: string;
email: string; email: string;
firstname: string; firstname: string;
lastname: string; lastname: string;
}; };
const privateKey = JSON.parse(PRIVATE_KEY_STRING); const privateKey = JSON.parse(PRIVATE_KEY_STRING);
const privateKeyImported = await jose.importJWK(privateKey, "RS256"); const privateKeyImported = await jose.importJWK(privateKey, 'RS256');
export async function generateIdToken( export async function generateIdToken(user: User, clientId: string, expired = false) {
user: User, const now = Math.floor(Date.now() / 1000);
clientId: string, const expiration = expired ? now + 1 : now + 1000000000; // Either expired or valid for a long time
expired = false
) {
const now = Math.floor(Date.now() / 1000);
const expiration = expired ? now + 1 : now + 1000000000; // Either expired or valid for a long time
const payload = { const payload = {
aud: clientId, aud: clientId,
email: user.email, email: user.email,
email_verified: true, email_verified: true,
exp: expiration, exp: expiration,
family_name: user.lastname, family_name: user.lastname,
given_name: user.firstname, given_name: user.firstname,
iat: now, iat: now,
iss: playwrightConfig.use!.baseURL, iss: playwrightConfig.use!.baseURL,
name: `${user.firstname} ${user.lastname}`, name: `${user.firstname} ${user.lastname}`,
nonce: "oW1A1O78GQ15D73OsHEx7WQKj7ZqvHLZu_37mdXIqAQ", nonce: 'oW1A1O78GQ15D73OsHEx7WQKj7ZqvHLZu_37mdXIqAQ',
sub: user.id, sub: user.id,
type: "id-token", type: 'id-token'
}; };
return await new jose.SignJWT(payload) return await new jose.SignJWT(payload)
.setProtectedHeader({ alg: "RS256", kid: privateKey.kid, typ: "JWT" }) .setProtectedHeader({ alg: 'RS256', kid: privateKey.kid, typ: 'JWT' })
.sign(privateKeyImported); .sign(privateKeyImported);
} }
export async function generateOauthAccessToken( export async function generateOauthAccessToken(user: User, clientId: string, expired = false) {
user: User, const now = Math.floor(Date.now() / 1000);
clientId: string, const expiration = expired ? now - 1000 : now + 1000000000; // Either expired or valid for a long time
expired = false
) {
const now = Math.floor(Date.now() / 1000);
const expiration = expired ? now - 1000 : now + 1000000000; // Either expired or valid for a long time
const payload = { const payload = {
aud: [clientId], aud: [clientId],
exp: expiration, exp: expiration,
iat: now, iat: now,
iss: playwrightConfig.use!.baseURL, iss: playwrightConfig.use!.baseURL,
sub: user.id, sub: user.id,
type: "oauth-access-token", type: 'oauth-access-token'
}; };
return await new jose.SignJWT(payload) return await new jose.SignJWT(payload)
.setProtectedHeader({ alg: "RS256", kid: privateKey.kid, typ: "JWT" }) .setProtectedHeader({ alg: 'RS256', kid: privateKey.kid, typ: 'JWT' })
.sign(privateKeyImported); .sign(privateKeyImported);
} }

View File

@@ -1,6 +1,10 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
export async function getUserCode(page: Page, clientId: string, clientSecret: string): Promise<string> { export async function getUserCode(
page: Page,
clientId: string,
clientSecret: string
): Promise<string> {
return page.request return page.request
.post('/api/oidc/device/authorize', { .post('/api/oidc/device/authorize', {
headers: { headers: {
@@ -16,25 +20,31 @@ export async function getUserCode(page: Page, clientId: string, clientSecret: st
.then((r) => r.user_code); .then((r) => r.user_code);
} }
export async function exchangeCode(page: Page, params: Record<string,string>): Promise<{access_token?: string, token_type?: string, expires_in?: number, error?: string}> { export async function exchangeCode(
page: Page,
params: Record<string, string>
): Promise<{ access_token?: string; token_type?: string; expires_in?: number; error?: string }> {
return page.request return page.request
.post('/api/oidc/token', { .post('/api/oidc/token', {
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
form: params, form: params
}) })
.then((r) => r.json()); .then((r) => r.json());
} }
export async function getClientAssertion(page: Page, data: {issuer: string, audience: string, subject: string}): Promise<string> { export async function getClientAssertion(
page: Page,
data: { issuer: string; audience: string; subject: string }
): Promise<string> {
return page.request return page.request
.post('/api/externalidp/sign', { .post('/api/externalidp/sign', {
data: { data: {
iss: data.issuer, iss: data.issuer,
aud: data.audience, aud: data.audience,
sub: data.subject, sub: data.subject
}, }
}) })
.then((r) => r.text()); .then((r) => r.text());
} }