test: add full-stack Playwright integration test infrastructure

Sprint 12 Phase 2: Real integration tests with seed DB
- R__seed_test_data.sql (Flyway repeatable, 7 members, strains, batches, docs, board, events)
- TestResetController (profile-gated per-test DB reset)
- docker-compose.test.yml (self-contained, tmpfs Postgres)
- Dockerfile.playwright (v1.60.0, pre-installed deps)
- 13 integration spec files, 70+ test cases (@smoke + @full)
- seed-constants.ts, selectors.ts, api-client.ts test helpers
This commit is contained in:
Patrick Plate
2026-06-18 14:43:16 +02:00
parent 6e25914074
commit 776149e7d3
25 changed files with 2127 additions and 39 deletions
+74
View File
@@ -0,0 +1,74 @@
/**
* API client for integration tests.
* Used for direct backend calls: DB verification, test reset, data assertions.
*/
const API_URL = process.env.API_URL || "http://localhost:8080"
export class ApiClient {
private token: string | null = null
async login(email: string, password: string): Promise<void> {
const res = await fetch(`${API_URL}/api/v1/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
})
if (!res.ok) throw new Error(`Login failed: ${res.status}`)
const data = await res.json()
this.token = data.token
}
async resetDb(): Promise<void> {
const res = await fetch(`${API_URL}/api/v1/test/reset-db`, {
method: "POST",
headers: this.authHeaders(),
})
if (!res.ok) throw new Error(`DB reset failed: ${res.status}`)
}
async getMembers(): Promise<any> {
return this.get("/api/v1/members")
}
async getDocuments(): Promise<any> {
return this.get("/api/v1/documents")
}
async getBatches(): Promise<any> {
return this.get("/api/v1/batches")
}
async getDistributions(): Promise<any> {
return this.get("/api/v1/distributions")
}
async getBoardPositions(): Promise<any> {
return this.get("/api/v1/board")
}
private authHeaders(): Record<string, string> {
const headers: Record<string, string> = {
"Content-Type": "application/json",
}
if (this.token) headers["Authorization"] = `Bearer ${this.token}`
return headers
}
private async get(path: string): Promise<any> {
const res = await fetch(`${API_URL}${path}`, {
headers: this.authHeaders(),
})
if (!res.ok) throw new Error(`GET ${path} failed: ${res.status}`)
return res.json()
}
private async post(path: string, body?: unknown): Promise<any> {
const res = await fetch(`${API_URL}${path}`, {
method: "POST",
headers: this.authHeaders(),
body: body ? JSON.stringify(body) : undefined,
})
if (!res.ok) throw new Error(`POST ${path} failed: ${res.status}`)
return res.json()
}
}