Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fcb9fd5c7 | ||
| 8bd4e081ea | |||
|
|
6e13570deb | ||
| 2eba66fb71 |
154
CLAUDE.md
154
CLAUDE.md
@@ -424,11 +424,24 @@ psql -d "flyer-crawler-test" -c "\dn+ public"
|
||||
|
||||
The dev container runs its own **local Bugsink instance** - it does NOT connect to the production Bugsink server:
|
||||
|
||||
- **Local Bugsink**: Runs at `http://localhost:8000` inside the container
|
||||
- **Pre-configured DSNs**: Set in `compose.dev.yml`, pointing to local instance
|
||||
- **Local Bugsink UI**: Accessible at `https://localhost:8443` (proxied from `http://localhost:8000` by nginx)
|
||||
- **Admin credentials**: `admin@localhost` / `admin`
|
||||
- **Bugsink Projects**: Backend (Dev) - Project ID 1, Frontend (Dev) - Project ID 2
|
||||
- **Configuration Files**:
|
||||
- `compose.dev.yml` - Sets default DSNs using `127.0.0.1:8000` protocol (for initial container setup)
|
||||
- `.env.local` - **OVERRIDES** compose.dev.yml with `localhost:8000` protocol (this is what the app actually uses)
|
||||
- **CRITICAL**: `.env.local` takes precedence over `compose.dev.yml` environment variables
|
||||
- **DSN Configuration**:
|
||||
- **Backend DSN** (Node.js/Express): Configured in `.env.local` as `SENTRY_DSN=http://<key>@localhost:8000/1`
|
||||
- **Frontend DSN** (React/Browser): Configured in `.env.local` as `VITE_SENTRY_DSN=http://<key>@localhost:8000/2`
|
||||
- **Why localhost instead of 127.0.0.1?** The `.env.local` file was created separately and uses `localhost` which works fine in practice
|
||||
- **HTTPS Setup**: Self-signed certificates auto-generated with mkcert on container startup (for UI access only, not for Sentry SDK)
|
||||
- **CSRF Protection**: Django configured with `SECURE_PROXY_SSL_HEADER` to trust `X-Forwarded-Proto` from nginx
|
||||
- **Isolated**: Dev errors stay local, don't pollute production/test dashboards
|
||||
- **No Gitea secrets needed**: Everything is self-contained in the container
|
||||
- **Accessing Errors**:
|
||||
- **Via Browser**: Open `https://localhost:8443` and login to view issues
|
||||
- **Via MCP**: Configure a second Bugsink MCP server pointing to `http://localhost:8000` (see MCP Servers section below)
|
||||
|
||||
---
|
||||
|
||||
@@ -436,64 +449,105 @@ The dev container runs its own **local Bugsink instance** - it does NOT connect
|
||||
|
||||
The following MCP servers are configured for this project:
|
||||
|
||||
| Server | Purpose |
|
||||
| --------------------- | ------------------------------------------- |
|
||||
| gitea-projectium | Gitea API for gitea.projectium.com |
|
||||
| gitea-torbonium | Gitea API for gitea.torbonium.com |
|
||||
| podman | Container management |
|
||||
| filesystem | File system access |
|
||||
| fetch | Web fetching |
|
||||
| markitdown | Convert documents to markdown |
|
||||
| sequential-thinking | Step-by-step reasoning |
|
||||
| memory | Knowledge graph persistence |
|
||||
| postgres | Direct database queries (localhost:5432) |
|
||||
| playwright | Browser automation and testing |
|
||||
| redis | Redis cache inspection (localhost:6379) |
|
||||
| sentry-selfhosted-mcp | Error tracking via Bugsink (localhost:8000) |
|
||||
| Server | Purpose |
|
||||
| ------------------- | ---------------------------------------------------------------------------- |
|
||||
| gitea-projectium | Gitea API for gitea.projectium.com |
|
||||
| gitea-torbonium | Gitea API for gitea.torbonium.com |
|
||||
| podman | Container management |
|
||||
| filesystem | File system access |
|
||||
| fetch | Web fetching |
|
||||
| markitdown | Convert documents to markdown |
|
||||
| sequential-thinking | Step-by-step reasoning |
|
||||
| memory | Knowledge graph persistence |
|
||||
| postgres | Direct database queries (localhost:5432) |
|
||||
| playwright | Browser automation and testing |
|
||||
| redis | Redis cache inspection (localhost:6379) |
|
||||
| bugsink | Error tracking - production Bugsink (bugsink.projectium.com) - **PROD/TEST** |
|
||||
| bugsink-dev | Error tracking - dev container Bugsink (localhost:8000) - **DEV CONTAINER** |
|
||||
|
||||
**Note:** MCP servers work in both **Claude CLI** and **Claude Code VS Code extension** (as of January 2026).
|
||||
|
||||
### Sentry/Bugsink MCP Server Setup (ADR-015)
|
||||
**CRITICAL**: There are **TWO separate Bugsink MCP servers**:
|
||||
|
||||
To enable Claude Code to query and analyze application errors from Bugsink:
|
||||
- **bugsink**: Connects to production Bugsink at `https://bugsink.projectium.com` for production and test server errors
|
||||
- **bugsink-dev**: Connects to local dev container Bugsink at `http://localhost:8000` for local development errors
|
||||
|
||||
1. **Install the MCP server**:
|
||||
### Bugsink MCP Server Setup (ADR-015)
|
||||
|
||||
```bash
|
||||
# Clone the sentry-selfhosted-mcp repository
|
||||
git clone https://github.com/ddfourtwo/sentry-selfhosted-mcp.git
|
||||
cd sentry-selfhosted-mcp
|
||||
npm install
|
||||
```
|
||||
**IMPORTANT**: You need to configure **TWO separate MCP servers** - one for production/test, one for local dev.
|
||||
|
||||
2. **Configure Claude Code** (add to `.claude/mcp.json`):
|
||||
#### Installation (shared for both servers)
|
||||
|
||||
```json
|
||||
{
|
||||
"sentry-selfhosted-mcp": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/sentry-selfhosted-mcp/dist/index.js"],
|
||||
"env": {
|
||||
"SENTRY_URL": "http://localhost:8000",
|
||||
"SENTRY_AUTH_TOKEN": "<get-from-bugsink-ui>",
|
||||
"SENTRY_ORG_SLUG": "flyer-crawler"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
# Clone the bugsink-mcp repository (NOT sentry-selfhosted-mcp)
|
||||
git clone https://github.com/j-shelfwood/bugsink-mcp.git
|
||||
cd bugsink-mcp
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
3. **Get the auth token**:
|
||||
- Navigate to Bugsink UI at `http://localhost:8000`
|
||||
- Log in with admin credentials
|
||||
- Go to Settings > API Keys
|
||||
- Create a new API key with read access
|
||||
#### Production/Test Bugsink MCP (bugsink)
|
||||
|
||||
4. **Available capabilities**:
|
||||
- List projects and issues
|
||||
- View detailed error events
|
||||
- Search by error message or stack trace
|
||||
- Update issue status (resolve, ignore)
|
||||
- Add comments to issues
|
||||
Add to `.claude/mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"bugsink": {
|
||||
"command": "node",
|
||||
"args": ["d:\\gitea\\bugsink-mcp\\dist\\index.js"],
|
||||
"env": {
|
||||
"BUGSINK_URL": "https://bugsink.projectium.com",
|
||||
"BUGSINK_API_TOKEN": "<get-from-production-bugsink>",
|
||||
"BUGSINK_ORG_SLUG": "sentry"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Get the auth token**:
|
||||
|
||||
- Navigate to https://bugsink.projectium.com
|
||||
- Log in with production credentials
|
||||
- Go to Settings > API Keys
|
||||
- Create a new API key with read access
|
||||
|
||||
#### Dev Container Bugsink MCP (bugsink-dev)
|
||||
|
||||
Add to `.claude/mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"bugsink-dev": {
|
||||
"command": "node",
|
||||
"args": ["d:\\gitea\\bugsink-mcp\\dist\\index.js"],
|
||||
"env": {
|
||||
"BUGSINK_URL": "http://localhost:8000",
|
||||
"BUGSINK_API_TOKEN": "<get-from-local-bugsink>",
|
||||
"BUGSINK_ORG_SLUG": "sentry"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Get the auth token**:
|
||||
|
||||
- Navigate to http://localhost:8000 (or https://localhost:8443)
|
||||
- Log in with `admin@localhost` / `admin`
|
||||
- Go to Settings > API Keys
|
||||
- Create a new API key with read access
|
||||
|
||||
#### MCP Tool Usage
|
||||
|
||||
When using Bugsink MCP tools, remember:
|
||||
|
||||
- `mcp__bugsink__*` tools connect to **production/test** Bugsink
|
||||
- `mcp__bugsink-dev__*` tools connect to **dev container** Bugsink
|
||||
- Available capabilities for both:
|
||||
- List projects and issues
|
||||
- View detailed error events and stacktraces
|
||||
- Search by error message or stack trace
|
||||
- Update issue status (resolve, ignore)
|
||||
- Create releases
|
||||
|
||||
### SSH Server Access
|
||||
|
||||
|
||||
@@ -147,6 +147,9 @@ ALLOWED_HOSTS = deduce_allowed_hosts(BUGSINK["BASE_URL"])\n\
|
||||
\n\
|
||||
# Console email backend for dev\n\
|
||||
EMAIL_BACKEND = "bugsink.email_backends.QuietConsoleEmailBackend"\n\
|
||||
\n\
|
||||
# HTTPS proxy support (nginx reverse proxy on port 8443)\n\
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")\n\
|
||||
' > /opt/bugsink/conf/bugsink_conf.py
|
||||
|
||||
# Create Bugsink startup script
|
||||
|
||||
@@ -59,7 +59,11 @@ See [INSTALL.md](INSTALL.md) for detailed setup instructions.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
This project uses environment variables for configuration (no `.env` files). Key variables:
|
||||
**Production/Test**: Uses Gitea CI/CD secrets injected during deployment (no local `.env` files)
|
||||
|
||||
**Dev Container**: Uses `.env.local` file which **overrides** the default DSNs in `compose.dev.yml`
|
||||
|
||||
Key variables:
|
||||
|
||||
| Variable | Description |
|
||||
| -------------------------------------------- | -------------------------------- |
|
||||
|
||||
@@ -50,7 +50,8 @@ services:
|
||||
- '80:80' # HTTP redirect to HTTPS (matches production)
|
||||
- '443:443' # Frontend HTTPS (nginx proxies Vite 5173 → 443)
|
||||
- '3001:3001' # Backend API
|
||||
- '8000:8000' # Bugsink error tracking (ADR-015)
|
||||
- '8000:8000' # Bugsink error tracking HTTP (ADR-015)
|
||||
- '8443:8443' # Bugsink error tracking HTTPS (ADR-015)
|
||||
environment:
|
||||
# Core settings
|
||||
- NODE_ENV=development
|
||||
@@ -77,13 +78,16 @@ services:
|
||||
- BUGSINK_DB_USER=bugsink
|
||||
- BUGSINK_DB_PASSWORD=bugsink_dev_password
|
||||
- BUGSINK_PORT=8000
|
||||
- BUGSINK_BASE_URL=http://localhost:8000
|
||||
- BUGSINK_BASE_URL=https://localhost:8443
|
||||
- BUGSINK_ADMIN_EMAIL=admin@localhost
|
||||
- BUGSINK_ADMIN_PASSWORD=admin
|
||||
- BUGSINK_SECRET_KEY=dev-bugsink-secret-key-minimum-50-characters-for-security
|
||||
# Sentry SDK configuration (points to local Bugsink)
|
||||
- SENTRY_DSN=http://59a58583-e869-7697-f94a-cfa0337676a8@localhost:8000/1
|
||||
- VITE_SENTRY_DSN=http://d5fc5221-4266-ff2f-9af8-5689696072f3@localhost:8000/2
|
||||
# Sentry SDK configuration (points to local Bugsink HTTP)
|
||||
# Note: Using HTTP with 127.0.0.1 instead of localhost because Sentry SDK
|
||||
# doesn't accept 'localhost' as a valid hostname in DSN validation
|
||||
# The browser accesses Bugsink at http://localhost:8000 (nginx proxies to HTTPS for the app)
|
||||
- SENTRY_DSN=http://cea01396-c562-46ad-b587-8fa5ee6b1d22@127.0.0.1:8000/1
|
||||
- VITE_SENTRY_DSN=http://d92663cb-73cf-4145-b677-b84029e4b762@127.0.0.1:8000/2
|
||||
- SENTRY_ENVIRONMENT=development
|
||||
- VITE_SENTRY_ENVIRONMENT=development
|
||||
- SENTRY_ENABLED=true
|
||||
|
||||
@@ -24,7 +24,29 @@ server {
|
||||
# Allow large file uploads (matches production)
|
||||
client_max_body_size 100M;
|
||||
|
||||
# Proxy all requests to Vite dev server on port 5173
|
||||
# Proxy API requests to Express server on port 3001
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:3001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Proxy WebSocket connections for real-time notifications
|
||||
location /ws {
|
||||
proxy_pass http://localhost:3001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Proxy all other requests to Vite dev server on port 5173
|
||||
location / {
|
||||
proxy_pass http://localhost:5173;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
534
docs/TESTING_SESSION_2026-01-21.md
Normal file
534
docs/TESTING_SESSION_2026-01-21.md
Normal file
@@ -0,0 +1,534 @@
|
||||
# Testing Session - UI/UX Improvements
|
||||
|
||||
**Date**: 2026-01-21
|
||||
**Tester**: [Your Name]
|
||||
**Session Start**: [Time]
|
||||
**Environment**: Dev Container
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Session Objective
|
||||
|
||||
Test all 4 critical UI/UX improvements:
|
||||
|
||||
1. Brand Colors (visual verification)
|
||||
2. Button Component (functional testing)
|
||||
3. Onboarding Tour (flow testing)
|
||||
4. Mobile Navigation (responsive testing)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Pre-Test Setup Checklist
|
||||
|
||||
### 1. Dev Server Status
|
||||
|
||||
- [ ] Dev server running at `http://localhost:5173`
|
||||
- [ ] Browser open (Chrome/Edge recommended)
|
||||
- [ ] DevTools open (F12)
|
||||
|
||||
**Command to start**:
|
||||
|
||||
```bash
|
||||
podman exec -it flyer-crawler-dev npm run dev:container
|
||||
```
|
||||
|
||||
**Server Status**: [ ] Running [ ] Not Running
|
||||
|
||||
---
|
||||
|
||||
### 2. Browser Setup
|
||||
|
||||
- [ ] Clear cache (Ctrl+Shift+Delete)
|
||||
- [ ] Clear localStorage for localhost
|
||||
- [ ] Enable responsive design mode (Ctrl+Shift+M)
|
||||
|
||||
**Browser Version**: ********\_********
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test Execution
|
||||
|
||||
### TEST 1: Onboarding Tour ⭐ CRITICAL
|
||||
|
||||
**Priority**: 🔴 Must Pass
|
||||
**Time**: 5 minutes
|
||||
|
||||
#### Steps:
|
||||
|
||||
1. Open DevTools → Application → Local Storage
|
||||
2. Delete key: `flyer_crawler_onboarding_completed`
|
||||
3. Refresh page (F5)
|
||||
4. Observe if tour appears
|
||||
|
||||
#### Expected:
|
||||
|
||||
- ✅ Tour modal appears within 2 seconds
|
||||
- ✅ Shows "Step 1 of 6"
|
||||
- ✅ Points to Flyer Uploader section
|
||||
- ✅ Skip button visible
|
||||
- ✅ Next button visible
|
||||
|
||||
#### Actual Result:
|
||||
|
||||
```
|
||||
[Record what you see here]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL [ ] ⚠️ PARTIAL
|
||||
|
||||
**Screenshots**: [Attach if needed]
|
||||
|
||||
---
|
||||
|
||||
### TEST 2: Tour Navigation
|
||||
|
||||
**Time**: 5 minutes
|
||||
|
||||
#### Steps:
|
||||
|
||||
Click "Next" button 6 times, observe each step
|
||||
|
||||
#### Verification Table:
|
||||
|
||||
| Step | Target | Visible? | Correct Text? | Notes |
|
||||
| ---- | -------------- | -------- | ------------- | ----- |
|
||||
| 1 | Flyer Uploader | [ ] | [ ] | |
|
||||
| 2 | Data Table | [ ] | [ ] | |
|
||||
| 3 | Watch Button | [ ] | [ ] | |
|
||||
| 4 | Watchlist | [ ] | [ ] | |
|
||||
| 5 | Price Chart | [ ] | [ ] | |
|
||||
| 6 | Shopping List | [ ] | [ ] | |
|
||||
|
||||
#### Additional Checks:
|
||||
|
||||
- [ ] Progress indicator updates (1/6 → 6/6)
|
||||
- [ ] Can click "Previous" button
|
||||
- [ ] Tour closes after step 6
|
||||
- [ ] localStorage key saved
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL
|
||||
|
||||
---
|
||||
|
||||
### TEST 3: Mobile Tab Bar ⭐ CRITICAL
|
||||
|
||||
**Priority**: 🔴 Must Pass
|
||||
**Time**: 8 minutes
|
||||
|
||||
#### Part A: Mobile View (375px)
|
||||
|
||||
**Setup**: Toggle device toolbar → iPhone SE
|
||||
|
||||
#### Checks:
|
||||
|
||||
- [ ] Bottom tab bar visible
|
||||
- [ ] 4 tabs present: Home, Deals, Lists, Profile
|
||||
- [ ] Left sidebar (flyer list) HIDDEN
|
||||
- [ ] Right sidebar (widgets) HIDDEN
|
||||
- [ ] Main content uses full width
|
||||
|
||||
**Visual Check**:
|
||||
|
||||
```
|
||||
Tab Bar Position: [ ] Bottom [ ] Other: _______
|
||||
Number of Tabs: _______
|
||||
Tab Bar Height: ~64px? [ ] Yes [ ] No
|
||||
```
|
||||
|
||||
#### Part B: Tab Navigation
|
||||
|
||||
Click each tab and verify:
|
||||
|
||||
| Tab | URL | Page Loads? | Highlights? | Content Correct? |
|
||||
| ------- | ---------- | ----------- | ----------- | ---------------- |
|
||||
| Home | `/` | [ ] | [ ] | [ ] |
|
||||
| Deals | `/deals` | [ ] | [ ] | [ ] |
|
||||
| Lists | `/lists` | [ ] | [ ] | [ ] |
|
||||
| Profile | `/profile` | [ ] | [ ] | [ ] |
|
||||
|
||||
#### Part C: Desktop View (1440px)
|
||||
|
||||
**Setup**: Exit device mode, maximize window
|
||||
|
||||
#### Checks:
|
||||
|
||||
- [ ] Tab bar HIDDEN (not visible)
|
||||
- [ ] Left sidebar VISIBLE
|
||||
- [ ] Right sidebar VISIBLE
|
||||
- [ ] 3-column layout intact
|
||||
- [ ] No layout regressions
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL
|
||||
|
||||
---
|
||||
|
||||
### TEST 4: Dark Mode ⭐ CRITICAL
|
||||
|
||||
**Priority**: 🔴 Must Pass
|
||||
**Time**: 5 minutes
|
||||
|
||||
#### Steps:
|
||||
|
||||
1. Click dark mode toggle in header
|
||||
2. Navigate: Home → Deals → Lists → Profile
|
||||
3. Observe colors and contrast
|
||||
|
||||
#### Visual Verification:
|
||||
|
||||
**Mobile Tab Bar**:
|
||||
|
||||
- [ ] Dark background (#111827 or similar)
|
||||
- [ ] Dark border color
|
||||
- [ ] Active tab: teal (#14b8a6)
|
||||
- [ ] Inactive tabs: gray
|
||||
|
||||
**New Pages**:
|
||||
|
||||
- [ ] DealsPage: dark background, light text
|
||||
- [ ] ShoppingListsPage: dark cards
|
||||
- [ ] FlyersPage: dark theme
|
||||
- [ ] No white boxes visible
|
||||
|
||||
**Button Component**:
|
||||
|
||||
- [ ] Primary buttons: teal background
|
||||
- [ ] Secondary buttons: gray background
|
||||
- [ ] Danger buttons: red background
|
||||
- [ ] All text readable
|
||||
|
||||
#### Toggle Back:
|
||||
|
||||
- [ ] Light mode restores correctly
|
||||
- [ ] No stuck dark elements
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL
|
||||
|
||||
---
|
||||
|
||||
### TEST 5: Brand Colors Visual Check
|
||||
|
||||
**Time**: 3 minutes
|
||||
|
||||
#### Verification:
|
||||
|
||||
Navigate through app and check teal color consistency:
|
||||
|
||||
- [ ] Active tab: teal
|
||||
- [ ] Primary buttons: teal
|
||||
- [ ] Links on hover: teal
|
||||
- [ ] Focus rings: teal
|
||||
- [ ] All teal shades match (#14b8a6)
|
||||
|
||||
**Color Picker Check** (optional):
|
||||
Use DevTools color picker on active tab:
|
||||
|
||||
- Expected: `#14b8a6` or `rgb(20, 184, 166)`
|
||||
- Actual: ********\_\_\_********
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL
|
||||
|
||||
---
|
||||
|
||||
### TEST 6: Button Component
|
||||
|
||||
**Time**: 5 minutes
|
||||
|
||||
#### Find and Test Buttons:
|
||||
|
||||
**FlyerUploader Page**:
|
||||
|
||||
- [ ] "Upload Another Flyer" button (primary, teal)
|
||||
- [ ] Button clickable
|
||||
- [ ] Hover effect works
|
||||
- [ ] Loading state (if applicable)
|
||||
|
||||
**ShoppingList Page** (navigate to /lists):
|
||||
|
||||
- [ ] "New List" button (secondary, gray)
|
||||
- [ ] "Delete List" button (danger, red)
|
||||
- [ ] Buttons functional
|
||||
- [ ] Hover states work
|
||||
|
||||
**In Dark Mode**:
|
||||
|
||||
- [ ] All button variants visible
|
||||
- [ ] Good contrast
|
||||
- [ ] No white backgrounds
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL
|
||||
|
||||
---
|
||||
|
||||
### TEST 7: Responsive Breakpoints
|
||||
|
||||
**Time**: 5 minutes
|
||||
|
||||
#### Test at each width:
|
||||
|
||||
**375px (Mobile)**:
|
||||
|
||||
```
|
||||
Tab bar: [ ] Visible [ ] Hidden
|
||||
Sidebars: [ ] Visible [ ] Hidden
|
||||
Layout: [ ] Single column [ ] Multi-column
|
||||
```
|
||||
|
||||
**768px (Tablet)**:
|
||||
|
||||
```
|
||||
Tab bar: [ ] Visible [ ] Hidden
|
||||
Sidebars: [ ] Visible [ ] Hidden
|
||||
Layout: [ ] Single column [ ] Multi-column
|
||||
```
|
||||
|
||||
**1024px (Desktop)**:
|
||||
|
||||
```
|
||||
Tab bar: [ ] Visible [ ] Hidden
|
||||
Sidebars: [ ] Visible [ ] Hidden
|
||||
Layout: [ ] Single column [ ] Multi-column
|
||||
```
|
||||
|
||||
**1440px (Large Desktop)**:
|
||||
|
||||
```
|
||||
Layout: [ ] Unchanged [ ] Broken
|
||||
All elements: [ ] Visible [ ] Hidden/Cut off
|
||||
```
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL
|
||||
|
||||
---
|
||||
|
||||
### TEST 8: Admin Routes (If Admin User)
|
||||
|
||||
**Time**: 3 minutes
|
||||
**Skip if**: [ ] Not admin user
|
||||
|
||||
#### Steps:
|
||||
|
||||
1. Log in as admin
|
||||
2. Navigate to `/admin`
|
||||
3. Check for tab bar
|
||||
|
||||
#### Checks:
|
||||
|
||||
- [ ] Admin dashboard loads
|
||||
- [ ] Tab bar NOT visible
|
||||
- [ ] Layout looks correct
|
||||
- [ ] Can navigate to subpages
|
||||
- [ ] Subpages work in mobile view
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL [ ] ⏭️ SKIPPED
|
||||
|
||||
---
|
||||
|
||||
### TEST 9: Console Errors
|
||||
|
||||
**Time**: 2 minutes
|
||||
|
||||
#### Steps:
|
||||
|
||||
1. Open Console tab in DevTools
|
||||
2. Clear console
|
||||
3. Navigate through app: Home → Deals → Lists → Profile → Home
|
||||
4. Check for red error messages
|
||||
|
||||
#### Results:
|
||||
|
||||
```
|
||||
Errors Found: [ ] None [ ] Some (list below)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
**React 19 warnings are OK** (peer dependencies)
|
||||
|
||||
**Status**: [ ] ✅ PASS (no errors) [ ] ❌ FAIL (errors present)
|
||||
|
||||
---
|
||||
|
||||
### TEST 10: Integration Flow
|
||||
|
||||
**Time**: 5 minutes
|
||||
|
||||
#### User Journey:
|
||||
|
||||
1. Start on Home page (mobile view)
|
||||
2. Navigate to Deals tab
|
||||
3. Navigate to Lists tab
|
||||
4. Navigate to Profile tab
|
||||
5. Navigate back to Home
|
||||
6. Toggle dark mode
|
||||
7. Navigate through tabs again
|
||||
|
||||
#### Checks:
|
||||
|
||||
- [ ] All navigation smooth
|
||||
- [ ] No data loss
|
||||
- [ ] Active tab always correct
|
||||
- [ ] Browser back button works
|
||||
- [ ] Dark mode persists across routes
|
||||
- [ ] No JavaScript errors
|
||||
- [ ] No layout shifting
|
||||
|
||||
**Status**: [ ] ✅ PASS [ ] ❌ FAIL
|
||||
|
||||
---
|
||||
|
||||
## 📊 Test Results Summary
|
||||
|
||||
### Critical Tests Status
|
||||
|
||||
| Test | Status | Priority | Notes |
|
||||
| ------------------- | ------ | ----------- | ----- |
|
||||
| 1. Onboarding Tour | [ ] | 🔴 Critical | |
|
||||
| 2. Tour Navigation | [ ] | 🟡 High | |
|
||||
| 3. Mobile Tab Bar | [ ] | 🔴 Critical | |
|
||||
| 4. Dark Mode | [ ] | 🔴 Critical | |
|
||||
| 5. Brand Colors | [ ] | 🟡 High | |
|
||||
| 6. Button Component | [ ] | 🟢 Medium | |
|
||||
| 7. Responsive | [ ] | 🔴 Critical | |
|
||||
| 8. Admin Routes | [ ] | 🟢 Medium | |
|
||||
| 9. Console Errors | [ ] | 🔴 Critical | |
|
||||
| 10. Integration | [ ] | 🟡 High | |
|
||||
|
||||
**Pass Rate**: **\_** / 10 tests passed
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Issues Found
|
||||
|
||||
### Critical Issues (Blockers)
|
||||
|
||||
1. ***
|
||||
2. ***
|
||||
3. ***
|
||||
|
||||
### High Priority Issues
|
||||
|
||||
1. ***
|
||||
2. ***
|
||||
3. ***
|
||||
|
||||
### Medium/Low Priority Issues
|
||||
|
||||
1. ***
|
||||
2. ***
|
||||
3. ***
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots
|
||||
|
||||
Attach screenshots for:
|
||||
|
||||
- [ ] Onboarding tour (step 1)
|
||||
- [ ] Mobile tab bar (375px)
|
||||
- [ ] Desktop layout (1440px)
|
||||
- [ ] Dark mode (tab bar)
|
||||
- [ ] Any bugs/issues found
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Final Decision
|
||||
|
||||
### Must-Pass Criteria
|
||||
|
||||
**Critical tests** (all must pass):
|
||||
|
||||
- [ ] Test 1: Onboarding Tour
|
||||
- [ ] Test 3: Mobile Tab Bar
|
||||
- [ ] Test 4: Dark Mode
|
||||
- [ ] Test 7: Responsive
|
||||
- [ ] Test 9: No Console Errors
|
||||
|
||||
**Result**: [ ] ALL CRITICAL PASS [ ] SOME FAIL
|
||||
|
||||
---
|
||||
|
||||
### Production Readiness
|
||||
|
||||
**Overall Assessment**:
|
||||
[ ] ✅ READY FOR PRODUCTION
|
||||
[ ] ⚠️ READY WITH MINOR ISSUES
|
||||
[ ] ❌ NOT READY (critical issues)
|
||||
|
||||
**Blocking Issues** (must fix before deploy):
|
||||
|
||||
1. ***
|
||||
2. ***
|
||||
3. ***
|
||||
|
||||
**Recommended Fixes** (can deploy, fix later):
|
||||
|
||||
1. ***
|
||||
2. ***
|
||||
3. ***
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Sign-Off
|
||||
|
||||
**Tester Name**: ****************\_\_\_****************
|
||||
|
||||
**Date/Time Completed**: ************\_\_\_************
|
||||
|
||||
**Total Testing Time**: **\_\_** minutes
|
||||
|
||||
**Recommended Action**:
|
||||
[ ] Deploy to production
|
||||
[ ] Deploy to staging first
|
||||
[ ] Fix issues, re-test
|
||||
[ ] Hold deployment
|
||||
|
||||
**Additional Notes**:
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 📋 Next Steps
|
||||
|
||||
**If PASS**:
|
||||
|
||||
1. [ ] Create commit with test results
|
||||
2. [ ] Update CHANGELOG.md
|
||||
3. [ ] Tag release (v0.12.4)
|
||||
4. [ ] Deploy to staging
|
||||
5. [ ] Monitor for 24 hours
|
||||
6. [ ] Deploy to production
|
||||
|
||||
**If FAIL**:
|
||||
|
||||
1. [ ] Log issues in GitHub/Gitea
|
||||
2. [ ] Assign to developer
|
||||
3. [ ] Schedule re-test
|
||||
4. [ ] Update test plan if needed
|
||||
|
||||
---
|
||||
|
||||
**Session End**: [Time]
|
||||
**Session Duration**: **\_\_** minutes
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.12.4",
|
||||
"version": "0.12.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.12.4",
|
||||
"version": "0.12.6",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.12.4",
|
||||
"version": "0.12.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||
|
||||
@@ -17,9 +17,48 @@ set -e
|
||||
|
||||
echo "🚀 Starting Flyer Crawler Dev Container..."
|
||||
|
||||
# Configure Bugsink HTTPS (ADR-015)
|
||||
echo "🔒 Configuring Bugsink HTTPS..."
|
||||
mkdir -p /etc/bugsink/ssl
|
||||
if [ ! -f "/etc/bugsink/ssl/localhost+2.pem" ]; then
|
||||
cd /etc/bugsink/ssl && mkcert localhost 127.0.0.1 ::1 > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Create nginx config for Bugsink HTTPS
|
||||
cat > /etc/nginx/sites-available/bugsink <<'NGINX_EOF'
|
||||
server {
|
||||
listen 8443 ssl http2;
|
||||
listen [::]:8443 ssl http2;
|
||||
server_name localhost;
|
||||
|
||||
ssl_certificate /etc/bugsink/ssl/localhost+2.pem;
|
||||
ssl_certificate_key /etc/bugsink/ssl/localhost+2-key.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
NGINX_EOF
|
||||
|
||||
ln -sf /etc/nginx/sites-available/bugsink /etc/nginx/sites-enabled/bugsink
|
||||
|
||||
# Copy the dev nginx config from mounted volume to nginx sites-available
|
||||
echo "📋 Copying nginx dev config..."
|
||||
cp /app/docker/nginx/dev.conf /etc/nginx/sites-available/default
|
||||
|
||||
# Start nginx in background (if installed)
|
||||
if command -v nginx &> /dev/null; then
|
||||
echo "🌐 Starting nginx (HTTPS proxy: Vite 5173 → port 443)..."
|
||||
echo "🌐 Starting nginx (HTTPS: Vite 5173 → 443, Bugsink 8000 → 8443, API 3001 → /api/)..."
|
||||
nginx &
|
||||
fi
|
||||
|
||||
@@ -27,6 +66,22 @@ fi
|
||||
echo "📊 Starting Bugsink error tracking..."
|
||||
/usr/local/bin/start-bugsink.sh > /var/log/bugsink/server.log 2>&1 &
|
||||
|
||||
# Wait for Bugsink to initialize, then run snappea migrations
|
||||
echo "⏳ Waiting for Bugsink to initialize..."
|
||||
sleep 5
|
||||
echo "🔧 Running Bugsink snappea database migrations..."
|
||||
cd /opt/bugsink/conf && \
|
||||
export DATABASE_URL="postgresql://bugsink:bugsink_dev_password@postgres:5432/bugsink" && \
|
||||
export SECRET_KEY="dev-bugsink-secret-key-minimum-50-characters-for-security" && \
|
||||
/opt/bugsink/bin/bugsink-manage migrate --database=snappea > /dev/null 2>&1
|
||||
|
||||
# Start Snappea task worker
|
||||
echo "🔄 Starting Snappea task worker..."
|
||||
cd /opt/bugsink/conf && \
|
||||
export DATABASE_URL="postgresql://bugsink:bugsink_dev_password@postgres:5432/bugsink" && \
|
||||
export SECRET_KEY="dev-bugsink-secret-key-minimum-50-characters-for-security" && \
|
||||
/opt/bugsink/bin/bugsink-manage runsnappea > /var/log/bugsink/snappea.log 2>&1 &
|
||||
|
||||
# Start Logstash in background
|
||||
echo "📝 Starting Logstash..."
|
||||
/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/bugsink.conf > /var/log/logstash/logstash.log 2>&1 &
|
||||
@@ -41,8 +96,8 @@ cd /app
|
||||
echo "💻 Starting development server..."
|
||||
echo " - Frontend: https://localhost (nginx HTTPS → Vite on 5173)"
|
||||
echo " - Backend API: http://localhost:3001"
|
||||
echo " - Bugsink: http://localhost:8000"
|
||||
echo " - Note: Accept the self-signed certificate warning in your browser"
|
||||
echo " - Bugsink: https://localhost:8443 (nginx HTTPS → Bugsink on 8000)"
|
||||
echo " - Note: Accept the self-signed certificate warnings in your browser"
|
||||
echo ""
|
||||
|
||||
# Run npm dev server (this will block and keep container alive)
|
||||
|
||||
@@ -15,7 +15,7 @@ export const Dashboard: React.FC = () => {
|
||||
<RecipeSuggester />
|
||||
|
||||
{/* Other Dashboard Widgets */}
|
||||
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80">
|
||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">Your Flyers</h2>
|
||||
<FlyerCountDisplay />
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
// The state and handlers for the old AuthModal and SignUpModal have been removed.
|
||||
return (
|
||||
<>
|
||||
<header className="bg-white dark:bg-gray-900 shadow-md sticky top-0 z-20">
|
||||
<header className="bg-white dark:bg-gray-900 shadow-md sticky top-0 z-20 border-b-2 border-brand-primary dark:border-brand-secondary">
|
||||
<div className="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex items-center">
|
||||
|
||||
@@ -43,7 +43,7 @@ export const Leaderboard: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6">
|
||||
<div className="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80">
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4 flex items-center">
|
||||
<Award className="w-6 h-6 mr-2 text-blue-500" />
|
||||
Top Users
|
||||
@@ -57,7 +57,7 @@ export const Leaderboard: React.FC = () => {
|
||||
{leaderboard.map((user) => (
|
||||
<li
|
||||
key={user.user_id}
|
||||
className="flex items-center space-x-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg transition hover:bg-gray-100 dark:hover:bg-gray-600"
|
||||
className="flex items-center space-x-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg transition-colors hover:bg-brand-light/30 dark:hover:bg-brand-dark/20"
|
||||
>
|
||||
<div className="shrink-0 w-8 text-center">{getRankIcon(user.rank)}</div>
|
||||
<img
|
||||
|
||||
@@ -48,7 +48,7 @@ export const RecipeSuggester: React.FC = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Get a Recipe Suggestion
|
||||
</h2>
|
||||
|
||||
@@ -155,9 +155,15 @@ export const MainLayout: React.FC<MainLayoutProps> = ({
|
||||
unitSystem={'imperial'} // This can be passed down or sourced from a context
|
||||
user={user}
|
||||
/>
|
||||
<PriceHistoryChart />
|
||||
<Leaderboard />
|
||||
<ActivityLog userProfile={userProfile} onLogClick={handleActivityLogClick} />
|
||||
{user && (
|
||||
<>
|
||||
<PriceHistoryChart />
|
||||
<Leaderboard />
|
||||
{userProfile && (
|
||||
<ActivityLog userProfile={userProfile} onLogClick={handleActivityLogClick} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,14 @@ import supertest from 'supertest';
|
||||
import { cleanupDb } from '../utils/cleanup';
|
||||
import { createAndLoginUser } from '../utils/testHelpers';
|
||||
import type { UserProfile } from '../../types';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
describe('Admin Route Authorization', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
let regularUser: UserProfile;
|
||||
let regularUserAuthToken: string;
|
||||
@@ -38,27 +38,27 @@ describe('Admin Route Authorization', () => {
|
||||
const adminEndpoints = [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/admin/stats',
|
||||
path: '/api/admin/stats',
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/admin/users',
|
||||
path: '/api/admin/users',
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/admin/corrections',
|
||||
path: '/api/admin/corrections',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/admin/corrections/1/approve',
|
||||
path: '/api/admin/corrections/1/approve',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/admin/trigger/daily-deal-check',
|
||||
path: '/api/admin/trigger/daily-deal-check',
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/admin/queues/status',
|
||||
path: '/api/admin/queues/status',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describe, it, expect, afterAll } from 'vitest';
|
||||
import supertest from 'supertest';
|
||||
import { getPool } from '../../services/db/connection.db';
|
||||
import { cleanupDb } from '../utils/cleanup';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -11,7 +11,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E Admin Dashboard Flow', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
// Use a unique email for every run to avoid collisions
|
||||
const uniqueId = Date.now();
|
||||
@@ -62,7 +62,7 @@ describe('E2E Admin Dashboard Flow', () => {
|
||||
|
||||
// 4. Fetch System Stats (Protected Admin Route)
|
||||
const statsResponse = await getRequest()
|
||||
.get('/admin/stats')
|
||||
.get('/api/admin/stats')
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(statsResponse.status).toBe(200);
|
||||
@@ -71,7 +71,7 @@ describe('E2E Admin Dashboard Flow', () => {
|
||||
|
||||
// 5. Fetch User List (Protected Admin Route)
|
||||
const usersResponse = await getRequest()
|
||||
.get('/admin/users')
|
||||
.get('/api/admin/users')
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(usersResponse.status).toBe(200);
|
||||
@@ -84,7 +84,7 @@ describe('E2E Admin Dashboard Flow', () => {
|
||||
|
||||
// 6. Check Queue Status (Protected Admin Route)
|
||||
const queueResponse = await getRequest()
|
||||
.get('/admin/queues/status')
|
||||
.get('/api/admin/queues/status')
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(queueResponse.status).toBe(200);
|
||||
|
||||
@@ -4,7 +4,7 @@ import supertest from 'supertest';
|
||||
import { cleanupDb } from '../utils/cleanup';
|
||||
import { createAndLoginUser, TEST_PASSWORD } from '../utils/testHelpers';
|
||||
import type { UserProfile } from '../../types';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -12,7 +12,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('Authentication E2E Flow', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
let testUser: UserProfile;
|
||||
let testUserAuthToken: string;
|
||||
@@ -142,7 +142,7 @@ describe('Authentication E2E Flow', () => {
|
||||
|
||||
// Act: Use the token to access a protected route
|
||||
const response = await getRequest()
|
||||
.get('/api/users/me')
|
||||
.get('/api/users/profile')
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
|
||||
// Assert
|
||||
@@ -165,7 +165,7 @@ describe('Authentication E2E Flow', () => {
|
||||
|
||||
// Act: Call the update endpoint
|
||||
const updateResponse = await getRequest()
|
||||
.put('/api/users/me')
|
||||
.put('/api/users/profile')
|
||||
.set('Authorization', `Bearer ${token}`)
|
||||
.send(profileUpdates);
|
||||
|
||||
@@ -176,7 +176,7 @@ describe('Authentication E2E Flow', () => {
|
||||
|
||||
// Act 2: Fetch the profile again to verify persistence
|
||||
const verifyResponse = await getRequest()
|
||||
.get('/api/users/me')
|
||||
.get('/api/users/profile')
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
|
||||
// Assert 2: Check the fetched data
|
||||
@@ -295,7 +295,7 @@ describe('Authentication E2E Flow', () => {
|
||||
|
||||
// 5. Use the new access token to access a protected route.
|
||||
const profileResponse = await getRequest()
|
||||
.get('/api/users/me')
|
||||
.get('/api/users/profile')
|
||||
.set('Authorization', `Bearer ${newAccessToken}`);
|
||||
expect(profileResponse.status).toBe(200);
|
||||
expect(profileResponse.body.data.user.user_id).toBe(testUser.user.user_id);
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
cleanupStoreLocations,
|
||||
type CreatedStoreLocation,
|
||||
} from '../utils/storeHelpers';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -21,7 +21,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E Budget Management Journey', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const userEmail = `budget-e2e-${uniqueId}@example.com`;
|
||||
@@ -310,7 +310,7 @@ describe('E2E Budget Management Journey', () => {
|
||||
|
||||
// Step 15: Delete account
|
||||
const deleteAccountResponse = await getRequest()
|
||||
.delete('/api/user/account')
|
||||
.delete('/api/users/account')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ password: userPassword });
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
cleanupStoreLocations,
|
||||
type CreatedStoreLocation,
|
||||
} from '../utils/storeHelpers';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -21,7 +21,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E Deals and Price Tracking Journey', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const userEmail = `deals-e2e-${uniqueId}@example.com`;
|
||||
@@ -373,7 +373,7 @@ describe('E2E Deals and Price Tracking Journey', () => {
|
||||
|
||||
// Step 11: Delete account
|
||||
const deleteAccountResponse = await getRequest()
|
||||
.delete('/api/user/account')
|
||||
.delete('/api/users/account')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ password: userPassword });
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { cleanupDb } from '../utils/cleanup';
|
||||
import { poll } from '../utils/poll';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -15,7 +15,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E Flyer Upload and Processing Workflow', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const userEmail = `e2e-uploader-${uniqueId}@example.com`;
|
||||
|
||||
@@ -8,7 +8,7 @@ import supertest from 'supertest';
|
||||
import { cleanupDb } from '../utils/cleanup';
|
||||
import { poll } from '../utils/poll';
|
||||
import { getPool } from '../../services/db/connection.db';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -16,7 +16,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E Inventory/Expiry Management Journey', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const userEmail = `inventory-e2e-${uniqueId}@example.com`;
|
||||
@@ -408,7 +408,7 @@ describe('E2E Inventory/Expiry Management Journey', () => {
|
||||
|
||||
// Step 20: Delete account
|
||||
const deleteAccountResponse = await getRequest()
|
||||
.delete('/api/user/account')
|
||||
.delete('/api/users/account')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ password: userPassword });
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
cleanupStoreLocations,
|
||||
type CreatedStoreLocation,
|
||||
} from '../utils/storeHelpers';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -21,7 +21,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E Receipt Processing Journey', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const userEmail = `receipt-e2e-${uniqueId}@example.com`;
|
||||
@@ -318,7 +318,7 @@ describe('E2E Receipt Processing Journey', () => {
|
||||
|
||||
// Step 19: Delete account
|
||||
const deleteAccountResponse = await getRequest()
|
||||
.delete('/api/user/account')
|
||||
.delete('/api/users/account')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ password: userPassword });
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import supertest from 'supertest';
|
||||
import { cleanupDb } from '../utils/cleanup';
|
||||
import { poll } from '../utils/poll';
|
||||
import { getPool } from '../../services/db/connection.db';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -16,7 +16,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E UPC Scanning Journey', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const userEmail = `upc-e2e-${uniqueId}@example.com`;
|
||||
@@ -219,7 +219,7 @@ describe('E2E UPC Scanning Journey', () => {
|
||||
|
||||
// Step 12: Delete account (self-service)
|
||||
const deleteAccountResponse = await getRequest()
|
||||
.delete('/api/user/account')
|
||||
.delete('/api/users/account')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ password: userPassword });
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { describe, it, expect, afterAll } from 'vitest';
|
||||
import supertest from 'supertest';
|
||||
import { cleanupDb } from '../utils/cleanup';
|
||||
import { app } from '../setup/e2e-global-setup';
|
||||
import { getServerUrl } from '../setup/e2e-global-setup';
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
@@ -10,7 +10,7 @@ import { app } from '../setup/e2e-global-setup';
|
||||
|
||||
describe('E2E User Journey', () => {
|
||||
// Create a getter function that returns supertest instance with the app
|
||||
const getRequest = () => supertest(app);
|
||||
const getRequest = () => supertest(getServerUrl());
|
||||
|
||||
// Use a unique email for every run to avoid collisions
|
||||
const uniqueId = Date.now();
|
||||
@@ -62,7 +62,7 @@ describe('E2E User Journey', () => {
|
||||
|
||||
// 3. Create a Shopping List
|
||||
const createListResponse = await getRequest()
|
||||
.post('/api/users/me/shopping-lists')
|
||||
.post('/api/users/shopping-lists')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ name: 'E2E Party List' });
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('E2E User Journey', () => {
|
||||
|
||||
// 4. Add an item to the list
|
||||
const addItemResponse = await getRequest()
|
||||
.post(`/api/users/me/shopping-lists/${shoppingListId}/items`)
|
||||
.post(`/api/users/shopping-lists/${shoppingListId}/items`)
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ customItemName: 'Chips' });
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('E2E User Journey', () => {
|
||||
|
||||
// 5. Verify the list and item exist via GET
|
||||
const getListsResponse = await getRequest()
|
||||
.get('/api/users/me/shopping-lists')
|
||||
.get('/api/users/shopping-lists')
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(getListsResponse.status).toBe(200);
|
||||
@@ -94,7 +94,7 @@ describe('E2E User Journey', () => {
|
||||
|
||||
// 6. Delete the User Account (Self-Service)
|
||||
const deleteAccountResponse = await getRequest()
|
||||
.delete('/api/users/me')
|
||||
.delete('/api/users/account')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ password: userPassword });
|
||||
|
||||
|
||||
@@ -21,8 +21,21 @@ let server: Server;
|
||||
let globalPool: ReturnType<typeof getPool> | null = null;
|
||||
// Temporary directory for test file storage (to avoid modifying committed fixtures)
|
||||
let tempStorageDir: string | null = null;
|
||||
// Export the Express app for use with supertest in e2e tests
|
||||
export let app: Express.Application;
|
||||
// Internal app variable - only used within the globalSetup process
|
||||
let app: Express.Application;
|
||||
|
||||
/**
|
||||
* Gets the base URL for the E2E test server.
|
||||
* Tests should make HTTP requests to this URL instead of accessing the app directly.
|
||||
*
|
||||
* NOTE: Due to Vitest's architecture, globalSetup runs in a separate Node.js process
|
||||
* from test files. This means the Express app instance cannot be shared directly.
|
||||
* Instead, tests should connect via HTTP to the server started by globalSetup.
|
||||
*/
|
||||
export function getServerUrl(): string {
|
||||
const port = process.env.TEST_PORT || 3098;
|
||||
return `http://localhost:${port}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans all BullMQ queues to ensure no stale jobs from previous test runs.
|
||||
|
||||
Reference in New Issue
Block a user