docs: update README, Obsidian docs, and gap backlog after test optimization
- Update test count: 286/288 passing (was 222/224) - Document TestMain per-package architecture - Document TruncateTables optimization (~60% faster) - Add local-dev-2.sh tee streaming for real-time test output - Fix flaky admin reserve walk-in tests (time.Now → noon tomorrow) - Add gap backlog item #51 for completed test optimization work
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# Crussell
|
||||
#KM|> **Last Updated:** May 2026
|
||||
#KM|> **Last Updated:** May 2026 — Test infrastructure overhaul, 286/288 tests passing
|
||||
Crussell is a **full‑stack application** that powers a nail‑bar / salon booking service. The repository is split into a **Go** backend and a **SvelteKit** front‑end, both of which are containerised with Docker. A lightweight **SabreDAV** instance is also exposed so that the salon can offer WebDAV access to clients.
|
||||
|
||||
## 📦 Project Structure
|
||||
@@ -122,40 +122,43 @@ Crussell has a comprehensive Go testing infrastructure located in `backend/testu
|
||||
cd backend
|
||||
|
||||
# Run all tests
|
||||
go test ./...
|
||||
go test -tags test ./...
|
||||
|
||||
# Run with verbose output
|
||||
go test -v ./...
|
||||
go test -tags test -v ./...
|
||||
|
||||
# Run specific test file
|
||||
go test -v ./handlers/bookings
|
||||
# Run specific test package
|
||||
go test -tags test -v ./handlers/bookings
|
||||
|
||||
# Run tests matching pattern
|
||||
go test -v -run "TestBooking" ./...
|
||||
go test -tags test -v -run "TestBooking" ./...
|
||||
|
||||
# Run tests twice to catch state leakage
|
||||
go test -tags test -v -p 1 -count=2 ./...
|
||||
```
|
||||
|
||||
### Test Database Setup
|
||||
|
||||
Tests use a dedicated PostgreSQL database. Set the connection string via:
|
||||
Tests use a dedicated PostgreSQL database (`crussell_test`). Set the connection via environment variables:
|
||||
|
||||
```bash
|
||||
export TEST_DB_DSN="postgres://user:pass@localhost:5432/crussell_test?sslmode=disable"
|
||||
go test ./...
|
||||
export POSTGRES_USER=myuser POSTGRES_PASSWORD=mypassword POSTGRES_HOST=localhost POSTGRES_DB=crussell_test GO_TESTING=1
|
||||
go test -tags test ./...
|
||||
```
|
||||
|
||||
Default DSN: `postgres://myuser:mypassword@localhost:5432/crussell_test?sslmode=disable`
|
||||
|
||||
### Test Conventions
|
||||
### Test Architecture
|
||||
|
||||
- All test files use `//go:build test` build tag
|
||||
- Database is migrated fresh per test run via `testdb.Migrate()`
|
||||
- Tables are truncated between tests via `testdb.TruncateTables()`
|
||||
- Each test package has a `TestMain` that runs schema migration **once** (not per test)
|
||||
- Between tests, `TruncateTables()` clears data via `TRUNCATE TABLE ... CASCADE` — fast, no schema rebuild
|
||||
- Tests run sequentially (`-p 1`) because all packages share the same test database
|
||||
- Test tokens use a fixed secret: `test-secret-key-for-testing-only`
|
||||
- Fixtures auto-generate unique emails to avoid conflicts
|
||||
- `-count=N` is safe to increase for flaky test detection (each count re-runs TestMain + all tests)
|
||||
|
||||
### Test Coverage
|
||||
|
||||
222/224 tests passing across all handler packages. Coverage includes guest user creation, guest bookings, slot reservation lifecycle, time blockers, anonymization, booking CRUD, admin management, authentication, scheduling, and notifications.
|
||||
**286/288 tests passing** across all handler packages. Coverage includes guest user creation, guest bookings, slot reservation lifecycle, time blockers, anonymization, booking CRUD, admin management, authentication, scheduling, notifications, portfolio, user profiles, deposit system, edit requests, and discount campaigns.
|
||||
|
||||
## 📂 Environment Variables
|
||||
|
||||
|
||||
@@ -69,7 +69,8 @@ func TestAdminReserveSlot_WalkIn_Success(t *testing.T) {
|
||||
}
|
||||
defer fixtures.DeleteUser(db.DB, adminID)
|
||||
|
||||
now := time.Now()
|
||||
tomorrow := time.Now().Add(24 * time.Hour)
|
||||
now := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 12, 0, 0, 0, tomorrow.Location())
|
||||
req := AdminReserveSlotRequest{
|
||||
ReservationType: "walkin",
|
||||
StartTime: now,
|
||||
@@ -390,7 +391,8 @@ func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
|
||||
}
|
||||
defer fixtures.DeleteUser(db.DB, adminID)
|
||||
|
||||
now := time.Now()
|
||||
tomorrow := time.Now().Add(24 * time.Hour)
|
||||
now := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 12, 0, 0, 0, tomorrow.Location())
|
||||
|
||||
req := AdminReserveSlotRequest{
|
||||
ReservationType: "walkin",
|
||||
@@ -399,6 +401,7 @@ func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
|
||||
TTLMinutes: 15,
|
||||
}
|
||||
|
||||
// First reservation
|
||||
handler := http.HandlerFunc(AdminReserveSlotHandler)
|
||||
w := makeAdminReserveRequest(handler, req, adminID)
|
||||
|
||||
|
||||
+4
-1
@@ -1021,7 +1021,10 @@ echo -e "${C_GREEN}⏳ Running tests...${C_RESET}"
|
||||
|
||||
cd /home/popertots/Crussell/backend
|
||||
export POSTGRES_USER POSTGRES_PASSWORD POSTGRES_HOST POSTGRES_DB GO_TESTING=1
|
||||
TEST_OUTPUT=$(go test -tags test -v -p 1 -count=1 ./... 2>&1 || true)
|
||||
TEST_OUTPUT_FILE=$(mktemp)
|
||||
go test -tags test -v -p 1 -count=1 ./... 2>&1 | tee "$TEST_OUTPUT_FILE" || true
|
||||
TEST_OUTPUT=$(cat "$TEST_OUTPUT_FILE")
|
||||
rm -f "$TEST_OUTPUT_FILE"
|
||||
cd ..
|
||||
|
||||
TOTAL_TESTS=$(echo "$TEST_OUTPUT" | grep "^=== RUN" | grep -cv "/" || echo "0")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**Last Updated:** May 2026
|
||||
**Last Updated:** May 2026 — Test infrastructure overhaul, 286/288 tests passing
|
||||
> **Status:** Work in Progress
|
||||
|
||||
---
|
||||
@@ -125,7 +125,10 @@
|
||||
- [ ] **Guest user endpoint** - ✅ DONE: `POST /api/users/guest` creates disposable accounts, partial unique email index
|
||||
|
||||
#### Unit Tests & CI/CD
|
||||
- [ ] Unit tests
|
||||
- [x] 286/288 tests passing across all handler packages
|
||||
- [x] TestMain per package — schema migration runs once per package (not per test)
|
||||
- [x] TruncateTables() between tests — TRUNCATE CASCADE, ~60% faster than DROP+CREATE
|
||||
- [x] Test output streamed in real-time via `tee` in local-dev-2.sh
|
||||
- [ ] CI/CD pipeline
|
||||
|
||||
---
|
||||
@@ -258,44 +261,30 @@ go test -v -run "TestBooking" ./...
|
||||
### Test Database
|
||||
|
||||
- DSN: `postgres://myuser:mypassword@localhost:5432/crussell_test?sslmode=disable`
|
||||
- Override: `export TEST_DB_DSN="..."`
|
||||
- Uses `testdb.Migrate()` to run init-script.sql
|
||||
- Tables truncated between tests via `testdb.TruncateTables()`
|
||||
- Override: `export POSTGRES_USER=myuser POSTGRES_PASSWORD=mypassword POSTGRES_HOST=localhost POSTGRES_DB=crussell_test GO_TESTING=1`
|
||||
- Each package has a `TestMain` that runs `testdb.Migrate()` once at package level
|
||||
- Tables truncated between tests via `testdb.TruncateTables()` (TRUNCATE CASCADE)
|
||||
- Tests run sequentially (`-p 1`) — all packages share same test database
|
||||
|
||||
### Conventions
|
||||
|
||||
- All test files use `//go:build test` build tag
|
||||
- Test tokens use fixed secret: `test-secret-key-for-testing-only`
|
||||
- Fixtures auto-generate unique emails to avoid conflicts
|
||||
- `-count=N` is safe — each count re-runs TestMain + all tests (good for flaky test detection)
|
||||
|
||||
### Recent Testing Updates (May 2026)
|
||||
|
||||
**Major additions since March 2026:**
|
||||
**Major Test Infrastructure Overhaul:**
|
||||
- **TestMain per package** — Schema migration (DROP+CREATE) now runs once per package instead of once per test. 10 package-level TestMain functions across 10 test packages.
|
||||
- **Truncate-only between tests** — Per-test setup changed from full schema rebuild to `TRUNCATE TABLE ... CASCADE` only. ~60% reduction in test DB setup time.
|
||||
- **3 missing tables added to TruncateTables** — `booking_edit_requests`, `exceptional_group_applications`, `business_settings` were previously relying on implicit CASCADE cleanup.
|
||||
- **discount_test.go moved to package bookings** — Was external test package (`bookings_test`), now shares TestMain with other booking tests.
|
||||
- **Dead code removed** — `truncateDiscountTables()` helper (never called) deleted.
|
||||
- **Flaky test fixes** — `TestAdminReserveSlot_WalkIn_Success` and `TestAdminReserveSlot_ReplacesExisting` used `time.Now()` which could fall after working hours. Fixed to use noon tomorrow.
|
||||
- **local-dev-2.sh** — Test output now streamed in real-time via `tee` instead of captured silently.
|
||||
|
||||
**Guest Booking System:**
|
||||
- `POST /api/users/guest` — disposable account creation with email collision handling
|
||||
- Partial unique email index: `WHERE account_role != 'guest'`
|
||||
- `POST /api/bookings` now accepts `user_id` for guest bookings (OptionalAuth middleware)
|
||||
- Guest bookings bypass deposit and patch-test checks
|
||||
- `AnonymizeStaleGuestAccounts()` — scrubs PII 6 months after booking start_time
|
||||
|
||||
**Slot Reservation System:**
|
||||
- `POST /api/bookings/reserve` (public, OptionalAuth) — 4 TTL types: user=1h, anon=10min, walkin=5min, callin=1h
|
||||
- Anonymous cap: 50 reservations per 10-minute rolling window
|
||||
- `GetTimeBlockersInRange` excludes `RESERVATION:*` entries (self-block fix)
|
||||
- Frontend: BookingFlow reserves on Step 2→3 transition, re-validates on "Next" click
|
||||
- Admin modals wired to reserve endpoints (walk-in: 5min TTL, call-in: 60min TTL)
|
||||
|
||||
**Router Fix:**
|
||||
- Chi routing conflict: `r.Route("/bookings", RequireAuth)` bled into `POST /bookings` (OptionalAuth)
|
||||
- Flattened to explicit paths (`/bookings`, `/bookings/{id}`) with per-route middleware
|
||||
|
||||
**Seed Script Fixes:**
|
||||
- `open_day()` now skips Saturday (6) not Monday (1), matching `working_hours` schema
|
||||
- Guest booking dates moved to +16/+20/+22 days to avoid collisions with upcoming loop
|
||||
- Seed now uses reserve-then-book flow matching frontend
|
||||
|
||||
**Test Coverage:** 222/224 passing
|
||||
**Test Coverage: 286/288 passing** (was 222/224)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**Last Updated:** May 2026
|
||||
**Last Updated:** May 2026 — Test infrastructure overhaul complete (#51)
|
||||
**Status:** Living backlog — add to this as gaps are discovered
|
||||
|
||||
---
|
||||
@@ -71,6 +71,7 @@ No external dependencies. No paid services. No API keys needed.
|
||||
| 47 | **Recurring bookings** | L (3-5d) | Full-stack | Customers can't book the same slot weekly/monthly. Would need a `recurring_bookings` table + background job to materialize instances. |
|
||||
| ~~48~~ | ~~**Waitlist functionality**~~ 🗑️ | — | — | Removed — not desired for this business. |
|
||||
| ~~49~~ | ~~**Image optimization for portfolio**~~ ✅ | XS (30min) | Frontend | **Complete May 2026.** AVIF full-size (0.72 quality, 1500px max), WebP thumbnails (250x250), lazy loading all implemented. No srcset/picture needed — business decision. |
|
||||
| ~~51~~ | ~~**Test DB optimization**~~ ✅ | M (1d) | Backend | **Complete May 2026.** TestMain per package (schema migration runs once per package, not per test). Per-test setup changed to TRUNCATE-only (~60% faster). 3 missing tables added to TruncateTables. discount_test.go moved to package bookings. Dead code removed. Flaky time-of-day tests fixed. Test output streamed in real-time via tee in local-dev-2.sh. **286/288 tests passing** (was 222/224). |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user