An easy-to-host PDS on the ATProtocol, MacOS. Grandma-approved.

docs: add test plan for MM-144 mobile onboarding flow

authored by malpercio.dev and committed by

Tangled 8c687de9 b1f19247

+218
+218
docs/test-plans/2026-03-15-MM-144.md
··· 1 + # Human Test Plan: MM-144 Mobile Onboarding Flow 2 + 3 + **Generated from:** `docs/implementation-plans/2026-03-15-MM-144/` 4 + **Branch:** `malpercio/mm-144-mobile-onboarding-flow-ui-welcome-email-claim-code-entry` 5 + 6 + --- 7 + 8 + ## Prerequisites 9 + 10 + - macOS with Xcode installed (latest stable) and iOS Simulator platform 11 + - Nix dev shell activated from workspace root: `nix develop --impure --accept-flake-config` 12 + - Frontend dependencies installed: `cd apps/identity-wallet && pnpm install` 13 + - Xcode project generated: `cargo tauri ios init` (and PATH patched per `apps/identity-wallet/CLAUDE.md`) 14 + - All automated tests passing: `cargo test` from workspace root exits 0 15 + - `pnpm build` in `apps/identity-wallet/` exits 0 16 + 17 + --- 18 + 19 + ## Phase 1: Welcome Screen (AC1.1) 20 + 21 + | Step | Action | Expected | 22 + |------|--------|----------| 23 + | 1.1 | From `apps/identity-wallet/`, run `cargo tauri ios dev`. Wait for iOS Simulator to launch and app to load. | App opens. Welcome screen is displayed. | 24 + | 1.2 | Observe the Welcome screen content. | `h1` reads "Identity Wallet". Tagline reads "Your self-sovereign identity, in your pocket." (gray, centered). | 25 + | 1.3 | Observe the button at the bottom. | Blue "Get Started" button, full-width (max 320px), rounded corners. | 26 + | 1.4 | Tap "Get Started". | App navigates to Claim Code screen (heading: "Enter Your Claim Code"). | 27 + 28 + --- 29 + 30 + ## Phase 2: Claim Code Screen Validation (AC1.2, AC1.6) 31 + 32 + | Step | Action | Expected | 33 + |------|--------|----------| 34 + | 2.1 | Observe the Claim Code screen. | Heading: "Enter Your Claim Code". Hint: "You'll receive a 6-character code from your administrator." Monospace input with placeholder "ABC123". | 35 + | 2.2 | Observe "Next" button without typing. | Button is gray (#9ca3af), disabled. Tapping does nothing. | 36 + | 2.3 | Type "abc" (3 chars). | Input displays "ABC" (auto-uppercased). "Next" remains disabled. | 37 + | 2.4 | Type "12#\$34" (adding non-alphanumeric). | Non-alphanumeric characters stripped. Input shows "ABC123" (6 chars). "Next" becomes enabled (blue #007aff). | 38 + | 2.5 | Delete one character. | Input shows "ABC12" (5 chars). "Next" reverts to disabled. | 39 + | 2.6 | Restore to "ABC123". Tap "Next". | App navigates to Email screen. | 40 + 41 + --- 42 + 43 + ## Phase 3: Email Screen Validation (AC1.3, AC1.6) 44 + 45 + | Step | Action | Expected | 46 + |------|--------|----------| 47 + | 3.1 | Observe the Email screen. | Heading: "Enter Your Email". Email input with placeholder "you@example.com". | 48 + | 3.2 | Observe "Next" without typing. | Button disabled. | 49 + | 3.3 | Type "notanemail". | Button remains disabled (regex `/^[^\s@]+@[^\s@]+\.[^\s@]+$/` fails). | 50 + | 3.4 | Clear and type "user@example.com". | Button becomes enabled. | 51 + | 3.5 | Clear and type "user@". | Button remains disabled. | 52 + | 3.6 | Enter "alice@test.com". Tap "Next". | App navigates to Handle screen. | 53 + 54 + --- 55 + 56 + ## Phase 4: Handle Screen Validation (AC1.4, AC1.6) 57 + 58 + | Step | Action | Expected | 59 + |------|--------|----------| 60 + | 4.1 | Observe the Handle screen. | Heading: "Choose Your Handle". Hint: "This is your unique identifier on the network (e.g. alice.ezpds.com)." Button reads "Create Account" (not "Next"). | 61 + | 4.2 | Observe "Create Account" without typing. | Button disabled. | 62 + | 4.3 | Type "myhandle". | Button becomes enabled. | 63 + | 4.4 | Clear the field. | Button reverts to disabled. | 64 + | 4.5 | Type a single space. | Button remains disabled (`value.trim().length > 0`). | 65 + | 4.6 | Type "alice" and leave it. | Button enabled. | 66 + 67 + --- 68 + 69 + ## Phase 5: Loading Screen (AC1.5) 70 + 71 + **Prerequisite:** Start relay (`cargo run -p relay`). Seed a valid claim code. 72 + 73 + | Step | Action | Expected | 74 + |------|--------|----------| 75 + | 5.1 | Complete the onboarding flow (claim code, email, handle). Tap "Create Account". | App transitions to Loading screen. | 76 + | 5.2 | Observe the Loading screen (use Network Link Conditioner in Simulator for extra latency if needed). | Circular spinner animates (48×48px, blue arc rotating). Text below: "Creating your account…" | 77 + | 5.3 | Wait for loading to complete. | App transitions to either the "Account Created!" screen or rewinds with an error. | 78 + 79 + --- 80 + 81 + ## Phase 6: Successful End-to-End Account Creation (AC2.1, AC2.3, AC2.5) 82 + 83 + **Prerequisite:** Relay running on `localhost:8080`. Seed a fresh, unredeemed claim code (e.g. "TEST01"). 84 + 85 + | Step | Action | Expected | 86 + |------|--------|----------| 87 + | 6.1 | Launch app. Tap "Get Started". Enter "TEST01". Tap "Next". | Claim Code screen advances to Email. | 88 + | 6.2 | Enter "e2e@example.com". Tap "Next". | Advances to Handle screen. | 89 + | 6.3 | Enter "e2euser". Tap "Create Account". | Loading screen appears, then "Account Created!" placeholder: heading "Account Created!" and text "DID ceremony coming soon…" | 90 + | 6.4 | Check relay logs. | `POST /v1/accounts/mobile` logged. Body contained `email`, `handle`, `claimCode: "TEST01"`, `platform: "ios"`, `devicePublicKey`. Response was 201. | 91 + 92 + --- 93 + 94 + ## Phase 7: Keychain Verification (AC4.1, AC4.2, AC4.3) 95 + 96 + **Prerequisite:** Phase 6 completed successfully. 97 + 98 + | Step | Action | Expected | 99 + |------|--------|----------| 100 + | 7.1 | Query Keychain for service `"ezpds-identity-wallet"`, account `"device-private-key"`. | 32 bytes of data returned (P-256 private key scalar). | 101 + | 7.2 | Query Keychain for service `"ezpds-identity-wallet"`, account `"device-token"`. | Token string returned matching relay response. | 102 + | 7.3 | Query Keychain for service `"ezpds-identity-wallet"`, account `"session-token"`. | Token string returned matching relay response. | 103 + 104 + --- 105 + 106 + ## Phase 8: Expired/Invalid Claim Code Error (AC3.1) 107 + 108 + **Prerequisite:** Relay running. No claim code "BADCOD" seeded (or expired one). 109 + 110 + | Step | Action | Expected | 111 + |------|--------|----------| 112 + | 8.1 | Complete onboarding with code "BADCOD", email "err@example.com", handle "erruser". Tap "Create Account". | Loading screen appears. | 113 + | 8.2 | Wait for loading to resolve. | App rewinds to **Claim Code screen**. Red error: "This claim code has expired. Please request a new one." Input border turns red (#ef4444). | 114 + | 8.3 | Verify form state. | Input still shows "BADCOD". Email and handle form values are preserved (will reappear on those screens). | 115 + 116 + --- 117 + 118 + ## Phase 9: Redeemed Claim Code Error (AC3.2) 119 + 120 + **Prerequisite:** Relay running. Claim code "USED01" previously redeemed. 121 + 122 + | Step | Action | Expected | 123 + |------|--------|----------| 124 + | 9.1 | Complete onboarding with "USED01", different email/handle. Tap "Create Account". | Loading screen. | 125 + | 9.2 | Wait for resolve. | App rewinds to **Claim Code screen**. Red error: "This claim code has already been used." | 126 + 127 + --- 128 + 129 + ## Phase 10: Email Taken Error (AC3.3) 130 + 131 + **Prerequisite:** Account exists with email "alice@example.com". Fresh claim code "CODE02" available. 132 + 133 + | Step | Action | Expected | 134 + |------|--------|----------| 135 + | 10.1 | Complete onboarding with "CODE02", email "alice@example.com", handle "bob". Tap "Create Account". | Loading screen. | 136 + | 10.2 | Wait for resolve. | App rewinds to **Email screen** (not Claim Code). Red error: "An account with that email already exists." | 137 + 138 + --- 139 + 140 + ## Phase 11: Handle Taken Error (AC3.4) 141 + 142 + **Prerequisite:** Account with handle "alice.ezpds.com" exists. Fresh claim code "CODE03" available. 143 + 144 + | Step | Action | Expected | 145 + |------|--------|----------| 146 + | 11.1 | Complete onboarding with "CODE03", email "unique@example.com", handle "alice.ezpds.com". Tap "Create Account". | Loading screen. | 147 + | 11.2 | Wait for resolve. | App rewinds to **Handle screen**. Red error: "That handle is taken. Please choose another." | 148 + 149 + --- 150 + 151 + ## Phase 12: Network Error (AC3.5) 152 + 153 + **Prerequisite:** No relay running on `localhost:8080`. 154 + 155 + | Step | Action | Expected | 156 + |------|--------|----------| 157 + | 12.1 | Complete onboarding with any values. Tap "Create Account". | Loading screen. | 158 + | 12.2 | Wait for resolve. | App rewinds to **Handle screen**. Red error: "Couldn't reach the server. Check your connection." | 159 + 160 + --- 161 + 162 + ## End-to-End: Full Happy Path 163 + 164 + Complete the full user journey in one pass: 165 + 166 + 1. Start relay: `cargo run -p relay` from workspace root. 167 + 2. Seed a fresh claim code via Bruno collection (`POST /v1/admin/claim-codes`). Note the code. 168 + 3. Launch app: `cargo tauri ios dev`. 169 + 4. Welcome screen → tap "Get Started". 170 + 5. Claim Code → enter seeded code, verify button enables at 6 chars → tap "Next". 171 + 6. Email → enter "fulltest@example.com", verify button enables on valid format → tap "Next". 172 + 7. Handle → enter "fulltestuser", verify button enables → tap "Create Account". 173 + 8. Loading → verify spinner + "Creating your account…" text. 174 + 9. Success → verify "Account Created!" / "DID ceremony coming soon…". 175 + 10. Relay logs → confirm 201 response, correct 5-field request body. 176 + 11. Keychain → verify `device-private-key` (32 bytes), `device-token`, `session-token` under service `"ezpds-identity-wallet"`. 177 + 178 + --- 179 + 180 + ## End-to-End: Error Recovery Flow 181 + 182 + Validate that the user can recover after an error without restarting the app: 183 + 184 + 1. Start relay with no claim codes seeded. 185 + 2. Complete onboarding with code "BADBAD", email "recover@example.com", handle "recoveruser". 186 + 3. Verify app rewinds to Claim Code screen with the expired error message. 187 + 4. Seed a valid claim code "GOOD01" in the relay. 188 + 5. Clear claim code input, enter "GOOD01". Tap "Next". 189 + 6. Email screen should still show "recover@example.com" (form state preserved). Tap "Next". 190 + 7. Handle screen should still show "recoveruser". Tap "Create Account". 191 + 8. Verify Loading screen appears and flow completes with "Account Created!". 192 + 193 + --- 194 + 195 + ## Traceability 196 + 197 + | Acceptance Criterion | Automated Test | Manual Steps | 198 + |----------------------|----------------|--------------| 199 + | AC1.1: Welcome screen | — | Phase 1 | 200 + | AC1.2: Claim code 6-char validation | — | Phase 2 | 201 + | AC1.3: Email regex validation | — | Phase 3 | 202 + | AC1.4: Handle non-empty validation | — | Phase 4 | 203 + | AC1.5: Loading spinner + status text | — | Phase 5 | 204 + | AC1.6: Button-gated navigation | — | Phases 2–4 | 205 + | AC2.1: Tauri IPC invocation | — | Phase 6 | 206 + | AC2.2: Request camelCase serialization | `create_mobile_account_request_serializes_camel_case` | — | 207 + | AC2.3: Keychain token storage | — | Phase 7 | 208 + | AC2.5: Result camelCase serialization | `create_account_result_serializes_camel_case` | — | 209 + | AC3.1: Expired code (Rust + frontend) | `error_expired_code_serializes_correctly` | Phase 8 | 210 + | AC3.2: Redeemed code (Rust + frontend) | `error_redeemed_code_serializes_correctly` | Phase 9 | 211 + | AC3.3: Email taken (Rust + frontend) | `error_email_taken_serializes_correctly` | Phase 10 | 212 + | AC3.4: Handle taken (Rust + frontend) | `error_handle_taken_serializes_correctly` | Phase 11 | 213 + | AC3.5: Network error (Rust + frontend) | `error_network_error_serializes_correctly` | Phase 12 | 214 + | AC4.1: device-private-key in Keychain | — | Phase 7, Step 7.1 | 215 + | AC4.2: device-token in Keychain | — | Phase 7, Step 7.2 | 216 + | AC4.3: session-token in Keychain | — | Phase 7, Step 7.3 | 217 + | AC5.1: `cargo build --workspace` | CI build command | — | 218 + | AC5.2: `pnpm build` | CI build command | — |