Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ jobs:
- name: Run Server Tests
run: ./gradlew test -x webapp

# Integration tests (*IT) boot the full Spring context against a PostgreSQL Testcontainer.
# They are excluded from the `test` task, so run them explicitly (ubuntu-latest provides Docker).
- name: Run Integration Tests
run: ./gradlew integrationTest -x webapp

- name: Check Client Formatting
run: pnpm run prettier:check

Expand Down Expand Up @@ -77,7 +82,7 @@ jobs:
run: pnpm exec playwright install --with-deps chromium

# Runs the server (gradlew bootRun) and client (pnpm start) on the runner
# against MySQL in a container, then runs Playwright. Same path as the
# against PostgreSQL in a container, then runs Playwright. Same path as the
# local fast runner; avoids the docker-image client-packaging issue.
- name: Run E2E tests
run: ./run-e2e-tests-local-fast.sh
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ The client toolchain uses **pnpm** (replaced npm in early 2026). Install it with
## Configuration & Security Tips

- Use `src/main/resources/config/application-local.yml` for local secrets (gitignored). Do not commit credentials.
- Docker environment values live in `config/benchmarking.env` and `config/mysql.env`.
- Docker environment values live in `config/benchmarking.env` and `config/postgres.env`.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ To simplify the simulation process, the tool can create courses and exams on Art

## Setup

The Benchmarking Tool is a Spring Boot application with an Angular client. It requires a MySQL database server.
To start a MySQL server in a Docker container, run:
The Benchmarking Tool is a Spring Boot application with an Angular client. It requires a PostgreSQL database server.
To start a PostgreSQL server in a Docker container, run:

```
docker compose -f src/main/docker/mysql.yml up -d
docker compose -f src/main/docker/postgresql.yml up -d
```

### Prerequisites
Expand All @@ -27,7 +27,7 @@ To build and run the project locally, you need:
- **pnpm 11.1.2+** — used for client dependency management (replaces npm). Two install options:
- **Recommended:** `corepack enable` — Corepack reads the `packageManager` field in `package.json` and installs the exact pnpm version on first use.
- **Alternative:** `npm install -g pnpm@11.1.2` (or any of the other [install methods](https://pnpm.io/installation)).
- **Docker** (for the MySQL / Prometheus / Grafana containers under `src/main/docker/`).
- **Docker** (for the PostgreSQL / Prometheus / Grafana containers under `src/main/docker/`).

### Configuration

Expand Down Expand Up @@ -112,7 +112,7 @@ for arm64 processors like Mac with the M1 processor family.

This will create a Docker image named `artemis-benchmarking` in the production profile.
To run the Docker image, you can use the docker-compose file `src/main/docker/app.yml`.
This will also start a MySQL server.
This will also start a PostgreSQL server.

After starting the containers with

Expand All @@ -129,9 +129,9 @@ To start a container with that image, you can use the docker-compose file `docke
docker compose up -d
```

This will also start a MySQL server.
This will also start a PostgreSQL server.
The application will be available on http://localhost:8080.
When using this docker-compose file, you can set the environment variables in the files `config/benchmarking.env` and `config/mysql.env`.
When using this docker-compose file, you can set the environment variables in the files `config/benchmarking.env` and `config/postgres.env`.
The published Docker image is not compatible with arm64 processors and will not work on Mac with the M1 processor family.

### User Management
Expand Down Expand Up @@ -225,7 +225,7 @@ The entities of the application are located in the `entities` directory.

### Database

The application uses a MySQL database. The database schema is managed by Liquibase. The changelog files are located in the `src/main/resources/config/liquibase` directory.
The application uses a PostgreSQL database. The database schema is managed by Liquibase. The changelog files are located in the `src/main/resources/config/liquibase` directory.
When creating a new changeset, orientate yourself on the existing changesets and follow the naming convention. Add the new changeset to the `master.xml` file.

The following diagram shows the current database schema:
Expand Down
7 changes: 4 additions & 3 deletions config/benchmarking.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
SPRING_PROFILES_ACTIVE="prod"

SPRING_DATASOURCE_URL="jdbc:mysql://mysql:3306/benchmarking"
SPRING_DATASOURCE_USERNAME="root"
SPRING_DATASOURCE_PASSWORD=""
SPRING_DATASOURCE_URL="jdbc:postgresql://postgres:5432/benchmarking"
SPRING_DATASOURCE_USERNAME="benchmarking"
# Change this password for a real production deployment.
SPRING_DATASOURCE_PASSWORD="benchmarking"
3 changes: 0 additions & 3 deletions config/mysql.env

This file was deleted.

4 changes: 4 additions & 0 deletions config/postgres.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POSTGRES_DB="benchmarking"
POSTGRES_USER="benchmarking"
# Change this password for a real production deployment.
POSTGRES_PASSWORD="benchmarking"
19 changes: 9 additions & 10 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,26 @@ services:
start_period: 600s
interval: 1s
depends_on:
mysql:
postgres:
condition: service_healthy
restart: always

mysql:
image: mysql:9.7.1
postgres:
image: postgres:18-alpine
volumes:
- artemis-mysql-data:/var/lib/mysql
- artemis-postgres-data:/var/lib/postgresql
env_file:
- config/mysql.env
- config/postgres.env
expose:
- '3306'
command: mysqld --lower_case_table_names=1 --tls-version='' --character_set_server=utf8mb4 --collation-server=utf8mb4_unicode_ci --explicit_defaults_for_timestamp
- '5432'
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u root --silent
test: pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"
interval: 5s
timeout: 3s
retries: 30
start_period: 300s
restart: always

volumes:
artemis-mysql-data:
name: artemis-mysql-data
artemis-postgres-data:
name: artemis-postgres-data
12 changes: 6 additions & 6 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# End-to-end tests

[Playwright](https://playwright.dev) end-to-end tests that drive the real application
(Angular client → Spring Boot server → MySQL) in a browser. They verify that the
(Angular client → Spring Boot server → PostgreSQL) in a browser. They verify that the
deployed app actually works, not just that it compiles.

- Specs live in `e2e/*.spec.ts`, shared helpers in `e2e/helpers.ts`.
Expand All @@ -14,7 +14,7 @@ deployed app actually works, not just that it compiles.
- **Node** ≥ 24.15.0 and **pnpm** (`corepack enable` activates the pinned version).
- **Java 25** (to build/run the server).
- **Docker** — required by the docker runner, and by the fast runner unless you already
have a local MySQL on `:3307`.
have a local PostgreSQL on `:5432`.
- Client dependencies and the Chromium browser:
```bash
pnpm install
Expand All @@ -31,7 +31,7 @@ arguments to Playwright (`--ui`, `--headed`, a test filter, …).
### Realistic (Docker) — `./run-e2e-tests-local.sh`

Builds the production WAR, wraps it in a runtime image, and runs the stack (server +
MySQL) via `docker compose`; tests run against `http://127.0.0.1:8080`. Slower, but it
PostgreSQL) via `docker compose`; tests run against `http://127.0.0.1:8080`. Slower, but it
exercises the production artifact.

```bash
Expand All @@ -48,7 +48,7 @@ exercises the production artifact.
### Fast (host) — `./run-e2e-tests-local-fast.sh`

Runs the server (`gradlew bootRun`) and client (`pnpm start`) directly on the host
against MySQL on `:3307` (an existing instance is reused, otherwise a throwaway
against PostgreSQL on `:5432` (an existing instance is reused, otherwise a throwaway
container is started); tests run against `http://localhost:9000`. Much faster, and
services stay up between runs.

Expand Down Expand Up @@ -79,7 +79,7 @@ HTML report as an artifact.
- The seeded admin login is **`admin` / `admin`** (`user` / `user` also exists); see
`src/main/resources/config/liquibase/data/user.csv`.
- The Docker stack uses the `prod` profile and a fresh database each run; the fast runner
uses the `dev` profile against your local `:3307` MySQL.
uses the `dev` profile against your local `:5432` PostgreSQL.

## Writing new tests

Expand All @@ -104,7 +104,7 @@ HTML report as an artifact.
sign-in.
- _Authenticated_ (`authenticated.spec.ts`): admin navigation after login; the
simulations page loads cleanly; the metrics page renders live JVM data.
- _Full-vertical CRUD_ (`user-management-crud.spec.ts`, UI → REST → MySQL): create a user,
- _Full-vertical CRUD_ (`user-management-crud.spec.ts`, UI → REST → PostgreSQL): create a user,
list it, persist across reload, view its detail, edit its name, grant the admin
authority, deactivate, reactivate, cancel a delete, and delete it.

Expand Down
2 changes: 1 addition & 1 deletion e2e/user-management-crud.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { test, expect, Page } from '@playwright/test';
import { login } from './helpers';

// Full-vertical CRUD against User Management: every action goes through the
// Angular UI -> Spring Boot REST API -> MySQL. The block is serial because the
// Angular UI -> Spring Boot REST API -> PostgreSQL. The block is serial because the
// steps build on the same user record (create -> read -> update -> delete).
test.describe.configure({ mode: 'serial' });

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mapstructVersion=1.6.3
archunitJunit5Version=1.4.2
junitVersion=6.1.0
jacksonDatabindNullableVersion=0.2.10
mysqlVersion=9.7.0
postgresqlVersion=42.7.7
testcontainersVersion=2.0.5
jgitVersion=7.7.0.202606012155-r
sshdVersion=2.18.0
Expand Down
18 changes: 10 additions & 8 deletions gradle/liquibase.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies {
liquibaseRuntime "info.picocli:picocli:4.7.7"

liquibaseRuntime "org.liquibase.ext:liquibase-hibernate6:${liquibaseVersion}"
liquibaseRuntime "com.mysql:mysql-connector-j:${mysqlVersion}"
liquibaseRuntime "org.postgresql:postgresql:${postgresqlVersion}"
}

project.ext.diffChangelogFile = "src/main/resources/config/liquibase/changelog/" + new Date().format("yyyyMMddHHmmss") + "_changelog.xml"
Expand All @@ -20,19 +20,21 @@ if (!project.hasProperty("runList")) {
liquibase {
activities {
register("main") {
driver = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/artemis-benchmarking"
username = "root"
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost:5432/benchmarking"
username = "benchmarking"
password = "benchmarking"
changelogFile = "src/main/resources/config/liquibase/master.xml"
logLevel = "debug"
classpath = "src/main/resources/"
}
register("diffLog") {
driver = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/artemis-benchmarking"
username = "root"
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost:5432/benchmarking"
username = "benchmarking"
password = "benchmarking"
changelogFile = project.ext.diffChangelogFile
referenceUrl = "hibernate:spring:de.tum.cit.aet.domain?dialect=org.hibernate.dialect.MySQL8Dialect&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.hibernate.SpringImplicitNamingStrategy"
referenceUrl = "hibernate:spring:de.tum.cit.aet.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.hibernate.SpringImplicitNamingStrategy"
logLevel = "debug"
classpath = "${layout.buildDirectory.dir("classes/java/main")}"
}
Expand Down
4 changes: 2 additions & 2 deletions gradle/profile_dev.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
dependencies {
implementation "com.mysql:mysql-connector-j:${mysqlVersion}"
testImplementation "org.testcontainers:testcontainers-mysql:${testcontainersVersion}"
implementation "org.postgresql:postgresql:${postgresqlVersion}"
testImplementation "org.testcontainers:testcontainers-postgresql:${testcontainersVersion}"
}

ext {
Expand Down
4 changes: 2 additions & 2 deletions gradle/profile_prod.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
dependencies {
implementation "com.mysql:mysql-connector-j:${mysqlVersion}"
testImplementation "org.testcontainers:testcontainers-mysql:${testcontainersVersion}"
implementation "org.postgresql:postgresql:${postgresqlVersion}"
testImplementation "org.testcontainers:testcontainers-postgresql:${testcontainersVersion}"
}

ext {
Expand Down
18 changes: 18 additions & 0 deletions gradle/test.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,21 @@ test {
minHeapSize = "2g" // initial heap size
maxHeapSize = "6g" // maximum heap size
}

// Execute only the integration tests: ./gradlew integrationTest
// The `test` task above excludes "**/*IT*"; CI runs this task separately (it needs Docker for the
// PostgreSQL Testcontainer) so the integration suite cannot silently rot again.
tasks.register("integrationTest", Test) {
useJUnitPlatform()
include "**/*IT*", "**/*IntTest*"
testClassesDirs = testing.suites.test.sources.output.classesDirs
classpath = testing.suites.test.sources.runtimeClasspath
shouldRunAfter(tasks.named("test"))
testLogging {
events "FAILED", "SKIPPED"
}
testLogging.showStandardStreams = true
reports.html.required = false
minHeapSize = "2g"
maxHeapSize = "6g"
}
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineConfig, devices } from '@playwright/test';

// The e2e suite runs against a full stack (Angular client + Spring Boot server + MySQL).
// The e2e suite runs against a full stack (Angular client + Spring Boot server + PostgreSQL).
// Locally and in CI the stack is started via docker compose before the tests run; override
// the target with E2E_BASE_URL if needed.
const baseURL = process.env.E2E_BASE_URL ?? 'http://127.0.0.1:8080';
Expand Down
43 changes: 21 additions & 22 deletions run-e2e-tests-local-fast.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ set -euo pipefail
# Fast Local E2E Test Runner — Artemis Benchmarking
# =============================================================================
# Runs the server (./gradlew bootRun) and client (pnpm start) directly on the
# host, with MySQL in a small throwaway container. No production image build,
# host, with PostgreSQL in a small throwaway container. No production image build,
# so it is much faster than ./run-e2e-tests-local.sh. Services are left running
# between runs, so re-runs (with --skip-*) take only seconds.
#
# If you already have a MySQL on localhost:3307 (db "artemis-benchmarking",
# empty root password — matching the dev profile), use --skip-db to run with
# no Docker at all.
# If you already have a PostgreSQL on localhost:5432 (db "benchmarking",
# user/password "benchmarking" — matching the dev profile), use --skip-db to run
# with no Docker at all.
#
# Usage:
# ./run-e2e-tests-local-fast.sh [options] [-- <extra playwright args>]
#
# Options:
# --stop Kill the server, client and MySQL container; then exit
# --skip-db Reuse an already-running MySQL on localhost:3307
# --stop Kill the server, client and PostgreSQL container; then exit
# --skip-db Reuse an already-running PostgreSQL on localhost:5432
# --skip-server Reuse an already-running server on :8080
# --skip-client Reuse an already-running client on :9000
# --ui Open the Playwright UI
Expand All @@ -35,7 +35,7 @@ warn() { echo -e "${YELLOW}[e2e]${NC} $*"; }
err() { echo -e "${RED}[e2e]${NC} $*"; }

LOCAL_DIR=".e2e-local"
MYSQL_CONTAINER="benchmarking-e2e-mysql"
POSTGRES_CONTAINER="benchmarking-e2e-postgres"
SERVER_PORT=8080
CLIENT_PORT=9000
export E2E_BASE_URL="http://localhost:${CLIENT_PORT}"
Expand Down Expand Up @@ -103,7 +103,7 @@ if [ "$STOP" = true ]; then
log "Stopping all fast e2e services..."
kill_pidfile "client"; free_port "$CLIENT_PORT" "client"
kill_pidfile "server"; free_port "$SERVER_PORT" "server"
docker rm -f "$MYSQL_CONTAINER" >/dev/null 2>&1 || true
docker rm -f "$POSTGRES_CONTAINER" >/dev/null 2>&1 || true
rm -rf "$LOCAL_DIR"
ok "All services stopped."
exit 0
Expand All @@ -115,27 +115,26 @@ for cmd in docker java node pnpm curl; do
done
mkdir -p "$LOCAL_DIR"

# --- MySQL (port 3307, matching the dev profile) -----------------------------
# --- PostgreSQL (port 5432, matching the dev profile) ------------------------
if [ "$SKIP_DB" = true ]; then
warn "Skipping MySQL (--skip-db) — expecting one on localhost:3307."
elif lsof -nP -iTCP:3307 -sTCP:LISTEN >/dev/null 2>&1; then
log "MySQL already listening on localhost:3307 — using the existing instance."
warn "Skipping PostgreSQL (--skip-db) — expecting one on localhost:5432."
elif lsof -nP -iTCP:5432 -sTCP:LISTEN >/dev/null 2>&1; then
log "PostgreSQL already listening on localhost:5432 — using the existing instance."
else
log "Starting MySQL container ($MYSQL_CONTAINER) on localhost:3307..."
docker rm -f "$MYSQL_CONTAINER" >/dev/null 2>&1 || true
docker run -d --name "$MYSQL_CONTAINER" -p 127.0.0.1:3307:3306 \
-e MYSQL_ALLOW_EMPTY_PASSWORD=yes -e MYSQL_DATABASE=artemis-benchmarking \
mysql:9.7.1 mysqld --lower_case_table_names=1 --tls-version='' \
--character_set_server=utf8mb4 --explicit_defaults_for_timestamp >/dev/null
log "Starting PostgreSQL container ($POSTGRES_CONTAINER) on localhost:5432..."
docker rm -f "$POSTGRES_CONTAINER" >/dev/null 2>&1 || true
docker run -d --name "$POSTGRES_CONTAINER" -p 127.0.0.1:5432:5432 \
-e POSTGRES_DB=benchmarking -e POSTGRES_USER=benchmarking -e POSTGRES_PASSWORD=benchmarking \
postgres:18-alpine >/dev/null
elapsed=0
until docker exec "$MYSQL_CONTAINER" mysqladmin ping -h 127.0.0.1 --silent >/dev/null 2>&1; do
if [ "$elapsed" -ge 120 ]; then err "MySQL not ready after 120s"; exit 1; fi
until docker exec "$POSTGRES_CONTAINER" pg_isready -U benchmarking -d benchmarking >/dev/null 2>&1; do
if [ "$elapsed" -ge 120 ]; then err "PostgreSQL not ready after 120s"; exit 1; fi
sleep 3; elapsed=$((elapsed + 3))
done
ok "MySQL ready (${elapsed}s)"
ok "PostgreSQL ready (${elapsed}s)"
fi

# --- Server (Spring Boot, dev profile -> MySQL on 3307) ----------------------
# --- Server (Spring Boot, dev profile -> PostgreSQL on 5432) -----------------
if [ "$SKIP_SERVER" = false ]; then
kill_pidfile "server"; free_port "$SERVER_PORT" "server"
log "Starting server (./gradlew bootRun); log: $LOCAL_DIR/server.log"
Expand Down
4 changes: 2 additions & 2 deletions run-e2e-tests-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ set -euo pipefail
# Local E2E Test Runner (Docker) — Artemis Benchmarking
# =============================================================================
# Builds the production WAR, wraps it in a runtime image, and runs the full
# stack (Spring Boot server + MySQL) via docker compose, then runs the
# stack (Spring Boot server + PostgreSQL) via docker compose, then runs the
# Playwright e2e suite against it. Slower than the fast runner but realistic:
# it exercises the production artifact in a container with a real database.
#
Expand Down Expand Up @@ -101,7 +101,7 @@ else
warn "Skipping build (reusing $IMAGE)."
fi

log "Starting stack (server + MySQL) via docker compose..."
log "Starting stack (server + PostgreSQL) via docker compose..."
docker compose up -d --wait --wait-timeout 600
ok "Stack is healthy."

Expand Down
Loading