Remove TESTING.md and DEPLOYMENT.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lawrence Hook
2026-02-15 19:10:05 -05:00
parent 6ed3ad0291
commit c5734518c4
2 changed files with 0 additions and 878 deletions
-521
View File
@@ -1,521 +0,0 @@
# RYS Premium Server Deployment Guide
Complete guide to deploying the premium subscription server and testing the monetization flow.
**Server URL:** `https://server.lawrencehook.com/rys/`
---
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Stripe Setup](#stripe-setup)
3. [AWS SES Setup](#aws-ses-setup)
4. [Server Deployment](#server-deployment)
5. [Apache Configuration](#apache-configuration)
6. [Extension Setup](#extension-setup)
7. [Testing](#testing)
8. [Troubleshooting](#troubleshooting)
9. [Maintenance](#maintenance)
---
## Prerequisites
- AWS Lightsail instance (Bitnami stack)
- Node.js v20+
- Stripe account (test mode for development)
- AWS SES configured for sending emails
---
## Stripe Setup
### Create Products
1. Go to https://dashboard.stripe.com/test/products
2. Click **"+ Add product"**
**Monthly Plan:**
- Name: `RYS Premium Monthly`
- Pricing: `$3.00 USD` / `month` / `recurring`
- Save and copy the **Price ID** (starts with `price_...`)
**Yearly Plan:**
- Name: `RYS Premium Yearly`
- Pricing: `$24.00 USD` / `year` / `recurring`
- Save and copy the **Price ID**
### Get API Keys
1. Go to https://dashboard.stripe.com/test/apikeys
2. Copy the **Secret key** (starts with `sk_test_...`)
### Configure Billing Portal
1. Go to Settings → Billing → Customer portal
2. Enable the customer portal
3. Configure allowed actions:
- [x] Update payment methods
- [x] View invoice history
- [x] Cancel subscriptions
4. Save changes
### Create Webhook Endpoint
1. Go to https://dashboard.stripe.com/test/webhooks
2. Click **"+ Add endpoint"**
3. Endpoint URL: `https://server.lawrencehook.com/rys/webhook/stripe`
4. Select events:
- `checkout.session.completed`
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `invoice.payment_failed`
- `invoice.payment_succeeded`
5. Click **"Add endpoint"**
6. Copy the **Signing secret** (starts with `whsec_...`)
---
## AWS SES Setup
### Verify Sender Email/Domain
1. In AWS Console → SES → Verified identities
2. Click "Create identity"
3. For email: click the verification link sent to you
4. For domain: add the DNS records provided
### Request Production Access
SES starts in sandbox mode (can only send to verified emails).
1. Go to SES → Account dashboard
2. Click "Request production access"
3. Fill out the form explaining your use case
4. Wait for approval (usually 24-48 hours)
### IAM Credentials
If not using instance roles, create IAM credentials:
1. Create a new user (e.g., `rys-premium-ses`)
2. Attach policy: `AmazonSESFullAccess`
3. Create access keys
4. Copy the **Access Key ID** and **Secret Access Key**
---
## Server Deployment
### SSH to Lightsail Instance
```bash
ssh bitnami@your-instance-ip
```
### Install Node.js 20+ (if needed)
```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node --version # Should show v20.x.x
```
### Install PM2
```bash
sudo npm install -g pm2
```
### Clone and Setup Repository
```bash
cd ~/github
git clone https://github.com/lawrencehook/remove-youtube-suggestions.git
cd remove-youtube-suggestions
git checkout feature/monetize
cd server
npm install
```
### Create Environment File
```bash
nano .env
```
Paste the following (fill in your values):
```bash
# Server
PORT=3005
BASE_URL=https://server.lawrencehook.com/rys
# JWT (generate with: openssl rand -base64 32)
JWT_SECRET=your-random-secret-at-least-32-characters-long
# Stripe
STRIPE_SECRET_KEY=sk_test_YOUR_KEY
STRIPE_PRICE_MONTHLY=price_YOUR_MONTHLY_ID
STRIPE_PRICE_YEARLY=price_YOUR_YEARLY_ID
STRIPE_WEBHOOK_SECRET=whsec_YOUR_WEBHOOK_SECRET
# AWS SES (uses instance role, no explicit credentials needed)
AWS_REGION=us-east-1
EMAIL_FROM=noreply@lawrencehook.com
```
Save: `Ctrl+O`, `Enter`, `Ctrl+X`
### Create Data Directory
```bash
mkdir -p data
echo '[]' > data/grandfathered.json
```
To add grandfathered users (past donors with lifetime access):
```bash
echo '["donor1@example.com", "donor2@example.com"]' > data/grandfathered.json
```
### Test Server Starts
```bash
npm start
```
Expected output:
```
Loaded X grandfathered emails
RYS Premium Server running on port 3005
Base URL: https://server.lawrencehook.com/rys
```
Press `Ctrl+C` to stop.
### Start with PM2
```bash
pm2 start src/index.js --name rys-premium
pm2 save
```
### Configure PM2 Auto-Start on Reboot
```bash
pm2 startup
# Run the command it outputs (starts with: sudo env PATH=...)
pm2 save
```
---
## Apache Configuration
Your Lightsail instance uses Bitnami's Apache with path-based proxying.
### Edit SSL Config
Edit your local deployments repo:
```bash
nano ~/github/deployments/opt/bitnami/apache/conf/bitnami/bitnami-ssl.conf
```
Add these lines inside the `<VirtualHost _default_:443>` block:
```apache
ProxyPass /rys/ http://localhost:3005/
ProxyPassReverse /rys/ http://localhost:3005/
```
### Deploy Apache Config
```bash
# Local machine
cd ~/github/deployments
git add -A && git commit -m "Add RYS Premium server proxy config" && git push
# On server
cd ~/github/deployments && git pull
./update_apache_conf.sh
```
---
## Extension Setup
### Verify Config Points to Production
Check `src/shared/config.js`:
```javascript
const PREMIUM_CONFIG = {
SERVER_URL: 'https://server.lawrencehook.com/rys',
// ...
};
```
### Build the Extension
**For Chrome:**
```bash
./make_chrome.sh
```
**For Firefox:**
```bash
./make_firefox.sh
```
### Load in Chrome
1. Navigate to `chrome://extensions/`
2. Enable **"Developer mode"** (toggle in top-right)
3. Click **"Load unpacked"**
4. Select: `chrome_extension/`
### Load in Firefox
1. Navigate to `about:debugging#/runtime/this-firefox`
2. Click **"Load Temporary Add-on..."**
3. Select: `src/firefox_manifest.json`
---
## Testing
### Automated Tests
```bash
cd server
npm test
```
All tests should pass (currently 46).
### Health Check
```bash
curl https://server.lawrencehook.com/rys/health
# Expected: {"status":"ok","timestamp":"..."}
```
### Magic Link Flow
**Via curl:**
```bash
curl -X POST https://server.lawrencehook.com/rys/auth/send-magic-link \
-H "Content-Type: application/json" \
-d '{"email":"your-email@example.com"}'
# Expected: {"request_id":"abc123-..."}
```
**Via extension:**
1. Go to any YouTube page
2. Click the RYS extension icon
3. Click the gear icon (settings menu)
4. Click **"Sign In"**
5. Enter your email and click **"Send Sign-In Link"**
6. Check your email and click the magic link
7. Return to extension — should show "Signed in successfully"
### License Check
After signing in:
1. Click gear icon → **"Account"**
2. Should show your email and status: **"Free"**
**For grandfathered users:**
- Add email to `data/grandfathered.json` on server
- Restart: `pm2 restart rys-premium`
- Sign out and sign back in
- Status should show: **"Premium (Lifetime)"**
### Checkout Flow
1. Open Account modal
2. Click **"Upgrade to Premium"**
3. Select a plan (Monthly or Yearly)
4. Click **"Continue to Checkout"**
5. Complete payment with test card:
- Card: `4242 4242 4242 4242`
- Expiry: Any future date (e.g., `12/34`)
- CVC: Any 3 digits (e.g., `123`)
- ZIP: Any valid ZIP (e.g., `12345`)
6. After success page, return to extension
7. Open Account — status should show **"Premium Active"**
### Billing Portal
1. With an active subscription, open Account modal
2. Click **"Manage Subscription"**
3. Stripe billing portal opens in new tab
4. Can cancel subscription here for testing
### Webhook Verification
1. Go to Stripe Dashboard → Webhooks → your endpoint
2. Click **"Send test webhook"**
3. Select `checkout.session.completed`
4. Check server logs: `pm2 logs rys-premium`
### Extension Smoke Tests
- **Sign-in flow:** Send magic link → click link → see "Signed in successfully"
- **Poll cancel:** Start sign-in → hit Cancel → spinner stops, no sign-in after cancel
- **Premium gate:** Mark option as premium → non-premium click opens upgrade modal
- **License refresh:** Open Account → premium label reflects correct status
- **Upgrade flow:** Choose plan → click checkout → Stripe checkout opens
- **Billing portal:** With subscription → click "Manage Subscription" → portal opens
### Pre-Launch Checklist
- [ ] All automated tests pass
- [ ] Server starts without errors
- [ ] `/health` endpoint returns OK
- [ ] Magic link emails are received
- [ ] Magic link verification works
- [ ] Grandfathered users show as premium
- [ ] Stripe checkout creates a session
- [ ] Stripe webhook is configured and verified
- [ ] Billing portal is accessible
- [ ] SSL certificate is valid
- [ ] CORS works from extension origins
### Test Cards Reference
| Card | Result |
|------|--------|
| `4242 4242 4242 4242` | Success |
| `4000 0000 0000 0002` | Declined |
| `4000 0025 0000 3155` | Requires authentication |
---
## Troubleshooting
### Server won't start
```bash
pm2 logs rys-premium --lines 50
```
Common issues:
- Missing .env variables
- JWT_SECRET too short (needs 32+ chars)
- Port already in use
### Magic link emails not sending
- Check SES is configured
- Verify EMAIL_FROM domain is verified in SES
- If in SES sandbox, recipient must also be verified
### CORS errors in extension
```bash
# Verify server is running
curl http://localhost:3005/health
```
### SSL certificate issues
```bash
sudo /opt/bitnami/bncert-tool # regenerate if needed
```
### Extension can't connect
```bash
# Test endpoint
curl https://server.lawrencehook.com/rys/health
# Check Apache config
sudo apachectl configtest
```
---
## Maintenance
### Update Server Code
```bash
cd ~/github/remove-youtube-suggestions
git pull
git checkout feature/monetize
cd server
npm install
pm2 restart rys-premium
```
### View Logs
```bash
pm2 logs rys-premium # Tail logs
pm2 logs rys-premium --lines 100 # Last 100 lines
```
### Server Commands
```bash
pm2 restart rys-premium # Restart
pm2 stop rys-premium # Stop
pm2 status # Check status
```
### Add to Deployment Script
Add to `~/github/deployments/run`:
```bash
# RYS Premium Server (pm2)
cd /home/bitnami/github/remove-youtube-suggestions && git checkout -- . && git pull && git checkout feature/monetize;
cd server && npm install && npm audit fix;
pm2 restart rys-premium || pm2 start src/index.js --name rys-premium;
```
### Updating Grandfathered List
1. Edit `data/grandfathered.json`
2. Restart: `pm2 restart rys-premium`
---
## Environment Variables Reference
| Variable | Description | Example |
|----------|-------------|---------|
| `PORT` | Server port | `3005` |
| `BASE_URL` | Public URL | `https://server.lawrencehook.com/rys` |
| `JWT_SECRET` | 32+ char secret | (generate with `openssl rand -base64 32`) |
| `STRIPE_SECRET_KEY` | Stripe API secret | `sk_test_...` |
| `STRIPE_PRICE_MONTHLY` | Monthly plan price ID | `price_...` |
| `STRIPE_PRICE_YEARLY` | Yearly plan price ID | `price_...` |
| `STRIPE_WEBHOOK_SECRET` | Webhook signing secret | `whsec_...` |
| `AWS_REGION` | AWS region for SES | `us-east-1` |
| `EMAIL_FROM` | Sender email address | `noreply@lawrencehook.com` |
---
## API Endpoints Reference
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| `/health` | GET | No | Health check |
| `/auth/send-magic-link` | POST | No | Send magic link email |
| `/auth/verify` | GET | No | Magic link target (HTML) |
| `/auth/poll` | GET | No | Poll for auth status |
| `/license/check` | GET | Yes | Check premium status |
| `/checkout/create` | POST | Yes | Create Stripe checkout |
| `/checkout/success` | GET | No | Success page (HTML) |
| `/checkout/cancel` | GET | No | Cancel page (HTML) |
| `/billing/portal` | POST | Yes | Create billing portal session |
| `/billing/return` | GET | No | Return page (HTML) |
| `/webhook/stripe` | POST | No* | Stripe webhook |
*Authenticated via Stripe signature
-357
View File
@@ -1,357 +0,0 @@
# Manual Testing Guide — Premium Subscription
This document covers all manual testing paths for the premium monetization feature.
## Prerequisites
- Extension loaded unpacked in Chrome (or Firefox)
- Server running locally or deployed to `server.lawrencehook.com`
- Stripe test mode configured with test price IDs
- AWS SES configured (or use a local email trap)
- At least one email in `data/grandfathered.json` for grandfathered testing
- A separate non-grandfathered email for regular user testing
- Stripe CLI installed for webhook testing (`stripe listen --forward-to localhost:3000/rys/webhook/stripe`)
---
## 1. Initial State (Not Signed In)
### 1.1 Options page loads correctly
- [ ] Open extension popup — settings menu shows "Sign In" (not "Account")
- [ ] "Donate" link visible in header, links to PayPal
### 1.2 Free features work without sign-in
- [ ] Toggle a non-premium feature (e.g., "Hide homepage recommendations")
- [ ] Verify it takes effect on YouTube
### 1.3 Premium feature click — not signed in
- [ ] Click any premium-gated toggle (marked with a lock/premium indicator)
- [ ] Verify the "Premium Required" modal appears
- [ ] Click "Cancel" — modal closes, setting unchanged
- [ ] Click the premium toggle again, then click "Sign In" in the modal
- [ ] Verify a new tab opens to `main.html?signin=1` and the popup closes
### 1.4 Schedule and Password (premium settings menu items)
- [ ] Click "Schedule" in the settings menu — Premium Required modal appears
- [ ] Click "Password" in the settings menu — Premium Required modal appears
---
## 2. Sign-In Flow
### 2.1 Happy path — sign in from new tab
- [ ] From the options tab (`main.html?signin=1`), sign-in modal opens automatically
- [ ] Enter a valid email, press Enter (or click send button)
- [ ] Modal transitions to waiting state with countdown timer
- [ ] Check email — magic link email received from the configured sender
- [ ] Click magic link — browser opens server success page ("You're signed in!")
- [ ] Return to extension tab — modal closes, status shows "Signed in successfully"
- [ ] Header now shows "Account" instead of "Sign In"
### 2.2 Sign in from popup
- [ ] Open extension popup, click "Sign In"
- [ ] Verify popup closes and a new tab opens to `main.html?signin=1`
- [ ] Complete the sign-in flow in the new tab
### 2.3 Invalid email
- [ ] Enter an invalid email (no @ symbol)
- [ ] Verify "Please enter a valid email" status message appears
- [ ] No network request is made
### 2.4 Rate limiting
- [ ] Send 5 magic link requests for the same email in rapid succession
- [ ] On the 6th attempt, verify a rate limit error message appears
- [ ] Wait for the rate limit window to pass (or clear rate limit data on server)
### 2.5 Cancel during polling
- [ ] Start sign-in flow, reach waiting state
- [ ] Click "Cancel" button
- [ ] Verify modal closes and polling stops (no continued network requests)
- [ ] Verify user is still not signed in
### 2.6 Poll timeout
- [ ] Start sign-in flow but do NOT click the magic link
- [ ] Wait for the 16-minute countdown to reach 0
- [ ] Verify error state appears with "Verification timed out" message
- [ ] Click "Retry" — email form reappears
### 2.7 Expired magic link
- [ ] Start sign-in flow, wait >15 minutes, then click the magic link
- [ ] Verify the server shows an error page (link expired)
- [ ] Extension should show error state when poll detects 404
### 2.8 Multiple sign-in attempts
- [ ] Start sign-in, reach waiting state
- [ ] Close modal, reopen, start a new sign-in with a different email
- [ ] Verify the previous polling is aborted (no interference)
---
## 3. Signed-In, Free User
### 3.1 Account modal
- [ ] Click "Account" in settings menu
- [ ] Verify Account modal opens showing:
- Your email address
- "Free Plan" status
- "Upgrade" button visible
- "Billing" button hidden
- "Sign Out" button visible
### 3.2 Premium feature click — signed in, not premium
- [ ] Click a premium feature toggle
- [ ] Verify the Upgrade modal appears (NOT the Premium Required modal)
### 3.3 Upgrade modal
- [ ] Verify Upgrade modal shows plan selection (Monthly / Yearly)
- [ ] Verify Yearly is selected by default
- [ ] Click Monthly — verify it becomes selected
- [ ] Click Yearly — verify it becomes selected
- [ ] Click "Cancel" — modal closes
### 3.4 License check — no subscription
- [ ] Open DevTools, check console for `[license] email -> free` on server
- [ ] Verify `is_premium` attribute on `<html>` is `"false"`
---
## 4. Checkout / Purchase Flow
### 4.1 Monthly subscription
- [ ] Click a premium feature → Upgrade modal
- [ ] Select "Monthly", click "Subscribe"
- [ ] Verify Stripe Checkout opens in a new tab
- [ ] Complete payment with Stripe test card (`4242 4242 4242 4242`)
- [ ] Verify Stripe success page appears
- [ ] Return to extension tab (click on it or close Stripe tab)
- [ ] Verify extension auto-refreshes license (visibility/focus event triggers refresh)
- [ ] Verify the premium feature you clicked is now enabled
- [ ] Verify "Donate" link changed to "Premium" in header
### 4.2 Yearly subscription
- [ ] Sign out, sign in with a different email
- [ ] Repeat checkout flow selecting "Yearly"
- [ ] Verify Stripe Checkout shows the yearly price
- [ ] Complete payment and verify premium activates
### 4.3 Cancel checkout
- [ ] Open Upgrade modal, click "Subscribe"
- [ ] On Stripe Checkout page, click back or close the tab
- [ ] Return to extension — verify user is still on Free Plan
- [ ] Verify no error messages appear
### 4.4 Checkout with expired session
- [ ] Clear `session_token` from `chrome.storage.local` manually (DevTools → Application → Local Storage)
- [ ] Try to subscribe — verify error about session expired
- [ ] Verify user is signed out and prompted to sign in again
---
## 5. Premium User Experience
### 5.1 All premium features accessible
- [ ] Toggle several premium features on — verify they take effect on YouTube
- [ ] Toggle them off — verify they revert
### 5.2 Account modal — premium subscriber
- [ ] Open Account modal
- [ ] Verify it shows:
- Your email
- "Premium Active" status
- "Billing" button visible
- "Upgrade" button hidden
- "Sign Out" button
### 5.3 Billing portal
- [ ] Click "Billing" button in Account modal
- [ ] Verify Stripe billing portal opens in new tab
- [ ] Verify you can see subscription details, update payment method, cancel
- [ ] Close billing portal tab, return to extension
### 5.4 Schedule (premium feature)
- [ ] Click "Schedule" in settings menu
- [ ] Verify Schedule modal opens (not blocked by premium gate)
- [ ] Configure a schedule and verify it works
### 5.5 Password (premium feature)
- [ ] Click "Password" in settings menu
- [ ] Verify Password modal opens (not blocked by premium gate)
- [ ] Set a password, close settings, reopen — verify password prompt appears
- [ ] Enter correct password — verify unlock works
- [ ] Disable password — verify it's removed
---
## 6. Grandfathered User
### 6.1 Sign in with grandfathered email
- [ ] Sign in with an email that's in `data/grandfathered.json`
- [ ] Verify license check returns `grandfathered: true`
### 6.2 Account modal — grandfathered
- [ ] Open Account modal
- [ ] Verify it shows:
- Your email
- "Lifetime Premium" status
- "Billing" button hidden (no subscription to manage)
- "Upgrade" button hidden
- "Sign Out" button
### 6.3 Premium features work
- [ ] Verify all premium features are accessible
- [ ] Verify "Donate" link shows "Premium"
### 6.4 Case-insensitive matching
- [ ] Add `test@example.com` to `grandfathered.json`
- [ ] Sign in with `Test@Example.com` (different case)
- [ ] Verify user is recognized as grandfathered
---
## 7. Sign Out
### 7.1 Sign out clears state
- [ ] While signed in (premium or free), click "Sign Out" in Account modal
- [ ] Verify header changes back to "Sign In"
- [ ] Verify "Donate" link reappears (if was showing "Premium")
- [ ] Verify premium features are re-gated
### 7.2 Storage is cleared
- [ ] After sign-out, check `chrome.storage.local` in DevTools
- [ ] Verify `session_token`, `license_token`, and `user_email` are removed
- [ ] Verify YouTube settings (non-premium) are NOT affected
---
## 8. Session Expiry
### 8.1 Expired session token
- [ ] Sign in, then manually modify the `session_token` in storage to an expired JWT
- [ ] Open Account modal (or trigger a license check)
- [ ] Verify "Session expired. Please sign in again." message appears
- [ ] Verify user is automatically signed out
- [ ] Verify sign-in modal opens
### 8.2 Invalid session token
- [ ] Replace `session_token` with garbage string in storage
- [ ] Trigger a license check
- [ ] Verify 401 from server causes automatic sign-out
---
## 9. License Token Caching & Refresh
### 9.1 Cached token is used
- [ ] Sign in as premium user, verify license check succeeds
- [ ] Disconnect from network (or stop the server)
- [ ] Close and reopen extension popup
- [ ] Verify premium features are still accessible (cached license token is valid)
### 9.2 Expired cached token without network
- [ ] Manually set `license_token` to an expired JWT in storage
- [ ] With server unreachable, open extension
- [ ] Verify user shows as non-premium (expired cache, can't refresh)
### 9.3 Token refresh near expiry
- [ ] Set a license token that expires within 24 hours (modify JWT payload)
- [ ] Open extension with server running
- [ ] Verify a fresh license check is triggered (not using cache)
### 9.4 Force refresh after checkout
- [ ] Complete a checkout flow
- [ ] Verify the extension calls `checkLicense(true)` (force refresh)
- [ ] Monitor network tab — a `/license/check` request should fire immediately on tab focus
---
## 10. Subscription Lifecycle
### 10.1 Cancel subscription via billing portal
- [ ] As a premium user, open Billing portal
- [ ] Cancel the subscription in Stripe
- [ ] Return to extension, close and reopen popup
- [ ] Wait for license token to expire (or force refresh)
- [ ] Verify user shows as "Free Plan" after refresh
- [ ] Verify premium features are re-gated
### 10.2 Resubscribe after cancellation
- [ ] After cancellation, click a premium feature
- [ ] Go through checkout flow again
- [ ] Verify premium re-activates
---
## 11. Stripe Webhooks
### 11.1 Webhook receives events
- [ ] With Stripe CLI forwarding, complete a checkout
- [ ] Verify `checkout.session.completed` event is logged on server
- [ ] Cancel subscription — verify `customer.subscription.deleted` is logged
### 11.2 Invalid webhook signature
- [ ] Send a POST to `/webhook/stripe` with invalid/missing `Stripe-Signature` header
- [ ] Verify 400 response
---
## 12. Cross-Browser
### 12.1 Chrome
- [ ] Run through sign-in, checkout, and premium feature flows on Chrome
### 12.2 Firefox
- [ ] Load extension in Firefox
- [ ] Verify sign-in flow works (popup-to-tab handoff, polling)
- [ ] Verify premium features gate correctly
- [ ] Verify `browser.storage.local` API works as expected
---
## 13. Edge Cases
### 13.1 Multiple tabs
- [ ] Open extension settings in two tabs
- [ ] Sign in on one tab
- [ ] Refresh the second tab — verify it reflects signed-in state
### 13.2 Rapid feature toggle clicks
- [ ] Click a premium feature rapidly multiple times
- [ ] Verify only one modal opens, no duplicate modals or errors
### 13.3 Network failure during sign-in
- [ ] Start sign-in, disconnect network after magic link is sent
- [ ] Verify polling handles network errors gracefully (retries silently)
- [ ] Reconnect network — verify polling resumes and succeeds when link is clicked
### 13.4 Server down during license check
- [ ] Sign in successfully, then stop the server
- [ ] Trigger a license check (reopen popup)
- [ ] Verify cached license token is used if still valid
- [ ] Verify no error shown to user
### 13.5 Extension update while signed in
- [ ] Sign in and get premium status
- [ ] Reload the extension (simulate update)
- [ ] Verify session and license tokens persist across reload
- [ ] Verify premium status is maintained
---
## 14. Analytics Verification
Use Mixpanel (or check console logs) to verify events fire correctly:
- [ ] `Sign In Started` — when email is submitted
- [ ] `Magic Link Sent` — after server responds
- [ ] `Sign In Success` — after verification completes
- [ ] `Sign In Canceled` — when user cancels polling
- [ ] `Sign In Error` — on timeout or failure
- [ ] `Premium Feature Click` — with correct `signedIn` flag
- [ ] `Upgrade Modal Opened` — when upgrade modal shows
- [ ] `Checkout Started` — with correct `plan` value
- [ ] `Checkout Completed` — after returning from successful checkout
- [ ] `Account Modal Opened` — with `isPremium` and `source`
- [ ] `Billing Portal Opened` — when billing portal opens
- [ ] `Session Expired` — when 401 triggers sign-out
- [ ] `Sign Out` — when user signs out
- [ ] `License Check` — with `cached`, `offline`, `isPremium` properties