docs: add Gift Card Terms, Privacy Policy, Terms, and Testing Architecture

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-11 22:08:32 +01:00
co-authored by Sisyphus
parent c1fc1a2314
commit 181c58147d
4 changed files with 649 additions and 0 deletions
@@ -0,0 +1,156 @@
# Gift Card Terms & Conditions
**Last Updated:** June 2026
**Status:** DRAFT — Local development (not yet in production)
---
## 1. Introduction
These Terms & Conditions ("Gift Card Terms") apply to all gift cards, vouchers, and prepaid balances issued by Crussell Salon. They supplement our [[Terms & Conditions - Overall App|General Terms & Conditions]].
By purchasing, receiving, or using a gift card, you agree to these Gift Card Terms.
---
## 2. Gift Card Purchase
### 2.1 Purchase Methods
Gift cards can be purchased:
- **Online:** Via our Platform (card or saved payment method)
- **In-store:** Cash, card, or other accepted payment methods
- **As gifts:** Purchased for another person (recipient receives card code)
### 3.2 Denominations
- Minimum value: £5
- Maximum value: £500 per card
- Custom amounts accepted (within range)
### 3.3 VAT Treatment
- Gift cards are **Single-Purpose Vouchers (SPVs)** under UK VAT law.
- **VAT is charged at point of purchase**, not at redemption.
- When you pay with a gift card, no additional VAT is charged (already paid).
- This applies to both direct gift card payments and account balance payments.
**Legal basis:** HMRC VAT Notice 700/12, EU VAT Directive Article 30a
---
## 3. Gift Card Expiry
### 3.1 Expiry Period
Gift cards expire **24 months (2 years) after last use**.
**"Last use" includes:**
- Redeeming the card to an account
- Topping up the card
- Checking the balance
- Using the card for payment
- Any admin action on the card
### 3.2 Rolling Expiry
The 24-month period **resets** with each use. For example:
- Card purchased: 1 January 2024
- Balance checked: 1 June 2024 → Expiry now 1 June 2026
- Card topped up: 1 December 2024 → Expiry now 1 December 2026
### 3.3 Expiry Notification
We will send warning emails before expiry (if purchaser contact info available):
- **1 month before expiry:** "Your gift card [CODE] expires in 30 days with £X remaining"
- **1 week before expiry:** "Your gift card [CODE] expires in 7 days with £X remaining"
### 3.4 After Expiry
- Unredeemed gift cards: Balance becomes dormant, transferred to recovery system.
- No automatic refund.
- Recovery possible with card code (contact us within 6 years).
**Legal basis:**
- UK Consumer Rights Act 2015: Expiry terms must be "fair and transparent"
- CMA guidance: 24 months is industry standard (John Lewis, M&S, Sainsbury's)
- Under 12 months risks being challenged as unfair contract term
---
## 4. Gift Card Redemption
### 4.1 Redemption Options
You can redeem a gift card in two ways:
**Option A: Redeem to Account Balance**
- Enter card code in your account.
- Full balance transferred to your account.
- Card becomes invalid (balance now in account).
- Account balance does not expire (but account may be deleted after 5 years idle).
**Option B: Use Directly for Payment**
- Enter card code at checkout.
- Deducted directly from card balance.
- Remaining balance stays on card (subject to expiry).
- Can split payment across multiple cards/methods.
### 4.2 Redemption Restrictions
- Gift cards cannot be exchanged for cash.
- Gift cards cannot be used to purchase other gift cards.
- Gift cards are non-transferable after redemption to account.
- Lost/stolen cards: We cannot replace unless we have record of purchase.
---
## 5. Account Balances
### 5.1 Expiry
- **Account balances do not expire.**
- However, your account may be deleted after 5 years of inactivity (see [[Terms & Conditions - Overall App#1.3 Inactive Account Policy|General T&Cs Section 1.3]]).
### 5.2 Account Deletion with Balance
If your account is deleted (due to inactivity or your request):
- Balance becomes **dormant** (not forfeited).
- Transferred to our recovery system.
- You receive your **Account ID** via email.
- You can recover balance at any time by providing Account ID.
- No deadline for recovery (but records deleted after 7 years per HMRC).
---
## 6. Dormant Balance Recovery
### 6.1 What is a Dormant Balance?
A dormant balance is an account balance from a deleted account. It remains recoverable indefinitely, but we no longer hold your personal data (GDPR compliance).
### 6.2 Recovery Process
To recover a dormant balance:
1. Contact us with your **Account ID** (provided in deletion email).
2. We verify the Account ID against our dormant balance records.
3. If valid, we refund the balance via your preferred method (bank transfer, new gift card, or account credit).
### 6.3 Verification
- **Account ID is required** — we cannot verify claims without it.
- After account deletion, we have no way to identify you (by design — GDPR).
- Lost Account ID = lost balance (we cannot recover without verification).
### 6.4 Legal Basis
- UK consumer protection law: Gift card balances are customer's money held by business.
- CMA unfair terms analysis: Recovery mechanism reduces legal risk from 40-50% to 10-20%.
- Analogous to Dormant Bank and Building Society Accounts Act 2008 (15-year dormancy).
---
## Appendix: VAT Examples
### Example 1: Direct Gift Card Payment
- Service cost: £60 (inc. VAT @ 20% = £10 VAT)
- Gift card purchased for £60 (inc. £10 VAT already paid)
- Payment with gift card: £60 deducted
- **No additional VAT charged** (already paid at gift card purchase)
### Example 2: Account Balance Payment
- Gift card £50 redeemed to account (inc. £8.33 VAT already paid)
- Service cost: £30 (inc. VAT @ 20% = £5 VAT)
- Payment with account balance: £30 deducted
- **No additional VAT charged** (already paid at gift card purchase)
### Example 3: Split Payment
- Service cost: £60 (inc. VAT @ 20% = £10 VAT)
- Gift card balance: £40 (inc. £6.67 VAT already paid)
- Cash payment: £20 (inc. £3.33 VAT charged now)
- **Total VAT: £10** (£6.67 from gift card + £3.33 from cash)
+99
View File
@@ -0,0 +1,99 @@
# Privacy Policy
**Last Updated:** June 2026
**Status:** DRAFT — Local development (not yet in production)
---
## 1. Introduction
This Privacy Policy explains how Crussell Salon ("we", "us", "our") collects, uses, and protects your personal data when you use our booking platform ("Platform").
We are committed to protecting your privacy and complying with the **UK General Data Protection Regulation (UK GDPR)** and **Data Protection Act 2018**.
**Data Controller:**
Crussell Salon
Edinburgh, Scotland
Email: help@crussell.invalid
---
## 2. Data We Collect
### 2.1 Personal Data (Identifiable Information)
**Account Information:**
- Name (first, last)
- Email address
- Phone number
- Date of birth (optional, for age verification)
- Account ID (for balance recovery after deletion)
**Booking Information:**
- Appointment dates, times, services
- Treatment notes and preferences
- Allergy and patch test records (health data — special category)
- Payment history and transaction records
**Financial Data:**
- Gift card codes and balances
- Account balances
- Payment transaction records (processed via Square, not stored by us)
- Dormant balance records (Account ID only, no PII)
### 2.2 Special Category Data (Health Data)
We collect health-related information with your **explicit consent**:
- Allergy records
- Patch test results
- Medical conditions affecting treatment
- Skin sensitivity notes
**Legal basis:** UK GDPR Article 9(2)(a) — Explicit consent
**Retention:** 7 years (insurance requirement) or account deletion (whichever is later)
---
## 3. Data Retention & Deletion Process
### 3.1 Retention Schedule
| Data Category | Retention Period | Legal Basis |
|---------------|------------------|-------------|
| **Active account data** | Account active + 2 years | Legitimate interest |
| **Inactive accounts (no balance)** | 2 years idle | GDPR storage limitation |
| **Inactive accounts (with balance)** | 5 years idle | Scottish prescriptive period |
| **Financial records** | 7 years | HMRC requirement |
| **Allergy/health records** | 7 years | Insurance requirement |
| **Dormant balances** | Indefinite (Account ID only) | Recovery mechanism |
| **Marketing preferences** | Until withdrawn | Consent |
### 5.2 Deletion Process
**Account deletion (your request):**
1. You confirm deletion (warning about data loss).
2. If balance exists, transferred to dormant balance system.
3. Account ID sent to you via email.
4. Personal data anonymized (name, email, phone replaced with placeholders).
5. Financial records retained 7 years (HMRC) then aggregated.
6. Allergy records retained 7 years (insurance) then deleted.
**Inactive account deletion (automatic):**
1. Warning emails sent at 18/23 months (no balance) or 4/59 months (with balance).
2. If no activity, account deleted as above.
3. Dormant balance recoverable with Account ID.
---
## 4. Your Rights
Under UK GDPR, you have the right to:
- **Access** your personal data (Article 15)
- **Rectify** inaccurate data (Article 16)
- **Erase** your data (Article 17 — subject to HMRC/insurance retention)
- **Restrict** processing (Article 18)
- **Data Portability** (Article 20)
- **Object** to processing (Article 21)
- **Withdraw Consent** (Article 7(3))
To exercise these rights, contact help@crussell.invalid. You also have the right to complain to the Information Commissioner's Office (ICO) at any time.
@@ -0,0 +1,140 @@
# Terms & Conditions — Overall App
**Last Updated:** June 2026
**Status:** DRAFT — Local development (not yet in production)
---
## What is this document?
These Terms & Conditions ("Terms") govern your use of the Crussell booking platform ("Platform"), accessible via our website and associated mobile applications.
By creating an account or making a booking through our Platform, you agree to be bound by these Terms. If you do not agree, please do not use our services.
**Business Details:**
- **Trading Name:** Crussell Salon
- **Registered Address:** Edinburgh, Scotland
- **Contact Email:** help@crussell.invalid
- **VAT Status:** Not currently VAT registered (threshold is £90,000; will register when reached)
---
## Tech Stack & Operations
For complete technical and structural details of the platform, see the [[Technical Manual]] and [[Overview]].
---
## 1. Account Creation & Deletion
### 1.1 Account Eligibility
- You must be at least 16 years old to create an account.
- You must provide accurate, current contact information.
- You are responsible for maintaining account security.
### 1.2 Account Deletion
You may request account deletion at any time. Upon deletion:
**If your account has no balance:**
- All personal data will be anonymized or deleted.
- Booking history will be retained for 7 years (HMRC requirement) then aggregated.
- You will lose access to loyalty stamps, referral codes, and booking history.
**If your account has a balance:**
- Your balance will become dormant and transferred to our recovery registry.
- You will receive your **Account ID** via email — keep this ID safe.
- You can recover your balance at any time by providing your Account ID.
- All other personal data will be anonymized.
**Warning:** Account deletion is permanent. You will lose:
- All booking history and treatment notes.
- Allergy and patch test records (you'll need to provide these again if you return).
- Loyalty stamps and referral codes.
- Access to your account balance (unless you retain your Account ID).
### 1.3 Inactive Account Policy
To comply with GDPR storage limitation principles, we delete inactive accounts:
- **No balance:** Deleted after 2 years of inactivity.
- **With balance:** Deleted after 5 years of inactivity (Scottish prescriptive period).
**Warning emails sent before deletion:**
- 18 months idle (no balance): 6-month warning.
- 23 months idle (no balance): 30-day warning.
- 4 years idle (with balance): 1-year warning with balance amount.
- 59 months idle (with balance): 30-day warning with balance amount.
All warning emails include your Account ID for future balance recovery.
---
## 2. Bookings & Appointments
### 2.1 Making a Booking
- Bookings are subject to availability.
- You will receive confirmation via email/SMS.
- Some services require a deposit (typically 20-50% of service cost).
### 2.2 Cancellations & Rescheduling
- **Client cancellation:** Must be made at least 24 hours before appointment.
- **Late cancellation (<24 hours):** Deposit may be forfeited.
- **No-show:** Deposit forfeited, may affect future booking eligibility.
- **Business cancellation:** Full refund or reschedule offered.
### 2.3 Deposits
- Deposits are non-refundable if you cancel <24 hours before appointment.
- Deposits are applied to your final bill.
- If we cancel, deposit is fully refunded.
### 2.4 Service Changes
- We reserve the right to refuse service for health/safety reasons.
- Patch tests may be required for certain treatments (allergy records retained for 7 years).
- Service prices may change; you'll be notified before booking.
---
## 3. Payments & Fees
### 3.1 Payment Methods
- Online card payments (via Square)
- In-person card/cash payments
- Gift cards
- Account balance (from redeemed gift cards)
### 3.2 Payment Processing
- Card payments processed securely via Square.
- We do not store full card details.
- Refunds processed to original payment method within 5-10 business days.
### 3.3 Split Payments
- You may split payment across multiple methods (e.g., gift card + cash).
- Each payment method processed separately.
- Refunds apply proportionally to each payment method.
---
## 4. Gift Cards & Account Balances
For detailed gift card terms, see [[Gift Card Terms & Conditions]].
### 4.1 Gift Card Expiry
- Gift cards expire **24 months after last use** (rolling expiry).
- "Last use" includes: redemption, top-up, balance check, or any admin action.
- Expiry date displayed on gift card and in your account.
### 4.2 Account Balances
- Once redeemed to your account, balance does **not** expire.
- However, your account may be deleted after 5 years of inactivity.
- If account deleted with balance, funds become dormant but recoverable with Account ID.
### 4.3 VAT Treatment
- Gift cards are Single-Purpose Vouchers (SPVs) under UK VAT law.
- VAT charged at point of gift card purchase, **not** at redemption.
- When you pay with gift card balance, no additional VAT charged (already paid).
---
## Appendix: Statutory Timeframes & Legal References
- **HMRC Corporation Tax records:** **6 years** from the end of the financial year (HMRC CH14600 / Companies Act 2006 s.388). We aggregate detailed records after **7 years** to maintain a safe buffer.
- **Scottish Contract Claims prescriptive period:** **5 years** (Prescription and Limitation (Scotland) Act 1973 s.6). Accounts with remaining balances must remain active for at least **5 years** to allow claims.
- **GDPR Storage Limitation:** **2 years** inactivity default for accounts with no balance (legitimate interest in relationship ends).
@@ -0,0 +1,254 @@
# Testing Architecture & DB Management
**Last Updated:** June 2026
**Status:** Technical Reference for Developers and AI Agents
---
## 1. Overview & Architecture
The Platform's testing suite uses a **real database approach** for integration and handler testing, combined with unit tests for pure logic.
```
TEST ARCHITECTURE
┌──────────────────────────────────────────────────────────────────┐
│ go test (Tool Harness) │
└──────────────────────────────────────────────────────────────────┘
│ (Concurrency controlled via -p 1 or Advisory Locks)
┌──────────────────────────────────────────────────────────────────┐
│ TestMain (Package Level) │
│ - Initializes testdb.NewPool() │
│ - Calls testdb.Migrate() │
└──────────────────────────────────────────────────────────────────┘
▼ (Acquires Global pg_advisory_lock(1337) on dedicated conn)
┌──────────────────────────────────────────────────────────────────┐
│ testdb.Migrate() │
│ - Sequentially drops all existing tables/types CASCADE │
│ - Parses init-script.sql statement-by-statement │
│ - Re-creates full fresh database schema │
│ ⚠️ ALL operations on the SAME connection (pool.Acquire) │
└──────────────────────────────────────────────────────────────────┘
▼ (Releases Advisory Lock, releases connection)
┌──────────────────────────────────────────────────────────────────┐
│ Individual Tests │
│ - Calls resetTestData(t) (Truncates all tables sequentially) │
│ - Seeds package-specific fixtures │
│ - Executes test logic and HTTP handlers │
│ ⚠️ TruncateTables uses pool.Acquire + pg_advisory_lock(1338) │
└──────────────────────────────────────────────────────────────────┘
```
### Key Core Principles:
- **No Mocking the DB:** We use a real PostgreSQL database instance (`crussell_test` or `mydb`) to ensure that all constraints, triggers, and foreign keys are validated exactly as they would behave in production.
- **Transactional Isolations:** Tests run sequentially within a package, and each test resets the database to a clean, default-seeded state.
- **Parallel Resilience:** Concurrency at the package level is handled safely via database-level **advisory locks** so that parallel packages never deadlock each other's DDL schema operations.
- **⚠️ Advisory locks require a dedicated connection:** Lock, work, and unlock must all use the same pgx connection. Use `pool.Acquire()` (not `pool.Exec()`) whenever acquiring advisory locks. See §2.2 for the correct pattern.
---
## 2. Database Management & Schema Initialization
### 2.1 The TestMain Pattern
Every Go package containing database integration tests (e.g., `payments`, `scheduling`, `bookings`) defines a `TestMain` function inside a test file (e.g., `testmain_test.go` or package-specific test file).
This function orchestrates the lifecycle of the connection pool for that package:
```go
func TestMain(m *testing.M) {
// 1. Establish the connection pool
pool, err := testdb.NewPool("")
if err != nil {
os.Exit(1)
}
// 2. Perform sequential schema migration with advisory locks
testdb.Migrate(&testing.T{}, pool)
db.DB = pool
jwt.Init()
// 3. Run the tests
code := m.Run()
// 4. Close the pool and exit
pool.Close()
os.Exit(code)
}
```
### 2.2 Global Advisory Locking (`testdb.Migrate`)
When multiple Go packages are tested concurrently, they all try to drop and recreate the schema on the same physical test database. To prevent PostgreSQL system catalog deadlocks, `testdb.Migrate` acquires a session-level advisory lock:
```go
// CRITICAL: pool.Acquire() ensures lock, work, and unlock all use the SAME connection.
// pg_advisory_lock is session-level — using pool.Exec() for each would route
// lock and unlock to different connections, rendering the lock useless.
conn, err := pool.Acquire(ctx)
if err != nil {
t.Fatalf("Failed to acquire connection for migration: %v", err)
}
defer conn.Release()
_, err = conn.Exec(ctx, "SELECT pg_advisory_lock(1337)")
if err != nil {
t.Fatalf("Failed to acquire migration advisory lock: %v", err)
}
defer conn.Exec(ctx, "SELECT pg_advisory_unlock(1337)")
```
**Why `pool.Acquire` instead of `pool.Exec`:**
- `pool.Exec(ctx, ...)` borrows a connection from the pool and returns it immediately. Each call may use a **different** connection.
- `pg_advisory_lock(1337)` is acquired on connection A, but your DDL runs on connection B, and `pg_advisory_unlock(1337)` fires on connection C. The lock is never actually held during the work.
- `pool.Acquire()` grabs a **dedicated** connection and holds it. Every subsequent `conn.Exec()` uses the same session, so the lock actually protects the critical section.
### 2.3 The "Drop & Recreate" Clean Slate
Before running `init-script.sql`, `testdb.Migrate` actively drops every single table, sequence, and enum custom type in CASCADE order to ensure zero pollution from previous test runs.
- **Drop Order Array (`dropOrder`):** Defined in `backend/testutils/testdb/testdb.go`. New tables must be appended at the **beginning** of this list to resolve foreign key constraints on drop.
- **Type Drops Array (`typeDrops`):** Holds custom Postgres enums. New types must be added here to be cleared cleanly.
### 2.4 TruncateTables Advisory Locking
`testdb.TruncateTables` is called by every test via `resetTestData(t)` to reset state between tests. It truncates all tables in dependency order using `TRUNCATE ... CASCADE`.
```go
func TruncateTables(t *testing.T, pool *pgxpool.Pool) {
conn, err := pool.Acquire(ctx)
// ... same pool.Acquire pattern as Migrate ...
_, err = conn.Exec(ctx, "SELECT pg_advisory_lock(1338)")
// ... all truncations use conn.Exec (same connection) ...
defer conn.Exec(ctx, "SELECT pg_advisory_unlock(1338)")
}
```
**Why this matters:**
- `TRUNCATE TABLE users CASCADE` acquires `ACCESS EXCLUSIVE` locks on `users` AND all tables with FK references to `users`.
- If two `TruncateTables` calls run concurrently (from different packages or processes), one may hold a lock on `users` while the other holds a lock on `gift_cards` (which references `users`). Each needs the other's lock to complete CASCADE → **deadlock**.
- `pg_advisory_lock(1338)` serializes all truncation across all connections, preventing this deadlock.
- **Lock IDs must not collide:** Migrate uses lock 1337, TruncateTables uses lock 1338.
### 2.5 Statement-by-Statement SQL Parser
Instead of executing the raw 2000-line `init-script.sql` as a single string (which can trigger pgx protocol hangs on DDL), `testdb.go` uses an in-memory statement splitter (`splitSQLStatements`) that respects dollar-quoted PL/pgSQL function blocks (`$$`) and executes DDL sequentially.
---
## 3. How to Run the Tests
### 3.1 Environment Variables
To run integration tests successfully, your environment must be populated with these variables:
| Var Name | Purpose | Example Value |
|----------|---------|---------------|
| `TEST_DB_DSN` | Connection string for the test database | `postgres://myuser:mypassword@localhost:5432/crussell_test?sslmode=disable` |
| `JWT_SECRET_KEY` | Secret used to sign test JWT tokens | `my_test_secret_key` |
| `GO_TESTING` | Flag to trigger mock clients / bypass locks | `1` |
### 3.2 Command Execution
**Run all tests sequentially (Recommended):**
```bash
go test -tags "test,dev" -p 1 -count=1 ./...
```
**Run tests for a single package:**
```bash
go test -tags "test,dev" -v -count=1 ./handlers/payments/
```
**Run a single test in a package:**
```bash
go test -tags "test,dev" -v -run=TestAdminCreateGiftCard ./handlers/payments/
```
---
## 4. Correct Way to Add New Tests
When implementing new features or endpoints, follow this checklist to implement tests:
### Checklist for Adding New Tests:
- [ ] **File Location:** Put test files in the same directory as the handlers they test, named `*_test.go`.
- [ ] **Tags:** Ensure the top of the file has the `//go:build test` constraint.
- [ ] **Reset State:** Always call `resetTestData(t)` at the very beginning of your test:
```go
func TestMyNewHandler(t *testing.T) {
resetTestData(t) // Truncates tables and seeds default working hours
ctx := context.Background()
...
}
```
- [ ] **Close Rows & Transactions:** Never leak a connection! Always use `defer rows.Close()` and commit or rollback transactions (`defer tx.Rollback(ctx)`). Leaked transactions will cause subsequent `TRUNCATE` operations in other tests to freeze.
- [ ] **Use Mock Clients:** If your endpoint calls external services like Square, ensure the test environment uses the in-memory mock client (`square.NewDevClient()`).
---
## 5. FAQ & Common Breakage Causes (For Developers & AI Agents)
If the test suite is hanging, failing on startup, or throwing database errors, check this reference:
### ❓ FAQ 1: The test suite hangs/freezes completely on startup (0% CPU, no output)
**Reason:**
A test is trying to run `testdb.TruncateTables` or `testdb.Migrate` (which executes `TRUNCATE` or `DROP TABLE`), but another active transaction or query has a lock on one of those tables.
- **Cause A: Connection leak in code.** A previous handler or test executed a query (like `.Query()`) but did not call `rows.Close()`, or opened a transaction (`.Begin()`) but did not commit or rollback.
- **Cause B: The Go dev server is running.** If the Go dev server (`go run ./main.go`) is currently running and holding open database sessions/transactions on the same database, test-suite DDL will block indefinitely.
- **Fix:** Kill the running dev server, check for unclosed `rows` or unrolled-back transactions, and run `docker exec postgres psql -U myuser -d crussell_test -c "SELECT pid, query, state FROM pg_stat_activity WHERE state != 'idle';"` to locate blocked sessions.
### ❓ FAQ 2: Error `relation "X" does not exist` during seed or migration
**Reason:**
A foreign key constraint or table creation script has a dependency ordering issue in `init-scripts/init-script.sql`.
- **Cause:** Table `A` references Table `B`, but Table `A` is created *before* Table `B` in the script.
- **Fix:** Do **not** use inline constraints like `booking_id CHAR(12) REFERENCES bookings(id)` for tables created early. Instead, declare the column normally (`booking_id CHAR(12)`), and append the constraint at the very bottom of the file using `ALTER TABLE payments ADD CONSTRAINT fk_payments_booking FOREIGN KEY (booking_id) REFERENCES bookings(id);`.
### ❓ FAQ 3: Error `relation "X" already exists` during Migrate
**Reason:**
You created a new table in the database schema, but forgot to add it to the test teardown list.
- **Cause:** When `testdb.Migrate` runs, it drops existing tables using the `dropOrder` list. If your new table isn't in that list, it won't be dropped. When `init-script.sql` tries to run `CREATE TABLE X`, it fails because the old table is still there.
- **Fix:** Edit `backend/testutils/testdb/testdb.go`. Find the `dropOrder` array and the `tables` truncation array inside `TruncateTables`. Append your new table name to the **beginning** of both lists.
### ❓ FAQ 4: Error `type "X" already exists` during Migrate
**Reason:**
Similar to FAQ 3, you added a new custom enum type in PostgreSQL but forgot to tell the test runner to clean it up.
- **Cause:** The enum type survives across migrations, causing `CREATE TYPE X` to fail.
- **Fix:** Find `typeDrops` array in `backend/testutils/testdb/testdb.go`. Add `"DROP TYPE IF EXISTS X CASCADE"` to the array.
### ❓ FAQ 5: Deadlock detected during `TRUNCATE` (in `TruncateTables` or `resetTestData`)
**Reason:**
`TRUNCATE TABLE users CASCADE` needs `ACCESS EXCLUSIVE` locks on `users` and all child tables referencing it via FK. Another concurrent `TruncateTables` call holds one of those child locks. PostgreSQL detects the circular wait and aborts one of the transactions.
**Causes:**
- **Cause A (most common):** Two `go test` processes running against the same database simultaneously (e.g., running the test suite from two terminals at once, or a CI pipeline overlapping with a dev run).
- **Cause B:** `-p` flag > 1 causing multiple packages to run concurrently without advisory locks protecting them.
- **Cause C:** A previous `TruncateTables` call held the advisory lock on a different connection than the work (pre-June 2026 bug — see §2.2 for why `pool.Acquire` is mandatory).
**Diagnosis:**
```sql
-- Find blocked/pending sessions on the test database
SELECT pid, wait_event_type, wait_event, state, query
FROM pg_stat_activity
WHERE datname = 'crussell_test' AND state != 'idle'
ORDER BY wait_event_type NULLS LAST;
```
Look for sessions in `wait_event_type = 'Lock'` waiting on `relation` events.
**Fix:**
1. Kill other `go test` processes.
2. Kill stale connections holding locks: `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'crussell_test' AND pid != pg_backend_pid();`
3. Ensure `TruncateTables` uses `pool.Acquire()` with `pg_advisory_lock(1338)` (lock ID must be different from `Migrate`'s 1337).
4. Run with `-p 1` to serialize packages.
### ❓ FAQ 6: Panic: `interface conversion: interface {} is nil, not string` during auth
**Reason:**
A handler test is calling `VerifyToken` or middleware, but the authentication headers or context keys are not set correctly.
- **Cause:** The user token is expired, signed with a different key, or the context is missing `mw.UserIDKey`.
- **Fix:** Make sure `jwt.Init()` is called in `TestMain`, and your test requests include the `Authorization: Bearer <token>` header with a token generated via `jwt.GenerateToken()`.
---
## Appendix: HMRC & Legal Compliance Retention Rules Reference
When writing or editing tests for cleanup and data retention, reference these statutory timelines:
- **HMRC Corporation Tax records:** **6 years** from the end of the financial year (HMRC CH14600 / Companies Act 2006 s.388). We aggregate detailed records after **7 years** to maintain a safe buffer.
- **Scottish Contract Claims prescriptive period:** **5 years** (Prescription and Limitation (Scotland) Act 1973 s.6). Accounts with remaining balances must remain active for at least **5 years** to allow claims.
- **GDPR Storage Limitation:** **2 years** inactivity default for accounts with no balance (legitimate interest in relationship ends).