mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
feat: adding e2e tests for launching agent.
This commit is contained in:
+14
-51
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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(?:\?.*)?$/);
|
||||
}
|
||||
|
||||
@@ -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(?:\?.*)?$/);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user