diff --git a/obsidian/Crussell/Gift Card Terms & Conditions.md b/obsidian/Crussell/Gift Card Terms & Conditions.md new file mode 100644 index 0000000..1fea629 --- /dev/null +++ b/obsidian/Crussell/Gift Card Terms & Conditions.md @@ -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) diff --git a/obsidian/Crussell/Privacy Policy.md b/obsidian/Crussell/Privacy Policy.md new file mode 100644 index 0000000..cd33ff8 --- /dev/null +++ b/obsidian/Crussell/Privacy Policy.md @@ -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. diff --git a/obsidian/Crussell/Terms & Conditions - Overall App.md b/obsidian/Crussell/Terms & Conditions - Overall App.md new file mode 100644 index 0000000..b47c1b1 --- /dev/null +++ b/obsidian/Crussell/Terms & Conditions - Overall App.md @@ -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). diff --git a/obsidian/Crussell/Testing Architecture & DB Management.md b/obsidian/Crussell/Testing Architecture & DB Management.md new file mode 100644 index 0000000..97b8b26 --- /dev/null +++ b/obsidian/Crussell/Testing Architecture & DB Management.md @@ -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 ` 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).