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:
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user