docs: update API docs and test counts for admin cancel
Update README and obsidian documentation with the new admin reservation cancel endpoint (DELETE /api/admin/bookings/reserve), updated API route tables, reservation TTL descriptions, and test count from 1,180 to 1,198.
This commit is contained in:
@@ -4,7 +4,7 @@ Nail salon booking platform — Go 1.25 backend + SvelteKit 5 SPA + PostgreSQL 1
|
||||
|
||||
## Features
|
||||
|
||||
**Booking**: Self-service (customer), walk-in (admin), call-in (admin). Slot reservations prevent double-booking (4 TTL types). **Self-blocking prevention**: `excludeUserID` parameter filters a user's own `RESERVATION` entries from time blocker overlap checks, allowing re-reservation and booking at overlapping slots. **Explicit cancellation**: `DELETE /api/bookings/reserve` releases a user's reservation. **Background cleanup**: 5-minute goroutine clears expired reservations. Guest accounts with GDPR-compliant anonymization (including `RESERVATION:edit_request:%` scrubbing). Service eligibility based on age + patch test validity. Overlap checks use `FOR UPDATE` row locks inside transactions. Closing-hours validation extracted into a reusable `closing_time` helper.
|
||||
**Booking**: Self-service (customer), walk-in (admin), call-in (admin). Slot reservations prevent double-booking (4 TTL types). **Self-blocking prevention**: `excludeUserID` parameter filters a user's own `RESERVATION` entries from time blocker overlap checks, allowing re-reservation and booking at overlapping slots. **Explicit cancellation**: `DELETE /api/bookings/reserve` releases a user reservation; `DELETE /api/admin/bookings/reserve` releases an admin walk-in/call-in reservation. **Background cleanup**: 5-minute goroutine clears expired reservations. Guest accounts with GDPR-compliant anonymization (including `RESERVATION:edit_request:%` scrubbing). Service eligibility based on age + patch test validity. Overlap checks use `FOR UPDATE` row locks inside transactions. Closing-hours validation extracted into a reusable `closing_time` helper.
|
||||
|
||||
**Payments**: Square Terminal (in-person) + Web Payments SDK (online). Cash with change calculation. Gift cards (12-digit code or account balance). Saved cards for faster checkout. Tips on completed bookings. Refunds with notice-period tiers and deposit protection (72h/24h thresholds). All payment types: deposit, full, partial, balance, tip. Payment >20% of total promotes `pending_release` bookings back to `confirmed`. Deposit paid is computed from payments on-the-fly. The first 50% of each payment is always carved out as deposit (via `buildSplitRecords`); any overflow beyond the booking total becomes a tip. A PostgreSQL `pg_advisory_lock` serializes payment attempts per-booking to prevent two-tab double-payment races. Gift card purchases now insert a pending payment record with VAT before calling Square — the DB transaction commits first, so Square failures leave a retryable pending record.
|
||||
|
||||
@@ -76,7 +76,7 @@ Default logins (password: `password`):
|
||||
```bash
|
||||
cd backend && go build -o bin/backend ./main.go
|
||||
cd frontend && npm ci && npm run build
|
||||
cd backend && go test -tags "test,dev" -count=1 -parallel 8 ./... # 1,180 tests, 0 failures, 4 skipped (~14s)
|
||||
cd backend && go test -tags "test,dev" -count=1 -parallel 8 ./... # 1,198 tests, 0 failures, 4 skipped (~13s)
|
||||
cd backend && go test -tags "test,dev" -count=1 -race -timeout 480s ./... # race detector (all packages, ~4min)
|
||||
cd backend && go test -tags "test,dev" -count=10 -parallel 8 ./... # thorough verification (~2-3min)
|
||||
```
|
||||
|
||||
@@ -215,7 +215,7 @@ npm run dev # Dev server with HMR
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go test -tags "test,dev" ./... # 1,180 tests, 0 failures, 4 skipped
|
||||
go test -tags "test,dev" ./... # 1,198 tests, 0 failures, 4 skipped
|
||||
go test -tags "test,dev" -v -run TestName ./... # Single test
|
||||
```
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ Backend (:8080)
|
||||
| Package | File(s) | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `handlers/auth` | local.go, social.go | Registration (with referral code validation), login, refresh, email verification |
|
||||
| `handlers/bookings` | bookings.go, reserve.go, manage.go, admin_reserve.go, cancel_reservation.go | Booking CRUD, reservations with **self-blocking prevention** (`excludeUserID` parameter on `CheckTimeBlockerOverlap` + pre-overlap DELETE with IP hash anon cleanup), admin management, edit requests, discounts, closing hours validation, active booking limits, GetBookingsByCreatedRange, created_by_name resolution, **explicit reservation cancellation** (`DELETE /api/bookings/reserve`) |
|
||||
| `handlers/bookings` | bookings.go, reserve.go, manage.go, admin_reserve.go, cancel_reservation.go, admin_cancel_reservation.go | Booking CRUD, reservations with **self-blocking prevention** (`excludeUserID` parameter on `CheckTimeBlockerOverlap` + pre-overlap DELETE with IP hash anon cleanup), admin management, edit requests, discounts, closing hours validation, active booking limits, GetBookingsByCreatedRange, created_by_name resolution, **explicit reservation cancellation** (`DELETE /api/bookings/reserve` for users, `DELETE /api/admin/bookings/reserve` for admin walk-in/call-in) |
|
||||
| `handlers/payments` | handlers.go, service.go, validators.go, giftcards.go, till.go, refunds.go, refund_policy.go | Square payments: terminal, online, refunds, tips, saved cards, gift cards (CRUD, topup, transfer, redeem, buy, expired balances, till sales). Refund calculation with notice-period tiers and deposit protection |
|
||||
| `handlers/webhooks` | square.go | Square webhook handler for payment status updates. **Fail-closed signature check** — rejects requests with 403 when `SQUARE_WEBHOOK_SIGNATURE_KEY` is set but header is missing. Dev mode: skips verification when env var is empty. Still uses hex-encoding stub (`verifySquareSignature`) — production requires HMAC-SHA256 with base64 output, `x-square-hmacsha256-signature` header. See `TODO(PROD)` in source. |
|
||||
| `handlers/admin` | users.go, analytics.go, custom_services.go, discount_campaigns.go, settings.go | Admin user management, custom services CRUD (list/create/get/update/promote/delete), discount campaigns, analytics (stub), business settings (GET/PUT with VAT, gift card config) |
|
||||
@@ -315,6 +315,7 @@ CORS uses `*` in local dev. In production behind Cloudflare, nginx handles CORS.
|
||||
| DELETE | `/api/bookings/{id}/edit-request` | Cancel edit request |
|
||||
| GET | `/api/bookings/{id}/edit-request` | View own pending edit request (enriched) |
|
||||
| GET | `/api/bookings/edit-requests` | List all own pending edit requests (enriched) |
|
||||
| DELETE | `/api/bookings/reserve` | Release user's own active reservation. Targets `RESERVATION:user:%` only (does not touch admin or anon reservations — partitioned by WHERE clause). Idempotent: 200 with no-op if no reservation exists. |
|
||||
| POST | `/api/bookings/{id}/payment` | Create online payment (deposit, full, partial, balance) |
|
||||
| POST | `/api/bookings/{id}/tip` | Add tip to completed booking |
|
||||
| GET | `/api/bookings/{id}/payment-summary` | Get payment summary for booking |
|
||||
@@ -342,6 +343,7 @@ CORS uses `*` in local dev. In production behind Cloudflare, nginx handles CORS.
|
||||
| POST | `/api/admin/bookings/{id}/confirm` | Confirm booking |
|
||||
| POST | `/api/admin/bookings/{id}/cancel` | Cancel booking |
|
||||
| POST | `/api/admin/bookings/reserve` | Reserve slot (walkin=5min, callin=1h) |
|
||||
| DELETE | `/api/admin/bookings/reserve` | Release admin's walk-in/call-in reservation. Targets `RESERVATION:admin:%` only (does not touch user or anon reservations — partitioned by WHERE clause). Idempotent: 200 with no-op if no reservation exists. |
|
||||
| GET | `/api/admin/bookings/{id}/edit-requests` | List edit requests (paginated, with total) |
|
||||
| GET | `/api/admin/bookings/edit-requests` | List ALL edit requests (enriched) |
|
||||
| GET | `/api/admin/bookings/{id}/edit-request` | View pending edit request for specific booking (enriched) |
|
||||
@@ -524,8 +526,8 @@ CORS uses `*` in local dev. In production behind Cloudflare, nginx handles CORS.
|
||||
|------|-----|---------------------|
|
||||
| Logged-in user | 1 hour | `RESERVATION:user:{userID}:{timestamp}` |
|
||||
| Anonymous user | 10 minutes | `RESERVATION:anon:{ipHash}:{timestamp}` |
|
||||
| Admin walk-in | 5 minutes | `RESERVATION:admin:walkin:{timestamp}` |
|
||||
| Admin call-in | 1 hour | `RESERVATION:admin:callin:{timestamp}` |
|
||||
| Admin walk-in | 5 minutes (15min cleanup) | `RESERVATION:admin:walkin:{customerID}:{timestamp}` |
|
||||
| Admin call-in | 1 hour (15min cleanup) | `RESERVATION:admin:callin:{customerID}:{timestamp}` |
|
||||
| Edit request | 24 hours | `RESERVATION:edit_request:{timestamp}` |
|
||||
|
||||
**Anonymous Cap:** 50 reservations per 10-minute rolling window. Returns 429 if exceeded.
|
||||
@@ -1242,7 +1244,7 @@ Files with this pattern: `bookings.go` (4 handlers), `custom_services.go`, `user
|
||||
|
||||
### Test Coverage
|
||||
|
||||
**1,180 tests run** across all packages (4 skipped, 0 failures). Recent additions: self-blocking prevention tests (excludeUserID coverage for GetAvailableHours, EditBookingHandler, AdminRescheduleBookingHandler, ReserveSlotHandler anon IP cleanup, AdminReserveSlotHandler anon IP cleanup), closing_time tests (3), content-type middleware tests (2), booking handler tests (FOR UPDATE overlap checks, admin reserve with closing_time, gift card buy with VAT). Booking integration tests continue to expand: duplicate completion guard, daily stamp cap (handler + SQL subquery), invalid status transitions, sequential edit, timezone independence, and past-booking no-show guard. The `clock` package itself has tests for Now() and clock interface correctness.
|
||||
**1,198 tests run** across all packages (4 skipped, 0 failures). Recent additions: self-blocking prevention tests (excludeUserID coverage for GetAvailableHours, EditBookingHandler, AdminRescheduleBookingHandler, ReserveSlotHandler anon IP cleanup, AdminReserveSlotHandler anon IP cleanup), closing_time tests (3), content-type middleware tests (2), booking handler tests (FOR UPDATE overlap checks, admin reserve with closing_time, gift card buy with VAT), and the new admin reservation cancel coverage (12 tests covering walkin + callin + isolation + no-op + idempotency + response format parity, plus 2 inverse-isolation tests on the user-side `cancel_reservation_test.go` to prove the two cancel endpoints are properly partitioned by their WHERE clauses). Booking integration tests continue to expand: duplicate completion guard, daily stamp cap (handler + SQL subquery), invalid status transitions, sequential edit, timezone independence, and past-booking no-show guard. The `clock` package itself has tests for Now() and clock interface correctness.
|
||||
|
||||
| Package | Coverage Area |
|
||||
|---------|--------------|
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Testing Architecture & DB Management
|
||||
|
||||
**Last Updated:** June 2026 (v2 — flakiness elimination, full t.Parallel coverage, titleCaser concurrency fix, -count=10 verification)
|
||||
**Last Updated:** July 2026 (v3 — admin reservation cancel coverage, 1,198 tests)
|
||||
|
||||
---
|
||||
|
||||
@@ -499,9 +499,9 @@ This appears in `TestAccount_DeleteGuest` and `TestLoyalty_Get`. The `dav.Servic
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Quick check (`-count=1`) | **~14s** |
|
||||
| Quick check (`-count=1`) | **~13s** |
|
||||
| Packages | 19 tested, 0 failures |
|
||||
| Tests | 1,169 run, 4 skipped, 0 failing |
|
||||
| Tests | 1,198 run, 4 skipped, 0 failing |
|
||||
|
||||
New test additions in this batch:
|
||||
| Test | Coverage |
|
||||
@@ -516,8 +516,11 @@ New test additions in this batch:
|
||||
| `TestContentType_*` | 2 tests for the new `JsonContentType` middleware |
|
||||
| `TestAdminReserveSlot_*` (expanded) | New overlap coverage using `FOR UPDATE` inside transactions |
|
||||
| `TestBuyGiftCard_*` (expanded) | Tests for pending-payment-first flow with VAT integration |
|
||||
| `TestAdminCancelReservation_*` (12 new) | New admin reservation-cancel coverage: walkin + callin success, isolation, no-op, unauth, empty ctx, walkin+callin coexistence, anon untouched, response format parity with user handler, overlapping reservations deleted, user reservations untouched, idempotent double-cancel |
|
||||
| `TestCancelReservation_DoesNotTouchAnonReservations` | Inverse-isolation test — user cancel ignores `RESERVATION:anon:%` (defensive — the WHERE clause only matches `RESERVATION:user:%`) |
|
||||
| `TestCancelReservation_DoesNotTouchAdminReservations` | Inverse-isolation test — user cancel ignores `RESERVATION:admin:%`. Pairs with the admin-side test that verifies admin cancel ignores `RESERVATION:user:%`. Proves the two endpoints are properly partitioned. |
|
||||
|
||||
**Total tests:** 1,169 run (4 skipped) across 19 packages. 0 failures. Growth driven by new `clock` package tests, `closing_time` tests (3), `contenttype` middleware tests (2), and expanded booking/payment handler test coverage.
|
||||
**Total tests:** 1,198 run (4 skipped) across 19 packages. 0 failures. Growth driven by new `clock` package tests, `closing_time` tests (3), `contenttype` middleware tests (2), expanded booking/payment handler test coverage, and the new admin reservation cancel coverage (12 tests: walkin + callin success, isolation, no-op, unauth, empty ctx, walkin+callin coexistence, anon untouched, response format parity, overlapping reservations deleted, user reservations untouched, idempotent double-cancel). Two inverse-isolation tests added to the existing user-side `cancel_reservation_test.go` to verify the user and admin cancel endpoints are properly partitioned by `RESERVATION:user:%` / `RESERVATION:admin:%` WHERE clauses.
|
||||
|
||||
### What Drives Test Time
|
||||
|
||||
@@ -634,9 +637,9 @@ This shouldn't appear anymore — the auth package's TestMain was updated to use
|
||||
|
||||
### Q: What's the total test count?
|
||||
|
||||
1,180 tests run across all packages (4 skipped). 0 failures across 20 packages.
|
||||
1,198 tests run across all packages (4 skipped). 0 failures across 20 packages.
|
||||
|
||||
**Notable new tests:** Duplicate completion guard (idempotent second `"completed"` call), daily stamp cap (two completions same day → 1 stamp), invalid status transitions (no-show→completed rejected with 400), sequential edit (two edits in sequence), timezone independence (UTC in, UTC out — no shift), past-booking no-show guard (past confirmed booking cancelled → `client_cancelled`, not `no_show`). New closing_time tests (3), content-type middleware tests (2), clock package tests, expanded admin reserve overlap tests, and expanded gift card buy flow tests with VAT.
|
||||
**Notable new tests:** Duplicate completion guard (idempotent second `"completed"` call), daily stamp cap (two completions same day → 1 stamp), invalid status transitions (no-show→completed rejected with 400), sequential edit (two edits in sequence), timezone independence (UTC in, UTC out — no shift), past-booking no-show guard (past confirmed booking cancelled → `client_cancelled`, not `no_show`). New closing_time tests (3), content-type middleware tests (2), clock package tests, expanded admin reserve overlap tests, expanded gift card buy flow tests with VAT, and full admin reservation cancel coverage (12 tests covering walkin + callin + isolation + no-op + idempotency + response format parity).
|
||||
|
||||
### Q: Why use `-count=10` for thorough verification?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user