Merge pull request 'test(e2e): give the suite a throwaway database of its own (#186)' (#270) from fix/186-disposable-e2e-database into main
Reviewed-on: #270
This commit was merged in pull request #270.
This commit is contained in:
@@ -29,3 +29,7 @@ frontend/playwright-results.json
|
|||||||
# mistaken for real migration history. Keep the mirror, drop the rest.
|
# mistaken for real migration history. Keep the mirror, drop the rest.
|
||||||
backend/src/db-drizzle/*.sql
|
backend/src/db-drizzle/*.sql
|
||||||
backend/src/db-drizzle/meta/
|
backend/src/db-drizzle/meta/
|
||||||
|
|
||||||
|
# Where an end-to-end run against the throwaway database writes its uploads.
|
||||||
|
# Disposable with the database it belongs to (#186).
|
||||||
|
backend/.e2e-uploads/
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ un-tests.ps1 -Suite e2e
|
|||||||
un-tests.ps1 -Suite all
|
un-tests.ps1 -Suite all
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Run the end-to-end suite against a throwaway database rather than your development one:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\scripts\start-local.ps1 -E2eDb # separate container, separate port, starts empty
|
||||||
|
.\scripts␍un-tests.ps1 -Suite e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Without `-E2eDb` the suite shares the development database, which nothing truncates — every run leaves its fixtures behind, and the storefront eventually renders enough of them to outrun the assertions' timeouts. See #186.
|
||||||
|
|
||||||
Both scripts switch to the pinned Node 26.7.0 (`NODE_VERSION` in `scripts/NodeVersion.ps1`) and verify that is what actually ends up running, then put the machine back to 18.16.1 when they finish — including when they fail partway, so an interrupted run does not leave the version switched. **`nvm use` rewrites a machine-global symlink, so this changes the Node version for every terminal on the machine while a script is running, not only the one you ran it in.** Both scripts say so as they do it.
|
Both scripts switch to the pinned Node 26.7.0 (`NODE_VERSION` in `scripts/NodeVersion.ps1`) and verify that is what actually ends up running, then put the machine back to 18.16.1 when they finish — including when they fail partway, so an interrupted run does not leave the version switched. **`nvm use` rewrites a machine-global symlink, so this changes the Node version for every terminal on the machine while a script is running, not only the one you ran it in.** Both scripts say so as they do it.
|
||||||
|
|
||||||
The Node 20 floor is not arbitrary: `node-pg-migrate` pulls in an `lru-cache` that calls `diagnostics_channel.tracingChannel()`, which does not exist before Node 19.9. On Node 18 migrations die inside minified library code with `(0 , U.tracingChannel) is not a function`, which says nothing about versions.
|
The Node 20 floor is not arbitrary: `node-pg-migrate` pulls in an `lru-cache` that calls `diagnostics_channel.tracingChannel()`, which does not exist before Node 19.9. On Node 18 migrations die inside minified library code with `(0 , U.tracingChannel) is not a function`, which says nothing about versions.
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# A throwaway Postgres for the end-to-end suite.
|
||||||
|
#
|
||||||
|
# The e2e suite used to run against the development database that
|
||||||
|
# start-local.ps1 brings up, and nothing ever truncated it: every run seeded
|
||||||
|
# more fixtures and left them. The unfiltered storefront grew monotonically —
|
||||||
|
# 1,662 items by the time #186 was filed — until rendering it outran the
|
||||||
|
# assertions' timeout. It failed locally, passed in CI where the database is
|
||||||
|
# fresh, and got slowly worse, which is the combination nobody can act on.
|
||||||
|
#
|
||||||
|
# Deliberately a mirror of docker-compose.test.yml rather than a shared file.
|
||||||
|
# The two suites must not share a database: the integration suite truncates
|
||||||
|
# between tests, so running it while an e2e run is in flight would delete that
|
||||||
|
# run's fixtures underneath it (#116). Different container, different port,
|
||||||
|
# different credentials — so the mistake is impossible rather than discouraged.
|
||||||
|
#
|
||||||
|
# tmpfs, like the integration database: the data is worthless the moment the
|
||||||
|
# run ends, and a container with nothing to persist starts faster and cannot
|
||||||
|
# accumulate anything between runs.
|
||||||
|
#
|
||||||
|
# docker compose -f backend/docker-compose.e2e.yml up -d
|
||||||
|
# docker compose -f backend/docker-compose.e2e.yml down
|
||||||
|
#
|
||||||
|
# The suite and the application both have to point at it. See
|
||||||
|
# scripts/start-local.ps1 -E2eDb, which does that for you.
|
||||||
|
services:
|
||||||
|
redefined-designs-e2e-db:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: redefined-designs-e2e-db
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=redefined_e2e
|
||||||
|
- POSTGRES_PASSWORD=redefined_e2e
|
||||||
|
- POSTGRES_DB=redefined_e2e
|
||||||
|
ports:
|
||||||
|
# Not 55432 (integration) and not 55500 (development). Override with
|
||||||
|
# E2E_DB_PORT if Hyper-V has reserved this one — it silently claims
|
||||||
|
# ranges on Windows, which is why the integration suite has TEST_PGPORT.
|
||||||
|
- "${E2E_DB_PORT:-55501}:5432"
|
||||||
|
tmpfs:
|
||||||
|
- /var/lib/postgresql/data
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
"test:integration:cov": "jest -c jest.integration.config.js --runInBand --coverage --forceExit",
|
"test:integration:cov": "jest -c jest.integration.config.js --runInBand --coverage --forceExit",
|
||||||
"bench:hashing": "tsx scripts/bench-hash-latency.ts",
|
"bench:hashing": "tsx scripts/bench-hash-latency.ts",
|
||||||
"backfill:images": "node dist/backfillImageReencode.js",
|
"backfill:images": "node dist/backfillImageReencode.js",
|
||||||
|
"db:e2e:up": "docker compose -f docker-compose.e2e.yml up -d",
|
||||||
|
"db:e2e:down": "docker compose -f docker-compose.e2e.yml down",
|
||||||
"db:test:up": "docker compose -f docker-compose.test.yml up -d",
|
"db:test:up": "docker compose -f docker-compose.test.yml up -d",
|
||||||
"db:test:down": "docker compose -f docker-compose.test.yml down -v",
|
"db:test:down": "docker compose -f docker-compose.test.yml down -v",
|
||||||
"migrate:up": "node migrate.js up",
|
"migrate:up": "node migrate.js up",
|
||||||
|
|||||||
@@ -18,7 +18,15 @@
|
|||||||
integration Backend Jest integration tests against a disposable Postgres.
|
integration Backend Jest integration tests against a disposable Postgres.
|
||||||
Brings the container up itself.
|
Brings the container up itself.
|
||||||
e2e Frontend Playwright tests. Needs the app stack up; start it
|
e2e Frontend Playwright tests. Needs the app stack up; start it
|
||||||
with .\scripts\start-local.ps1 first.
|
with .\scripts\start-local.ps1 -E2eDb first.
|
||||||
|
|
||||||
|
-E2eDb matters. Without it the suite runs against the
|
||||||
|
development database, which nothing truncates: every run seeds
|
||||||
|
more fixtures and leaves them, the unfiltered storefront grows
|
||||||
|
monotonically, and eventually rendering it outruns the
|
||||||
|
assertions' timeout. It then fails locally, passes in CI where
|
||||||
|
the database is fresh, and gets worse (#186). With it, the
|
||||||
|
stack runs against a throwaway database that starts empty.
|
||||||
all All three, in that order — cheapest and most isolated first,
|
all All three, in that order — cheapest and most isolated first,
|
||||||
so a failure that a later suite would also show up in is
|
so a failure that a later suite would also show up in is
|
||||||
reported by the suite that localises it best.
|
reported by the suite that localises it best.
|
||||||
|
|||||||
+40
-4
@@ -35,6 +35,19 @@ param(
|
|||||||
[int]$ApiPort = 3000,
|
[int]$ApiPort = 3000,
|
||||||
[int]$WebPort = 5173,
|
[int]$WebPort = 5173,
|
||||||
[switch]$Fresh,
|
[switch]$Fresh,
|
||||||
|
# Runs the stack against the throwaway end-to-end database instead of the
|
||||||
|
# development one: a separate container on a separate port, with tmpfs
|
||||||
|
# storage, so it starts empty every time.
|
||||||
|
#
|
||||||
|
# The e2e suite used to run against the development database, and nothing
|
||||||
|
# ever truncated it. Every run seeded more fixtures and left them, so the
|
||||||
|
# unfiltered storefront grew monotonically — 1,662 items by the time #186
|
||||||
|
# was filed — until rendering it outran the assertions' timeout. It failed
|
||||||
|
# locally, passed in CI where the database is fresh, and got steadily worse.
|
||||||
|
#
|
||||||
|
# This does not touch the development database, so anything you have set up
|
||||||
|
# there by hand survives.
|
||||||
|
[switch]$E2eDb,
|
||||||
[switch]$Stop,
|
[switch]$Stop,
|
||||||
# What -Stop puts the machine back to. nvm's default here is 18.16.1, which
|
# What -Stop puts the machine back to. nvm's default here is 18.16.1, which
|
||||||
# is too old to run this project's tooling but is what everything else on
|
# is too old to run this project's tooling but is what everything else on
|
||||||
@@ -52,10 +65,25 @@ $ErrorActionPreference = 'Stop'
|
|||||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||||
$StateDir = Join-Path $RepoRoot '.local'
|
$StateDir = Join-Path $RepoRoot '.local'
|
||||||
$PidFile = Join-Path $StateDir 'pids.json'
|
$PidFile = Join-Path $StateDir 'pids.json'
|
||||||
$Container = 'redefined-designs-local-db'
|
# Which database this run uses. Everything downstream reads these four, so the
|
||||||
$DbUser = 'redefined_local'
|
# choice is made once here rather than branched at each use.
|
||||||
$DbPassword = 'redefined_local'
|
if ($E2eDb) {
|
||||||
$DbName = 'redefined_local'
|
$Container = 'redefined-designs-e2e-db'
|
||||||
|
$DbUser = 'redefined_e2e'
|
||||||
|
$DbPassword = 'redefined_e2e'
|
||||||
|
$DbName = 'redefined_e2e'
|
||||||
|
# Not 55500 (development) and not 55432 (the integration suite). The three
|
||||||
|
# must not collide: the integration suite truncates between tests, so
|
||||||
|
# sharing a database with an e2e run would delete that run's fixtures
|
||||||
|
# underneath it (#116).
|
||||||
|
if (-not $PSBoundParameters.ContainsKey('DbPort')) { $DbPort = 55501 }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$Container = 'redefined-designs-local-db'
|
||||||
|
$DbUser = 'redefined_local'
|
||||||
|
$DbPassword = 'redefined_local'
|
||||||
|
$DbName = 'redefined_local'
|
||||||
|
}
|
||||||
|
|
||||||
function Write-Step { param([string]$Message) Write-Host "==> $Message" -ForegroundColor Cyan }
|
function Write-Step { param([string]$Message) Write-Host "==> $Message" -ForegroundColor Cyan }
|
||||||
function Write-Note { param([string]$Message) Write-Host " $Message" -ForegroundColor DarkGray }
|
function Write-Note { param([string]$Message) Write-Host " $Message" -ForegroundColor DarkGray }
|
||||||
@@ -165,11 +193,19 @@ function Start-Database {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Step "Creating the database container on port $DbPort"
|
Write-Step "Creating the database container on port $DbPort"
|
||||||
|
# tmpfs for the e2e database only: its contents are worthless the
|
||||||
|
# moment the run ends, and storing nothing is what makes it start empty
|
||||||
|
# every time rather than accumulating fixtures the way the development
|
||||||
|
# database did (#186). Migrations run on every start below, so an empty
|
||||||
|
# volume is a working one. The development database keeps its storage.
|
||||||
|
$storage = if ($E2eDb) { @('--tmpfs', '/var/lib/postgresql/data') } else { @() }
|
||||||
|
|
||||||
docker run -d --name $Container `
|
docker run -d --name $Container `
|
||||||
-e "POSTGRES_USER=$DbUser" `
|
-e "POSTGRES_USER=$DbUser" `
|
||||||
-e "POSTGRES_PASSWORD=$DbPassword" `
|
-e "POSTGRES_PASSWORD=$DbPassword" `
|
||||||
-e "POSTGRES_DB=$DbName" `
|
-e "POSTGRES_DB=$DbName" `
|
||||||
-p "${DbPort}:5432" `
|
-p "${DbPort}:5432" `
|
||||||
|
@storage `
|
||||||
postgres:16 *>$null
|
postgres:16 *>$null
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user