feat: adding e2e tests for launching agent.

This commit is contained in:
killian-larcher
2026-05-21 23:46:03 +02:00
parent 55be9374e1
commit 252aab9b90
22 changed files with 1446 additions and 182 deletions
+14 -51
View File
@@ -1,10 +1,5 @@
import {expect, test} from "@playwright/test";
import {execSync} from "child_process";
import fs from "fs";
import os from "os";
import path from "path";
import {createAgentWithDockerDatabases} from "./helpers/agent-cli";
import {create, edit, get, remove} from "./helpers/agent";
import {create, edit, get, launch, remove} from "./helpers/agent";
import {LOCAL_STORAGE_PATH} from "./helpers/session";
const agent = {
@@ -64,53 +59,21 @@ test.describe.serial(() => {
await expect(page.getByText(agent.bName)).toHaveCount(0);
});
// test("Launch the updated agent", async ({page}) => {
// await page.goto("/dashboard/agents");
// await expect(page.getByRole("heading", {name: "Agents"})).toBeVisible();
// await get(page, agent.aName).click();
//
// await expect(page).toHaveURL(/\/dashboard\/agents\/.+/);
// await expect(page.getByText(agent.aName, {exact: true})).toBeVisible();
// await expect(page.getByText("Registration & Setup")).toBeVisible();
//
// const commandInput = page.locator("input[readonly]").first();
// await page.locator("input[readonly]").first().locator("xpath=following-sibling::button[1]").click();
// const command = await commandInput.inputValue();
//
// agentWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), "portabase-agent-"));
// await createAgentWithDockerDatabases(command, agentWorkspace);
// execSync(`portabase start "${agent.aName}"`, {
// cwd: agentWorkspace,
// stdio: "pipe",
// timeout: 120_000,
// });
//
// await expect(page.getByText("Never connected.")).toHaveCount(0, {timeout: 120_000});
// await expect(page.getByText("Action Required")).toHaveCount(0);
// });
test("Launch the updated agent", async ({page}) => {
await page.goto("/dashboard/agents");
await expect(page.getByRole("heading", {name: "Agents"})).toBeVisible();
await get(page, agent.aName).click();
test.afterAll(async () => {
if (agentWorkspace) {
try {
execSync(`portabase stop "${agent.aName}"`, {
cwd: agentWorkspace,
stdio: "pipe",
timeout: 30_000,
});
} catch {
}
await expect(page).toHaveURL(/\/dashboard\/agents\/.+/);
await expect(page.getByText(agent.aUpdatedName, {exact: true})).toBeVisible();
await expect(page.getByText("Registration & Setup")).toBeVisible();
try {
execSync(`portabase uninstall --force "${agent.aName}"`, {
cwd: agentWorkspace,
stdio: "pipe",
timeout: 30_000,
});
} catch {
}
const commandInput = page.locator("input[readonly]").last();
await page.locator("input[readonly]").first().locator("xpath=following-sibling::button[1]").click();
const edgeKey = await commandInput.inputValue();
fs.rmSync(agentWorkspace, {recursive: true, force: true});
agentWorkspace = null;
}
await launch(edgeKey)
await expect(page.getByRole("heading", { name: "Managed Databases" })).toBeVisible();
});
});
+14
View File
@@ -1,4 +1,5 @@
import {Page} from "@playwright/test";
import {execSync} from "node:child_process";
/**
@@ -64,3 +65,16 @@ export async function remove(page: Page, name: string) {
await page.getByRole("button", {name: /Delete Agent/i}).click();
await page.getByRole("button", {name: "Delete", exact: true}).click();
}
export async function launch(edgeKey: string) {
const output = execSync(
`EDGE_KEY=${edgeKey} docker compose -f e2e/helpers/agent/docker-compose.agent-a.yml up -d`,
{
cwd: process.cwd(),
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
},
);
return output;
}
+154
View File
@@ -0,0 +1,154 @@
{
"databases": [
{
"name": "PostgreSQL 18",
"database": "backup_e2e",
"type": "postgresql",
"username": "backup",
"password": "backup",
"port": 5432,
"host": "postgresql-18",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef001"
},
{
"name": "PostgreSQL 17",
"database": "backup_e2e",
"type": "postgresql",
"username": "backup",
"password": "backup",
"port": 5432,
"host": "postgresql-17",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef002"
},
{
"name": "PostgreSQL 16",
"database": "backup_e2e",
"type": "postgresql",
"username": "backup",
"password": "backup",
"port": 5432,
"host": "postgresql-16",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef003"
},
{
"name": "PostgreSQL 15",
"database": "backup_e2e",
"type": "postgresql",
"username": "backup",
"password": "backup",
"port": 5432,
"host": "postgresql-15",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef004"
},
{
"name": "PostgreSQL 14",
"database": "backup_e2e",
"type": "postgresql",
"username": "backup",
"password": "backup",
"port": 5432,
"host": "postgresql-14",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef005"
},
{
"name": "PostgreSQL 13",
"database": "backup_e2e",
"type": "postgresql",
"username": "backup",
"password": "backup",
"port": 5432,
"host": "postgresql-13",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef006"
},
{
"name": "PostgreSQL 12",
"database": "backup_e2e",
"type": "postgresql",
"username": "backup",
"password": "backup",
"port": 5432,
"host": "postgresql-12",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef007"
},
{
"name": "MySQL 8.4",
"database": "backup_e2e",
"type": "mysql",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mysql-8.4",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef008"
},
{
"name": "MySQL 8.0",
"database": "backup_e2e",
"type": "mysql",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mysql-8.0",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef009"
},
{
"name": "MariaDB 11.4",
"database": "backup_e2e",
"type": "mariadb",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mariadb-11.4",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef010"
},
{
"name": "MariaDB 11.3",
"database": "backup_e2e",
"type": "mariadb",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mariadb-11.3",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef011"
},
{
"name": "MariaDB 11.2",
"database": "backup_e2e",
"type": "mariadb",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mariadb-11.2",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef012"
},
{
"name": "MariaDB 11.1",
"database": "backup_e2e",
"type": "mariadb",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mariadb-11.1",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef013"
},
{
"name": "MariaDB 11.0",
"database": "backup_e2e",
"type": "mariadb",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mariadb-11.0",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef014"
},
{
"name": "MariaDB 10.11",
"database": "backup_e2e",
"type": "mariadb",
"username": "backup",
"password": "backup",
"port": 3306,
"host": "mariadb-10.11",
"generated_id": "9f8a5b18-0f45-4b4c-a821-93d7e46ef015"
}
]
}
+139
View File
@@ -0,0 +1,139 @@
[[databases]]
name = "MongoDB 8.0 Auth"
database = "backup_e2e"
type = "mongodb"
username = "backup"
password = "backup"
port = 27017
host = "mongodb-8.0-auth"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef101"
[[databases]]
name = "MongoDB 8.0 No Auth"
database = "backup_e2e"
type = "mongodb"
username = ""
password = ""
port = 27017
host = "mongodb-8.0-no-auth"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef102"
[[databases]]
name = "MongoDB 7.0 Auth"
database = "backup_e2e"
type = "mongodb"
username = "backup"
password = "backup"
port = 27017
host = "mongodb-7.0-auth"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef103"
[[databases]]
name = "MongoDB 7.0 No Auth"
database = "backup_e2e"
type = "mongodb"
username = ""
password = ""
port = 27017
host = "mongodb-7.0-no-auth"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef104"
[[databases]]
name = "MongoDB 6.0 Auth"
database = "backup_e2e"
type = "mongodb"
username = "backup"
password = "backup"
port = 27017
host = "mongodb-6.0-auth"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef105"
[[databases]]
name = "MongoDB 6.0 No Auth"
database = "backup_e2e"
type = "mongodb"
username = ""
password = ""
port = 27017
host = "mongodb-6.0-no-auth"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef106"
[[databases]]
name = "Redis 8"
database = "0"
type = "redis"
username = ""
password = ""
port = 6379
host = "redis-8"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef107"
[[databases]]
name = "Redis 7"
database = "0"
type = "redis"
username = ""
password = ""
port = 6379
host = "redis-7"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef108"
[[databases]]
name = "Valkey 8"
database = "0"
type = "valkey"
username = ""
password = ""
port = 6379
host = "valkey-8"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef109"
[[databases]]
name = "Valkey 7"
database = "0"
type = "valkey"
username = ""
password = ""
port = 6379
host = "valkey-7"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef110"
[[databases]]
name = "MSSQL 2022"
database = "master"
type = "mssql"
username = "sa"
password = "BackupRootPassw0rd!"
port = 1433
host = "mssql-2022"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef111"
[[databases]]
name = "MSSQL 2019"
database = "master"
type = "mssql"
username = "sa"
password = "BackupRootPassw0rd!"
port = 1433
host = "mssql-2019"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef112"
[[databases]]
name = "SQLite 3.46"
database = "/data/backup_e2e.db"
type = "sqlite"
username = ""
password = ""
port = 0
host = "sqlite-3.46"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef113"
[[databases]]
name = "SQLite 3.45"
database = "/data/backup_e2e.db"
type = "sqlite"
username = ""
password = ""
port = 0
host = "sqlite-3.45"
generated_id = "9f8a5b18-0f45-4b4c-a821-93d7e46ef114"
@@ -0,0 +1,24 @@
name: portabase-agent-a
services:
app:
container_name: portabase-agent
image: portabase/agent:latest
restart: unless-stopped
volumes:
- ./databases.json:/config/config.json
environment:
LOG: info
TZ: "UTC"
POLLING: 5
EDGE_KEY: "${EDGE_KEY}"
networks:
- portabase
extra_hosts:
- "localhost:host-gateway"
networks:
portabase:
name: portabase
external: true
@@ -0,0 +1,23 @@
name: portabase-agent-b
services:
app:
container_name: portabase-agent
image: portabase/agent:latest
restart: unless-stopped
volumes:
- ./databases.json:/config/config.json
environment:
LOG: info
TZ: "UTC"
POLLING: 5
DATA_PATH: "/data"
EDGE_KEY: "${EDGE_KEY}"
networks:
- portabase
networks:
portabase:
name: portabase
external: true
@@ -0,0 +1,390 @@
name: database
x-postgresql-common: &postgresql-common
environment:
POSTGRES_DB: backup_e2e
POSTGRES_USER: backup
POSTGRES_PASSWORD: backup
healthcheck:
test: ["CMD-SHELL", "pg_isready -U backup -d backup_e2e"]
interval: 10s
timeout: 5s
retries: 5
networks:
- portabase
x-mysql-common: &mysql-common
environment:
MYSQL_DATABASE: backup_e2e
MYSQL_USER: backup
MYSQL_PASSWORD: backup
MYSQL_ROOT_PASSWORD: backup-root
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
networks:
- portabase
x-mariadb-common: &mariadb-common
environment:
MARIADB_DATABASE: backup_e2e
MARIADB_USER: backup
MARIADB_PASSWORD: backup
MARIADB_ROOT_PASSWORD: backup-root
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -h 127.0.0.1 -uroot -p$$MARIADB_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
networks:
- portabase
x-mongodb-auth-common: &mongodb-auth-common
environment:
MONGO_INITDB_ROOT_USERNAME: backup
MONGO_INITDB_ROOT_PASSWORD: backup
MONGO_INITDB_DATABASE: backup_e2e
healthcheck:
test:
[
"CMD-SHELL",
"mongosh --quiet --username $$MONGO_INITDB_ROOT_USERNAME --password $$MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval \"db.runCommand({ ping: 1 }).ok\" | grep 1",
]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
networks:
- portabase
x-mongodb-no-auth-common: &mongodb-no-auth-common
healthcheck:
test:
[
"CMD-SHELL",
"mongosh --quiet --eval \"db.runCommand({ ping: 1 }).ok\" | grep 1",
]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
networks:
- portabase
x-redis-common: &redis-common
command: ["redis-server", "--appendonly", "yes"]
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 10s
timeout: 5s
retries: 1
networks:
- portabase
x-valkey-common: &valkey-common
command: ["valkey-server", "--appendonly", "yes"]
healthcheck:
test: ["CMD-SHELL", "valkey-cli ping | grep PONG"]
interval: 10s
timeout: 5s
retries: 10
networks:
- portabase
x-mssql-common: &mssql-common
platform: linux/amd64
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "BackupRootPassw0rd!"
healthcheck:
test:
[
"CMD-SHELL",
"(test -x /opt/mssql-tools18/bin/sqlcmd && /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P $$MSSQL_SA_PASSWORD -Q \"SELECT 1\") || (test -x /opt/mssql-tools/bin/sqlcmd && /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $$MSSQL_SA_PASSWORD -Q \"SELECT 1\")",
]
interval: 15s
timeout: 10s
retries: 20
start_period: 40s
networks:
- portabase
x-sqlite-common: &sqlite-common
command: ["/bin/sh", "-lc", "mkdir -p /data && sqlite3 /data/backup_e2e.db 'SELECT 1;' >/dev/null && tail -f /dev/null"]
healthcheck:
test: ["CMD-SHELL", "sqlite3 /data/backup_e2e.db 'SELECT 1;' | grep 1"]
interval: 10s
timeout: 5s
retries: 1
networks:
- portabase
services:
postgresql-18:
<<: *postgresql-common
image: postgres:18-alpine
expose:
- "5432"
volumes:
- postgresql-18-backup-e2e-data:/var/lib/postgresql/data
postgresql-17:
<<: *postgresql-common
image: postgres:17-alpine
expose:
- "5432"
volumes:
- postgresql-17-backup-e2e-data:/var/lib/postgresql/data
postgresql-16:
<<: *postgresql-common
image: postgres:16-alpine
expose:
- "5432"
volumes:
- postgresql-16-backup-e2e-data:/var/lib/postgresql/data
postgresql-15:
<<: *postgresql-common
image: postgres:15-alpine
expose:
- "5432"
volumes:
- postgresql-15-backup-e2e-data:/var/lib/postgresql/data
postgresql-14:
<<: *postgresql-common
image: postgres:14-alpine
expose:
- "5432"
volumes:
- postgresql-14-backup-e2e-data:/var/lib/postgresql/data
postgresql-13:
<<: *postgresql-common
image: postgres:13-alpine
expose:
- "5432"
volumes:
- postgresql-13-backup-e2e-data:/var/lib/postgresql/data
postgresql-12:
<<: *postgresql-common
image: postgres:12-alpine
expose:
- "5432"
volumes:
- postgresql-12-backup-e2e-data:/var/lib/postgresql/data
mysql-8.4:
<<: *mysql-common
image: mysql:8.4
expose:
- "3306"
volumes:
- mysql-8.4-backup-e2e-data:/var/lib/mysql
mysql-8.0:
<<: *mysql-common
image: mysql:8.0
expose:
- "3306"
volumes:
- mysql-8.0-backup-e2e-data:/var/lib/mysql
mariadb-11.4:
<<: *mariadb-common
image: mariadb:11.4
expose:
- "3306"
volumes:
- mariadb-11.4-backup-e2e-data:/var/lib/mysql
mariadb-11.3:
<<: *mariadb-common
image: mariadb:11.3
expose:
- "3306"
volumes:
- mariadb-11.3-backup-e2e-data:/var/lib/mysql
mariadb-11.2:
<<: *mariadb-common
image: mariadb:11.2
expose:
- "3306"
volumes:
- mariadb-11.2-backup-e2e-data:/var/lib/mysql
mariadb-11.1:
<<: *mariadb-common
image: mariadb:11.1
expose:
- "3306"
volumes:
- mariadb-11.1-backup-e2e-data:/var/lib/mysql
mariadb-11.0:
<<: *mariadb-common
image: mariadb:11.0
expose:
- "3306"
volumes:
- mariadb-11.0-backup-e2e-data:/var/lib/mysql
mariadb-10.11:
<<: *mariadb-common
image: mariadb:10.11
expose:
- "3306"
volumes:
- mariadb-10.11-backup-e2e-data:/var/lib/mysql
mongodb-8.0-auth:
<<: *mongodb-auth-common
image: mongo:8.0
expose:
- "27017"
volumes:
- mongodb-8.0-auth-backup-e2e-data:/data/db
mongodb-8.0-no-auth:
<<: *mongodb-no-auth-common
image: mongo:8.0
expose:
- "27017"
volumes:
- mongodb-8.0-no-auth-backup-e2e-data:/data/db
mongodb-7.0-auth:
<<: *mongodb-auth-common
image: mongo:7.0
expose:
- "27017"
volumes:
- mongodb-7.0-auth-backup-e2e-data:/data/db
mongodb-7.0-no-auth:
<<: *mongodb-no-auth-common
image: mongo:7.0
expose:
- "27017"
volumes:
- mongodb-7.0-no-auth-backup-e2e-data:/data/db
mongodb-6.0-auth:
<<: *mongodb-auth-common
image: mongo:6.0
expose:
- "27017"
volumes:
- mongodb-6.0-auth-backup-e2e-data:/data/db
mongodb-6.0-no-auth:
<<: *mongodb-no-auth-common
image: mongo:6.0
expose:
- "27017"
volumes:
- mongodb-6.0-no-auth-backup-e2e-data:/data/db
redis-8:
<<: *redis-common
image: redis:8
expose:
- "6379"
volumes:
- redis-8-backup-e2e-data:/data
redis-7:
<<: *redis-common
image: redis:7
expose:
- "6379"
volumes:
- redis-7-backup-e2e-data:/data
valkey-8:
<<: *valkey-common
image: valkey/valkey:8
expose:
- "6379"
volumes:
- valkey-8-backup-e2e-data:/data
valkey-7:
<<: *valkey-common
image: valkey/valkey:7
expose:
- "6379"
volumes:
- valkey-7-backup-e2e-data:/data
mssql-2022:
<<: *mssql-common
image: mcr.microsoft.com/mssql/server:2022-latest
expose:
- "1433"
volumes:
- mssql-2022-backup-e2e-data:/var/opt/mssql
mssql-2019:
<<: *mssql-common
image: mcr.microsoft.com/mssql/server:2019-latest
expose:
- "1433"
volumes:
- mssql-2019-backup-e2e-data:/var/opt/mssql
sqlite-3.46:
<<: *sqlite-common
image: keinos/sqlite3:3.46.1
volumes:
- sqlite-3.46-backup-e2e-data:/data
sqlite-3.45:
<<: *sqlite-common
image: keinos/sqlite3:3.45.3
volumes:
- sqlite-3.45-backup-e2e-data:/data
volumes:
postgresql-18-backup-e2e-data:
postgresql-17-backup-e2e-data:
postgresql-16-backup-e2e-data:
postgresql-15-backup-e2e-data:
postgresql-14-backup-e2e-data:
postgresql-13-backup-e2e-data:
postgresql-12-backup-e2e-data:
mysql-8.4-backup-e2e-data:
mysql-8.0-backup-e2e-data:
mariadb-11.4-backup-e2e-data:
mariadb-11.3-backup-e2e-data:
mariadb-11.2-backup-e2e-data:
mariadb-11.1-backup-e2e-data:
mariadb-11.0-backup-e2e-data:
mariadb-10.11-backup-e2e-data:
mongodb-8.0-auth-backup-e2e-data:
mongodb-8.0-no-auth-backup-e2e-data:
mongodb-7.0-auth-backup-e2e-data:
mongodb-7.0-no-auth-backup-e2e-data:
mongodb-6.0-auth-backup-e2e-data:
mongodb-6.0-no-auth-backup-e2e-data:
redis-8-backup-e2e-data:
redis-7-backup-e2e-data:
valkey-8-backup-e2e-data:
valkey-7-backup-e2e-data:
mssql-2022-backup-e2e-data:
mssql-2019-backup-e2e-data:
sqlite-3.46-backup-e2e-data:
sqlite-3.45-backup-e2e-data:
networks:
portabase:
name: portabase
external: true
+165
View File
@@ -0,0 +1,165 @@
import {expect, Page} from "@playwright/test";
import {execSync} from "node:child_process";
export const oidcProviders: Array<{ id: string, title: string }> = [
{id: "authentik", title: "Authentik"},
{id: "pocket-id", title: "Pocket ID"},
{id: "keycloak", title: "Keycloak"},
]
export async function loginWithOidc(page: Page, providerId) {
await page.goto("/login");
const provider = oidcProviders.find((entry) => entry.id === providerId);
if (!provider) throw new Error(`Unknown OIDC provider: ${providerId}`)
await expect(page.getByRole("button", {name: provider.title})).toBeVisible();
const authorizationUrl = await getOidcAuthorizationUrl(page, providerId);
await page.goto(authorizationUrl);
switch (providerId) {
case "pocket-id":
await loginWithPocketId(page)
return
case "authentik":
await loginWithAuthentik(page)
return
case "keycloak":
await loginWithKeycloak(page)
return
}
// if (providerId === "pocket-id") {
// await loginWithPocketId(page);
// return;
// }
//
// const authorizationUrl = await getOidcAuthorizationUrl(page, providerId);
// await page.goto(authorizationUrl);
//
// if (providerId === "authentik") {
// await loginWithAuthentik(page);
// return;
// }
//
// await loginWithKeycloak(page);
}
const AUTHENTIK_USER = "admin@example.com";
const AUTHENTIK_PASSWORD = "testPASS123456!";
const KEYCLOAK_USER = "admin";
const KEYCLOAK_PASSWORD = "admin";
const oidcHostRewrites: Record<string, string> = {
"http://authentik-server:9000": "http://localhost:3057",
"http://keycloak:8080": "http://localhost:3056",
"http://pocket-id:1411": "http://localhost:3055",
};
function toBrowserReachableUrl(url: string) {
let browserUrl = url;
for (const [internalOrigin, browserOrigin] of Object.entries(oidcHostRewrites)) {
browserUrl = browserUrl.replace(internalOrigin, browserOrigin);
}
return browserUrl;
}
async function getOidcAuthorizationUrl(page: Page, providerId) {
for (let attempt = 0; attempt < 3; attempt++) {
const result = await page.evaluate(async ({providerId}) => {
const response = await fetch("/api/auth/sign-in/sso", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
providerId,
providerType: "oidc",
callbackURL: "/dashboard",
}),
});
const data = await response.json().catch(() => ({}));
return {
ok: response.ok,
status: response.status,
data,
};
}, {providerId});
if (result.ok && result.data?.url) {
return toBrowserReachableUrl(result.data.url);
}
if (result.status !== 429 || attempt === 2) {
throw new Error(`Failed to initiate ${providerId} OIDC sign-in: ${JSON.stringify(result)}`);
}
await page.waitForTimeout(2_000);
}
throw new Error(`Failed to initiate ${providerId} OIDC sign-in after retries.`);
}
async function loginWithAuthentik(page: Page) {
const usernameField = page.locator('input[name="uidField"], input[type="text"], input[type="email"]').first();
await expect(usernameField).toBeVisible();
await usernameField.fill(AUTHENTIK_USER);
await usernameField.press("Enter").catch(() => undefined);
if (await page.getByLabel(/password/i).count() === 0) {
await page.getByRole("button", {name: /log in|sign in|continue/i}).click();
}
const passwordField = page.locator('input[name="password"], input[type="password"]').first();
await expect(passwordField).toBeVisible();
await passwordField.fill(AUTHENTIK_PASSWORD);
await page.getByRole("button", {name: /log in|sign in|continue/i}).click();
}
async function loginWithKeycloak(page: Page) {
await page.locator('input[name="username"], #username').fill(KEYCLOAK_USER);
await page.locator('input[name="password"], #password').fill(KEYCLOAK_PASSWORD);
await page.locator('input[type="submit"], #kc-login').click();
}
function getPocketIdAccessUrl() {
const output = execSync(
"docker compose -f docker-compose.oidc.yml exec -T pocket-id ./pocket-id one-time-access-token admin",
{
cwd: process.cwd(),
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
},
);
const match = output.match(/https?:\/\/\S+/);
if (!match) {
throw new Error(`Failed to extract Pocket ID access URL from output:\n${output}`);
}
return match[0];
}
async function loginWithPocketId(page: Page) {
// const accessUrl = getPocketIdAccessUrl();
// const authorizationUrl = await getOidcAuthorizationUrl(page, "pocket-id");
// await page.goto(authorizationUrl);
// await page.goto(accessUrl);
// await page.goto(authorizationUrl);
await page.waitForURL(/\/dashboard\/home(?:\?.*)?$/);
}
+20
View File
@@ -0,0 +1,20 @@
import {expect, test} from "@playwright/test";
import {logout} from "./helpers/auth";
import {loginWithOidc, oidcProviders} from "./helpers/oidc";
test.describe.serial("OIDC auth", () => {
for (const provider of oidcProviders) {
test(`OIDC login/logout with ${provider.title}`, async ({page}) => {
await loginWithOidc(page, provider.id);
await expect(page).toHaveURL(/\/dashboard\/home(?:\?.*)?$/);
await expect(page.getByRole("link", {name: "Logo Portabase"})).toBeVisible();
await logout(page);
await expect(page).toHaveURL(/\/login(?:\?.*)?$/);
});
}
});