This document outlines the comprehensive testing strategy to prevent regressions and ensure code quality.
- Tests MUST NEVER be executed inside the
divemap_backendDocker container.- Rationale: The container points to the live dev database (
dbservice). Test teardown drops all tables, wiping dev data.
- Rationale: The container points to the live dev database (
- Approved methods:
- Quick (Host): run pytest from the host in a Python virtual environment.
- Thorough (Containers): run
backend/docker-test-github-actions.shto execute tests in isolated containers with a separate MySQL instance.
cd backend
source divemap_venv/bin/activate
export PYTHONPATH="/home/kargig/src/divemap/backend/divemap_venv/lib/python3.11/site-packages:$PYTHONPATH"
# Required for OAuth tests
export GOOGLE_CLIENT_ID="dummy-client-id-for-testing"
python -m pytest tests/ -vcd backend
./docker-test-github-actions.sh- Guarantees isolation: separate MySQL container, no shared volumes with
divemap_db.
# Inside app container β forbidden
docker exec -it divemap_backend bash
pytest
python -m pytestNew Features Added:
- System Statistics & Metrics: Comprehensive platform statistics and health monitoring
- Recent Activity Monitoring: Real-time tracking of user actions and system changes
- System Health Endpoints: Backend API endpoints for monitoring system status
- Real-time Activity Tracking: Database queries for user and system activity
New Test Categories Added:
def test_get_system_metrics(self, client, admin_headers):
"""Test system metrics endpoint returns detailed health information."""
def test_get_general_statistics(self, client, admin_headers):
"""Test general statistics endpoint returns comprehensive platform statistics."""
def test_get_platform_stats(self, client, admin_headers):
"""Test platform statistics endpoint returns detailed breakdown."""
def test_get_recent_activity(self, client, admin_headers):
"""Test recent activity endpoint returns user and system activity."""
def test_activity_filtering(self, client, admin_headers):
"""Test activity endpoint supports time and type filtering."""System Health Tests:
def test_system_resources_monitoring(self, client, admin_headers):
"""Test that system health includes CPU, memory, and disk usage."""
def test_database_health_check(self, client, admin_headers):
"""Test database connection status and response time monitoring."""
def test_external_services_status(self, client, admin_headers):
"""Test external services (OAuth, geocoding) status reporting."""Activity Tracking Tests:
def test_user_registration_activity(self, client, admin_headers):
"""Test that user registrations are tracked in activity feed."""
def test_content_creation_activity(self, client, admin_headers):
"""Test that content creation (dive sites, centers, dives) is tracked."""
def test_engagement_activity(self, client, admin_headers):
"""Test that user engagement (comments, ratings) is tracked."""Frontend Integration Tests:
- System Metrics and General Statistics pages loading and data display
- Recent Activity page with filtering and real-time updates
- Admin dashboard navigation and accessibility
- Auto-refresh functionality and manual refresh options
- Responsive design testing on mobile and desktop
API Endpoint Tests:
GET /api/v1/admin/system/statistics- General statistics with platform statsGET /api/v1/admin/system/metrics- System health monitoringGET /api/v1/admin/system/stats- Platform statistics breakdownGET /api/v1/admin/system/activity- Recent activity with filtering
Testing Results:
- Backend Tests: All system monitoring endpoints working correctly β
- Frontend Validation: System Metrics, General Statistics, and Recent Activity pages operational β
- ESLint Compliance: All formatting issues resolved β
- API Testing: All new monitoring endpoints functional β
New Features Added:
- Advanced Trip Search: Full-text search across trip descriptions, special requirements, diving center names, dive site names, and dive descriptions
- Location-Based Filtering: Filter by country, region, and address with intelligent matching
- Duration Filtering: Filter trips by minimum and maximum duration
- Advanced Sorting: Sort by relevance, date, duration, and other criteria
- Client IP Detection: Robust proxy header analysis for accurate client identification in rate limiting
- Enhanced Rate Limiting: Intelligent admin exemptions with client IP validation
New Test Categories Added:
def test_advanced_trip_search(self, client, user_headers):
"""Test full-text search across multiple trip fields."""
def test_location_based_filtering(self, client, user_headers):
"""Test location filtering by country, region, and address."""
def test_duration_filtering(self, client, user_headers):
"""Test trip filtering by minimum and maximum duration."""
def test_advanced_sorting(self, client, user_headers):
"""Test sorting by relevance, date, duration, and other criteria."""
#### Client IP Detection Tests
def test_client_ip_detection_proxy_headers(self, client):
"""Test client IP detection from various proxy headers."""
def test_rate_limiting_with_client_ip(self, client):
"""Test rate limiting with accurate client IP detection."""
def test_admin_rate_limit_exemption(self, client, admin_headers):
"""Test admin rate limit exemptions with client IP validation."""Search Functionality Tests:
def test_search_across_trip_fields(self, client, user_headers):
"""Test search functionality across trip descriptions, requirements, and associated data."""
def test_search_with_location_filtering(self, client, user_headers):
"""Test search combined with location-based filtering."""
def test_search_with_duration_filtering(self, client, user_headers):
"""Test search combined with duration filtering."""
def test_search_sorting_options(self, client, user_headers):
"""Test various sorting options for search results."""Client IP Detection Tests:
def test_x_forwarded_for_header(self, client):
"""Test client IP detection from X-Forwarded-For header."""
def test_x_real_ip_header(self, client):
"""Test client IP detection from X-Real-IP header."""
def test_cf_connecting_ip_header(self, client):
"""Test client IP detection from Cloudflare headers."""
def test_proxy_chain_analysis(self, client):
"""Test intelligent proxy chain analysis for accurate IP detection."""Frontend Integration Tests:
- Advanced search form with multiple filter options
- Search results display with sorting controls
- Location filtering interface
- Duration range selection
- Real-time search results updates
API Endpoint Tests:
GET /api/v1/newsletters/trips- Advanced search with filtering and sortingGET /api/v1/admin/system/statistics- General statistics with client IP detection- Rate limiting endpoints with client IP validation
Testing Results:
- Backend Tests: All advanced search endpoints working correctly β
- Frontend Validation: Advanced search interface operational β
- ESLint Compliance: All formatting issues resolved β
- Client IP Detection: Accurate proxy header analysis β
- Rate Limiting: Intelligent admin exemptions with IP validation β
New Features Added:
- Diving Certification Tracking: Users can set and display their diving certification
- Dive Count Management: Users can track their total number of completed dives
- Secure Password Management: Password change functionality with current password verification
- Comment Credentials Display: User's diving information appears in comments
New Test Categories Added:
def test_update_user_profile_with_diving_info(self, client, user_headers):
"""Test updating user profile with diving certification and dive count."""
def test_change_password_success(self, client, user_headers):
"""Test successful password change with current password verification."""
def test_change_password_invalid_current(self, client, user_headers):
"""Test password change with invalid current password."""
def test_get_user_profile_with_diving_info(self, client, user_headers):
"""Test retrieving user profile with diving information."""Comment Integration Tests:
def test_dive_site_comment_with_user_diving_info(self, client, user_headers):
"""Test that dive site comments include user diving information."""
def test_diving_center_comment_with_user_diving_info(self, client, user_headers):
"""Test that diving center comments include user diving information."""Database Migration Tests:
- Migration
0005_add_user_diving_fields.pysuccessfully applied - New fields
diving_certificationandnumber_of_divesadded to users table - Default values properly set (certification: null, dives: 0)
API Endpoint Tests:
POST /api/v1/users/me/change-password- Password change functionality- Enhanced
PUT /api/v1/users/me- Profile updates with diving fields - Enhanced comment responses - Include user diving information
Frontend Integration Tests:
- Profile page form validation and submission
- Comment display with user credentials badges
- Real-time profile updates in UI
- Password change form with confirmation
Testing Results:
- Backend Tests: 150/150 tests passed β
- Frontend Validation: All systems operational β
- Regression Tests: All tests passed β
- API Testing: All new endpoints working correctly β
New Testing Infrastructure:
- Separated Production and Development Dockerfiles for optimized testing
- Dependency Management Optimization with 82% image size reduction
- Environment-Specific Testing capabilities
Docker Testing Strategy:
docker build -f Dockerfile.dev -t divemap_frontend_dev .
docker run divemap_frontend_dev npm run test:frontend
docker run divemap_frontend_dev npm run test:validation
docker run divemap_frontend_dev npm run test:e2e
#### Production Testing (optimized, no testing tools)
docker build -t divemap_frontend_prod .
docker run -p 8080:8080 divemap_frontend_prod
Dependency Optimization:
- Production Dependencies: React, React Router, React Query, OpenLayers, Tailwind CSS, Axios
- Development Dependencies: Puppeteer (testing only)
- Image Size Reduction: 797MB β 144MB (82% improvement)
Testing Environment Separation:
| Environment | Dependencies | Testing | Size | Use Case |
|---|---|---|---|---|
| Development | All dependencies | β Full suite | ~200MB | Development & testing |
| Production | Production only | β No testing | 144MB | Production deployment |
CI/CD Integration:
- name: Test Frontend
run: |
docker build -f Dockerfile.dev -t divemap_frontend_test .
docker run divemap_frontend_test npm run test:e2e
#### Production deployment
- name: Deploy Frontend
run: |
docker build -t divemap_frontend_prod .
docker push divemap_frontend_prodTesting Results with Docker Optimization:
- Development Tests: All Puppeteer tests working β
- Production Build: Optimized and secure β
- Image Size: 82% reduction achieved β
- Security: Production excludes development tools β
Root Cause: API returns latitude/longitude as strings, but frontend was calling .toFixed() directly.
Solution: Added Number() conversion before .toFixed():
// Before (causing error)
<span>{center.latitude.toFixed(4)}, {center.longitude.toFixed(4)}</span>
// After (fixed)
<span>
{Number(center.latitude).toFixed(4)}, {Number(center.longitude).toFixed(4)}
</span>Files Fixed:
frontend/src/pages/DivingCenters.js- Line 225
Root Cause: AvailableTag model objects were being returned directly instead of being serialized to dictionaries. Solution: Updated tag serialization in dive sites router:
"tags": tags # SQLAlchemy model objects
#### After (fixed)
tags_dict = [
{
"id": tag.id,
"name": tag.name,
"description": tag.description,
"created_by": tag.created_by,
"created_at": tag.created_at.isoformat() if tag.created_at else None
}
for tag in tags
]Files Fixed:
backend/app/routers/dive_sites.py- Enhanced tag serializationbackend/app/routers/diving_centers.py- Enhanced tag serialization
Root Cause: Single Dockerfile included all dependencies, causing large image size and security concerns. Solution: Separated production and development Dockerfiles:
RUN npm ci --only=production --timeout=300000
#### Development Dockerfile (frontend/Dockerfile.dev)
RUN npm ci --timeout=300000Benefits:
- 82% image size reduction (797MB β 144MB)
- Enhanced security (production excludes dev tools)
- Optimized testing (development includes full test suite)
- Better CI/CD integration (environment-specific builds)
New Fields Added:
max_depth: Maximum depth in meters (optional, 0-1000m range)aliases: Alternative names/aliases for dive sites (optional, structured as array of alias objects)
Validation Tests Added:
def test_create_dive_site_with_aliases_admin_success(self, client, admin_headers):
"""Test creating a dive site with max_depth and aliases fields."""
dive_site_data = {
"name": "Test Dive Site with Aliases",
"description": "A test dive site with aliases",
"latitude": 10.0,
"longitude": 20.0,
"max_depth": 25.5,
"aliases": ["Test Site", "Test Location"]
}
response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["max_depth"] == 25.5
assert "aliases" in data
assert isinstance(data["aliases"], list)
#### Test aliases CRUD operations
def test_create_dive_site_alias_admin_success(self, client, admin_headers):
"""Test creating an alias for a dive site."""
# First create a dive site
dive_site_data = {
"name": "Test Dive Site for Aliases",
"description": "A test dive site",
"latitude": 10.0,
"longitude": 20.0
}
create_response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert create_response.status_code == status.HTTP_200_OK
dive_site_id = create_response.json()["id"]
# Create an alias
alias_data = {"alias": "Test Alias"}
alias_response = client.post(f"/api/v1/dive-sites/{dive_site_id}/aliases", json=alias_data, headers=admin_headers)
assert alias_response.status_code == status.HTTP_200_OK
data = alias_response.json()
assert data["alias"] == "Test Alias"
assert data["dive_site_id"] == dive_site_id
def test_get_dive_site_with_aliases(self, client, admin_headers):
"""Test retrieving a dive site with its aliases."""
# Create dive site with aliases
dive_site_data = {
"name": "Test Dive Site with Aliases",
"description": "A test dive site",
"latitude": 10.0,
"longitude": 20.0
}
create_response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert create_response.status_code == status.HTTP_200_OK
dive_site_id = create_response.json()["id"]
# Add aliases
aliases = ["Alias 1", "Alias 2", "Alias 3"]
for alias in aliases:
alias_data = {"alias": alias}
client.post(f"/api/v1/dive-sites/{dive_site_id}/aliases", json=alias_data, headers=admin_headers)
# Get dive site and verify aliases
get_response = client.get(f"/api/v1/dive-sites/{dive_site_id}", headers=admin_headers)
assert get_response.status_code == status.HTTP_200_OK
data = get_response.json()
assert "aliases" in data
assert len(data["aliases"]) == 3
alias_names = [alias["alias"] for alias in data["aliases"]]
assert all(alias in alias_names for alias in aliases)
#### Test newsletter parsing with aliases
def test_newsletter_parsing_with_aliases(self, client, admin_headers):
"""Test that newsletter parsing correctly matches dive sites using aliases."""
# Create dive site with aliases
dive_site_data = {
"name": "Great Barrier Reef",
"description": "A famous dive site",
"latitude": -16.5,
"longitude": 145.67
}
create_response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert create_response.status_code == status.HTTP_200_OK
dive_site_id = create_response.json()["id"]
# Add aliases that might appear in newsletters
aliases = ["GBR", "The Reef", "Great Barrier"]
for alias in aliases:
alias_data = {"alias": alias}
client.post(f"/api/v1/dive-sites/{dive_site_id}/aliases", json=alias_data, headers=admin_headers)
# Test newsletter parsing with alias matching
newsletter_content = "Join us for a dive trip to GBR this weekend!"
newsletter_data = {"content": newsletter_content}
parse_response = client.post("/api/v1/newsletters/", json=newsletter_data, headers=admin_headers)
assert parse_response.status_code == status.HTTP_200_OK
# Verify that the dive site was matched using the alias
data = parse_response.json()
assert "dive_sites" in data
assert len(data["dive_sites"]) > 0
matched_site = data["dive_sites"][0]
assert matched_site["name"] == "Great Barrier Reef"
#### Test frontend aliases functionality
def test_frontend_aliases_display(self, client, admin_headers):
"""Test that aliases are properly displayed in the frontend."""
# Create dive site with aliases
dive_site_data = {
"name": "Test Dive Site",
"description": "A test dive site",
"latitude": 10.0,
"longitude": 20.0
}
create_response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert create_response.status_code == status.HTTP_200_OK
dive_site_id = create_response.json()["id"]
# Add aliases
aliases = ["Test Alias 1", "Test Alias 2"]
for alias in aliases:
alias_data = {"alias": alias}
client.post(f"/api/v1/dive-sites/{dive_site_id}/aliases", json=alias_data, headers=admin_headers)
# Get dive site and verify aliases are included
get_response = client.get(f"/api/v1/dive-sites/{dive_site_id}")
assert get_response.status_code == status.HTTP_200_OK
data = get_response.json()
assert "aliases" in data
assert len(data["aliases"]) == 2
# Verify alias structure
for alias in data["aliases"]:
assert "id" in alias
assert "dive_site_id" in alias
assert "alias" in alias
assert "created_at" in alias
assert alias["dive_site_id"] == dive_site_id
#### Test admin aliases management
def test_admin_aliases_management(self, client, admin_headers):
"""Test admin functionality for managing dive site aliases."""
# Create dive site
dive_site_data = {
"name": "Admin Test Site",
"description": "A test dive site for admin testing",
"latitude": 10.0,
"longitude": 20.0
}
create_response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert create_response.status_code == status.HTTP_200_OK
dive_site_id = create_response.json()["id"]
# Test creating aliases
aliases_to_create = ["Admin Alias 1", "Admin Alias 2", "Admin Alias 3"]
created_aliases = []
for alias_name in aliases_to_create:
alias_data = {"alias": alias_name}
create_alias_response = client.post(f"/api/v1/dive-sites/{dive_site_id}/aliases", json=alias_data, headers=admin_headers)
assert create_alias_response.status_code == status.HTTP_200_OK
created_aliases.append(create_alias_response.json())
# Test updating aliases
first_alias = created_aliases[0]
update_data = {"alias": "Updated Admin Alias"}
update_response = client.put(f"/api/v1/dive-sites/{dive_site_id}/aliases/{first_alias['id']}", json=update_data, headers=admin_headers)
assert update_response.status_code == status.HTTP_200_OK
assert update_response.json()["alias"] == "Updated Admin Alias"
# Test deleting aliases
second_alias = created_aliases[1]
delete_response = client.delete(f"/api/v1/dive-sites/{dive_site_id}/aliases/{second_alias['id']}", headers=admin_headers)
assert delete_response.status_code == status.HTTP_200_OK
# Verify final state
get_response = client.get(f"/api/v1/dive-sites/{dive_site_id}", headers=admin_headers)
assert get_response.status_code == status.HTTP_200_OK
data = get_response.json()
assert len(data["aliases"]) == 2 # One updated, one deleted, one remaining
alias_names = [alias["alias"] for alias in data["aliases"]]
assert "Updated Admin Alias" in alias_names
assert "Admin Alias 3" in alias_names
assert "Admin Alias 2" not in alias_names # Should be deleted
#### Test empty string handling for optional fields
def test_update_dive_site_with_empty_max_depth_admin_success(self, client, admin_headers):
"""Test updating a dive site with empty max_depth field."""
# Create dive site with max_depth
dive_site_data = {
"name": "Test Dive Site for Empty Max Depth",
"description": "A test dive site",
"latitude": 10.0,
"longitude": 20.0,
"max_depth": 25.5
}
create_response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert create_response.status_code == status.HTTP_200_OK
dive_site_id = create_response.json()["id"]
# Update with empty max_depth
update_data = {
"name": "Updated Test Dive Site",
"description": "Updated description",
"max_depth": "" # Empty string sent from frontend
}
update_response = client.put(f"/api/v1/dive-sites/{dive_site_id}", json=update_data, headers=admin_headers)
assert update_response.status_code == status.HTTP_200_OK
data = update_response.json()
assert data["max_depth"] is None # Should be null when empty string is sent
#### Test mandatory field validation
def test_update_dive_site_with_null_latitude_rejected(self, client, admin_headers):
"""Test that updating a dive site with null latitude is rejected."""
# Create dive site
dive_site_data = {
"name": "Test Dive Site for Null Latitude",
"description": "A test dive site",
"latitude": 10.0,
"longitude": 20.0
}
create_response = client.post("/api/v1/dive-sites/", json=dive_site_data, headers=admin_headers)
assert create_response.status_code == status.HTTP_200_OK
dive_site_id = create_response.json()["id"]
# Try to update with null latitude
update_data = {
"name": "Updated Test Dive Site",
"latitude": None
}
update_response = client.put(f"/api/v1/dive-sites/{dive_site_id}", json=update_data, headers=admin_headers)
assert update_response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert "Latitude cannot be empty" in update_response.json()["detail"]Validation Improvements:
- Mandatory Fields: Latitude and longitude are now properly enforced as required
- Empty String Handling: Backend converts empty strings to
nullfor optional numeric fields - Client-side Validation: Frontend prevents submission with empty required fields
- Error Messages: Clear feedback for validation failures
Files Modified:
backend/tests/test_dive_sites.py- Added comprehensive validation testsbackend/app/schemas.py- Added Pydantic validator for empty string handlingbackend/app/routers/dive_sites.py- Added server-side validation for mandatory fieldsfrontend/src/pages/EditDiveSite.js- Added client-side validationfrontend/src/pages/CreateDiveSite.js- Added client-side validation
Root Cause: Admin pages had "Add" buttons that navigated to /admin/dive-sites/create and /admin/diving-centers/create, but these routes didn't exist in React Router.
Solution: Created comprehensive create forms and added proper routes:
// Added routes in App.js
<Route path="/admin/dive-sites/create" element={<CreateDiveSite />} />
<Route path="/admin/diving-centers/create" element={<CreateDivingCenter />} />Features Implemented:
- CreateDiveSite.js: Comprehensive form with all dive site fields
- CreateDivingCenter.js: Comprehensive form with all diving center fields
- Form Validation: Client-side validation with required fields
- API Integration: Proper POST requests with error handling
- User Experience: Responsive design with proper navigation
Files Created/Fixed:
frontend/src/pages/CreateDiveSite.js- New comprehensive create formfrontend/src/pages/CreateDivingCenter.js- New comprehensive create formfrontend/src/App.js- Added routes and imports
Root Cause: Database had dive sites with 'expert' difficulty level, but schema only allowed 'beginner', 'intermediate', 'advanced'. Solution: Updated all difficulty level patterns to include 'expert':
difficulty_level: Optional[str] = Field(None, pattern=r"^(beginner|intermediate|advanced)$")
#### After
difficulty_level: Optional[str] = Field(None, pattern=r"^(beginner|intermediate|advanced|expert)$")Files Fixed:
backend/app/schemas.py- DiveSiteBase, DiveSiteUpdate, DiveSiteSearchParamsbackend/app/routers/dive_sites.py- Query parameter validation
- Purpose: Ensure database migrations work correctly
- Usage:
python run_migrations.py - Environment: Must use virtual environment with proper PYTHONPATH
Migration Test Commands:
cd backend
source divemap_venv/bin/activate
export PYTHONPATH="/home/kargig/src/divemap/backend/divemap_venv/lib/python3.11/site-packages:$PYTHONPATH"
#### Run migrations
python run_migrations.py
#### Create test migration
python create_migration.py "Test migration"
#### Check migration status
alembic current
alembic historyMigration Testing Checklist:
- Virtual environment activated
- PYTHONPATH set correctly
- Database connection working
- Migrations run without errors
- Rollback functionality works
- Auto-generation creates valid migrations
- Purpose: Basic connectivity and API health checks
- Usage:
node validate_frontend.js - Tests:
- Frontend accessibility
- Backend API endpoints
- Data type validation
- Purpose: Comprehensive regression testing
- Usage:
node test_regressions.js - Tests:
- Data type validation (lat/lng as strings)
- Numeric conversion safety
- API endpoint functionality
- Common frontend issues
// Test that API returns expected types
if (typeof center.latitude !== 'string') {
throw new Error(`Expected latitude to be string, got ${typeof center.latitude}`);
}
// Test numeric conversion
const latNum = Number(center.latitude);
if (isNaN(latNum)) {
throw new Error(`Cannot convert latitude "${center.latitude}" to number`);
}- All CRUD endpoints
- Media endpoints
- Gear rental endpoints
- Authentication endpoints
- Type conversion safety
- Null/undefined checks
- Array safety checks
- Automated testing for common frontend errors
- Data type safety validation
- API parameter filtering testing
- Cross-browser compatibility testing
- Tag management system testing
- Bulk operations testing (tag selection, form submission)
- Tag display in dive site details
- Multiple tag selection in edit forms
- Tag creation and assignment
- Bulk tag operations (add/remove)
- Tag validation and error handling
- Tag state management and persistence
- Zoom level tracking and display
- Map counter positioning and content
- Zoom behavior for single vs multiple site selection
- Maximum zoom level configuration
- Map fit behavior and constraints
- Real-time zoom level updates
- Run
node validate_frontend.js - Run
node test_regressions.js - Test all affected pages manually
- Check browser console for errors
- Latitude/Longitude: Always strings from API, convert with
Number() - Ratings: Can be
nullornumber - Arrays: Check
Array.isArray()before.map()
- Empty Parameters: Filter out empty strings before API calls
- Trailing Slashes: Some endpoints require trailing slashes
- Authentication: Check token presence for protected routes
- Rating Display: Use numeric format
X.X/10instead of stars - Coordinate Display: Convert strings to numbers before formatting
- Loading States: Show spinners during API calls
- Run existing tests:
node test_regressions.js - Note current state: Document what's working
- Plan changes: Identify potential impact areas
- Run validation:
node validate_frontend.js - Run regression tests:
node test_regressions.js - Manual testing: Visit affected pages
- Check console: Look for JavaScript errors
// β Risky - assumes number
value.toFixed(2)
// β
Safe - handles strings
Number(value).toFixed(2)
// β Risky - assumes array
items.map(item => ...)
// β
Safe - checks array
Array.isArray(items) && items.map(item => ...)// β Risky - sends empty strings
api.get('/endpoint', { params: { name: '', rating: '' } })
// β
Safe - filters empty values
const filteredParams = Object.fromEntries(
Object.entries(params).filter(([_, v]) => v !== '')
)- NEVER install Python packages in system Python
- ALWAYS use virtual environments for Python development
- ALWAYS activate virtual environment before installing packages
- NEVER install npm packages globally
- ALWAYS use project-specific node_modules
cd backend
source divemap_venv/bin/activate
export PYTHONPATH="/home/kargig/src/divemap/backend/divemap_venv/lib/python3.11/site-packages:$PYTHONPATH"
pip install package_name
#### β WRONG - Never install in system Python
pip install package_name # This is forbiddennpm install package_name
#### β WRONG - Never install globally
npm install -g package_name # This is forbiddencd backend
#### Activate virtual environment (if using venv)
source divemap_venv/bin/activate
#### Set PYTHONPATH for virtual environment (if needed)
export PYTHONPATH="/home/kargig/src/divemap/backend/divemap_venv/lib/python3.11/site-packages:$PYTHONPATH"
#### Run all tests with verbose output
python -m pytest tests/ -v
#### Run specific test file
python -m pytest tests/test_dive_sites.py -v
#### Run tests with coverage
python -m pytest tests/ --cov=app --cov-report=htmlpython -m pytest tests/test_auth.py -v
#### Dive sites tests
python -m pytest tests/test_dive_sites.py -v
#### Diving centers tests
python -m pytest tests/test_diving_centers.py -v
#### Tags tests
python -m pytest tests/test_tags.py -v
#### Users tests
python -m pytest tests/test_users.py -v
#### Migration tests
python run_migrations.py
alembic current
alembic historyFrontend tests should be run from the system (not Docker containers) for best compatibility.
- Google Chrome installed on the system (
/usr/bin/google-chrome) - Node.js and npm installed
- Frontend dependencies installed (
npm install)
cd frontend
#### Run regression tests (API-focused, no browser required)
node tests/test_regressions.jscd frontend
#### Run regression tests (API and data type validation)
node tests/test_regressions.js
#### Run validation tests (browser automation - requires Chrome)
node tests/validate_frontend.js
#### Run frontend tests (browser automation - requires Chrome)
node tests/test_frontend.js- Purpose: API connectivity and data type validation
- Requirements: No browser needed
- Usage:
node tests/test_regressions.js - Tests:
- Data type validation (lat/lng as strings)
- API endpoint functionality
- Common frontend issues
- Backend connectivity
- Purpose: Frontend accessibility and user interface testing
- Requirements: Google Chrome browser
- Usage:
node tests/validate_frontend.js - Tests:
- Page navigation
- Form functionality
- Responsive design
- Accessibility checks
- Purpose: Comprehensive frontend functionality testing
- Requirements: Google Chrome browser
- Usage:
node tests/test_frontend.js - Tests:
- All page loading
- User interactions
- Error handling
- Admin functionality
sed -i 's/page\.waitForTimeout(/await new Promise(resolve => setTimeout(resolve, /g' tests/*.jssudo apt-get update && sudo apt-get install -y google-chrome-stable
#### Or use Chromium
sudo apt-get install -y chromium-browserdocker-compose ps
#### Check backend connectivity
curl -s http://localhost:8000/healthcd frontend
node tests/test_regressions.js-
Data Type Tests: Should show 2/2 passed
-
API Endpoints: Should show 4/6 working (404s for non-existent IDs are normal)
-
Expected Output:
π Data Type Tests: 2/2 passed π API Endpoints: 4/6 working
- Only run if Chrome is available
- May have navigation issues in headless mode
- Focus on regression tests for core functionality
cd frontend
node tests/test_regressions.js
#### β AVOID - Running from Docker container
docker exec divemap_frontend node tests/test_regressions.jsdocker-compose ps
#### View container logs
docker-compose logs backend
docker-compose logs frontenddocker-compose restart
#### Restart specific service
docker-compose restart backend
docker-compose restart frontendcurl -s http://localhost:8000/api/v1/dive-sites/ | jq 'length'
#### Test diving centers endpoint
curl -s http://localhost:8000/api/v1/diving-centers/ | jq 'length'
#### Test specific dive site
curl -s http://localhost:8000/api/v1/dive-sites/1 | jq '.name'
#### Test authentication (Personal Access Token)
curl -s -X GET http://localhost:8000/api/v1/auth/me \
-H "Authorization: Bearer dm_pat_your_token_here" | jq .docker-compose up -d
#### 2. Wait for services to be ready
sleep 10
#### 3. Run backend tests (in Docker container)
cd backend
source divemap_venv/bin/activate
export PYTHONPATH="/home/kargig/src/divemap/backend/divemap_venv/lib/python3.11/site-packages:$PYTHONPATH"
python -m pytest tests/ -v
#### 4. Run frontend tests (from system)
cd ../frontend
node tests/test_regressions.js
#### 5. Optional: Run browser tests (requires Chrome)
node tests/validate_frontend.js
node tests/test_frontend.js
#### 6. Check container status
cd ..
docker-compose ps#!/bin/bash
cat > run_all_tests.sh << 'EOF'
echo "π Running Complete Test Suite..."
#### Check if services are running
if ! docker-compose ps | grep -q "Up"; then
echo "π¦ Starting services..."
docker-compose up -d
sleep 10
fi
#### Run backend tests
echo "π§ͺ Running backend tests..."
cd backend
source divemap_venv/bin/activate
export PYTHONPATH="/home/kargig/src/divemap/backend/divemap_venv/lib/python3.11/site-packages:$PYTHONPATH"
python -m pytest tests/ -v
BACKEND_RESULT=$?
#### Run frontend tests
echo "π¨ Running frontend tests..."
cd ../frontend
node tests/test_regressions.js
FRONTEND_RESULT=$?
#### Optional: Run browser tests if Chrome is available
if command -v google-chrome &> /dev/null; then
echo "π Running browser tests..."
node tests/validate_frontend.js
BROWSER_RESULT=$?
else
echo "β οΈ Chrome not found, skipping browser tests"
BROWSER_RESULT=0
fi
#### Summary
echo ""
echo "π Test Results Summary:"
echo "Backend Tests: $([ $BACKEND_RESULT -eq 0 ] && echo "β
PASSED" || echo "β FAILED")"
echo "Frontend Regression Tests: $([ $FRONTEND_RESULT -eq 0 ] && echo "β
PASSED" || echo "β FAILED")"
echo "Browser Tests: $([ $BROWSER_RESULT -eq 0 ] && echo "β
PASSED" || echo "β FAILED")"
if [ $BACKEND_RESULT -eq 0 ] && [ $FRONTEND_RESULT -eq 0 ] && [ $BROWSER_RESULT -eq 0 ]; then
echo "π ALL TESTS PASSED!"
exit 0
else
echo "β οΈ Some tests failed. Check output above."
exit 1
fi
EOF
chmod +x run_all_tests.sh
#### Run all tests
./run_all_tests.sh- Home page loads
- Dive sites page loads
- Diving centers page loads
- Detail pages load without errors
- Edit pages work for admin users
- Create pages work for admin users (new)
- Search functionality works
- No console errors
- Admin login works (admin/ADMIN_PASSWORD)
- API endpoints return expected data types
Expected Output:
=============================== warnings summary ===============================
================= 228 passed, 72 warnings in 280.70s (0:04:40) =================
What This Means:
- β All 228 backend tests passed
β οΈ 72 warnings (mostly deprecation warnings, not critical)- β±οΈ Tests completed in ~4.5 minutes
Expected Output:
π Data Type Tests: 2/2 passed
π API Endpoints: 4/6 working
π Regression Test Summary:
Data Types: β
PASSED
API Endpoints: β FAILED
What This Means:
- β Data Type Tests: 2/2 passed - All data type validations working correctly
- β API Endpoints: 4/6 working - Core endpoints working, 404s are normal for non-existent IDs
- β "FAILED" status is misleading - The 404 errors are expected behavior
Normal 404 Errors:
/api/v1/diving-centers/1- Returns 404 (diving center ID 1 doesn't exist)/api/v1/diving-centers/1/gear-rental- Returns 404 (same reason)
Expected Issues:
- Puppeteer API Compatibility: May need to update
waitForTimeoutcalls - Navigation Errors: Browser tests may fail due to navigation timing
- Chrome Requirements: Tests require Google Chrome to be installed
Troubleshooting:
sed -i 's/page\.waitForTimeout(/await new Promise(resolve => setTimeout(resolve, /g' tests/*.js
#### Install Chrome if missing
sudo apt-get install -y google-chrome-stableβ SUCCESSFUL TESTS:
- Backend API Tests: All 228 tests passed
- Data Type Validation: All data types correctly validated
- Core API Endpoints: All main endpoints working
- Database Integration: All database operations working
- Frontend Browser Tests: Puppeteer API compatibility issues
- Expected 404s: Some diving center endpoints return 404 (expected behavior)
π― KEY FINDINGS:
- Backend is fully functional and tested
- API data types are correctly handled
- Core functionality is working as expected
- The 404 errors for non-existent diving centers are normal behavior
- Puppeteer Integration: Full browser testing
- Visual Regression: Screenshot comparison
- Performance Testing: Load time monitoring
- TypeScript: Static type checking
- PropTypes: React component validation
- ESLint Rules: Enforce best practices
- Git Hooks: Pre-commit testing
- CI/CD Pipeline: Automated testing on push
- Deployment Validation: Post-deploy health checks
// Always convert API strings to numbers when needed
const lat = Number(center.latitude);
const lng = Number(center.longitude);
// Check for valid numbers
if (!isNaN(lat) && !isNaN(lng)) {
// Safe to use lat/lng
}// Always check if array before mapping
if (Array.isArray(items)) {
items.map(item => ...)
}// Filter out empty parameters
const cleanParams = Object.fromEntries(
Object.entries(params).filter(([_, v]) => v !== '')
);// Wrap components in error boundaries
<ErrorBoundary>
<Component />
</ErrorBoundary>- NEVER commit to git automatically
- ONLY commit when explicitly requested by the user
- ALWAYS ask for permission before committing
- ALWAYS provide clear commit messages when requested
#### β WRONG - Never commit automatically
git add . && git commit -m "auto commit" # This is forbidden- Use descriptive commit messages
- Include affected files in commit message
- Reference issue numbers if applicable
- Test changes before committing
- Console Errors: Monitor browser console
- API Errors: Track 4xx/5xx responses
- User Reports: Collect user feedback
- Page Load Times: Track loading performance
- API Response Times: Monitor backend performance
- User Experience: Track user interactions
This testing strategy ensures that:
- Regressions are caught early through automated testing
- Common issues are prevented through guidelines
- Code quality is maintained through validation
- User experience is protected through comprehensive testing
Remember: Always run tests before and after making changes to prevent regressions!