Skip to content

Latest commit

Β 

History

History
1539 lines (1089 loc) Β· 44.8 KB

File metadata and controls

1539 lines (1089 loc) Β· 44.8 KB

Testing Strategy for Divemap

Overview

This document outlines the comprehensive testing strategy to prevent regressions and ensure code quality.

0. Testing Policy: Where tests MUST run

  • Tests MUST NEVER be executed inside the divemap_backend Docker container.
    • Rationale: The container points to the live dev database (db service). Test teardown drops all tables, wiping dev data.
  • Approved methods:
    • Quick (Host): run pytest from the host in a Python virtual environment.
    • Thorough (Containers): run backend/docker-test-github-actions.sh to execute tests in isolated containers with a separate MySQL instance.

Quick (Host) – Backend

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/ -v

Thorough (Containers) – Backend

cd backend
./docker-test-github-actions.sh
  • Guarantees isolation: separate MySQL container, no shared volumes with divemap_db.

DO NOT (forbidden)

# Inside app container – forbidden
docker exec -it divemap_backend bash
pytest
python -m pytest

1. Latest Features Added

βœ… Added: Admin Dashboard System Monitoring Testing

New 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:

System Monitoring Tests

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:

Test system resource monitoring

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:

Test activity data collection

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 stats
  • GET /api/v1/admin/system/metrics - System health monitoring
  • GET /api/v1/admin/system/stats - Platform statistics breakdown
  • GET /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 βœ…

βœ… Added: Advanced Search and Client IP Detection Testing

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:

Advanced Search Tests

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:

Test search across multiple fields

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:

Test proxy header handling

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 sorting
  • GET /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 βœ…

βœ… Added: Enhanced User Profile Management Testing

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:

User Profile Management Tests

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:

Test comment responses include user diving information

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.py successfully applied
  • New fields diving_certification and number_of_dives added 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 βœ…

βœ… Added: Docker-Based Testing Optimization

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:

Development Testing (with full test suite)

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:

Development testing

- 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_prod

Testing 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 βœ…

2. Immediate Fixes Applied

βœ… Fixed: center.latitude.toFixed is not a function Error

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

βœ… Fixed: Dive Sites API Serialization Issues

Root Cause: AvailableTag model objects were being returned directly instead of being serialized to dictionaries. Solution: Updated tag serialization in dive sites router:

Before (causing 500 errors)

"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 serialization
  • backend/app/routers/diving_centers.py - Enhanced tag serialization

βœ… Fixed: Docker Testing Infrastructure

Root Cause: Single Dockerfile included all dependencies, causing large image size and security concerns. Solution: Separated production and development Dockerfiles:

Production Dockerfile (frontend/Dockerfile)

RUN npm ci --only=production --timeout=300000

#### Development Dockerfile (frontend/Dockerfile.dev)

RUN npm ci --timeout=300000

Benefits:

  • 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)

βœ… Added: Dive Site Field Validation Testing

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:

Test new fields creation

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 null for 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 tests
  • backend/app/schemas.py - Added Pydantic validator for empty string handling
  • backend/app/routers/dive_sites.py - Added server-side validation for mandatory fields
  • frontend/src/pages/EditDiveSite.js - Added client-side validation
  • frontend/src/pages/CreateDiveSite.js - Added client-side validation

βœ… Fixed: Frontend Create Pages

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 form
  • frontend/src/pages/CreateDivingCenter.js - New comprehensive create form
  • frontend/src/App.js - Added routes and imports

βœ… Fixed: Difficulty Level Validation

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':

Before

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, DiveSiteSearchParams
  • backend/app/routers/dive_sites.py - Query parameter validation

2. Testing Infrastructure

2.1 Database Migration Testing

Alembic Migration Testing

  • Purpose: Ensure database migrations work correctly
  • Usage: python run_migrations.py
  • Environment: Must use virtual environment with proper PYTHONPATH

Migration Test Commands:

Test migration system

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 history

Migration Testing Checklist:

  • Virtual environment activated
  • PYTHONPATH set correctly
  • Database connection working
  • Migrations run without errors
  • Rollback functionality works
  • Auto-generation creates valid migrations

2.2 Validation Scripts

validate_frontend.js

  • Purpose: Basic connectivity and API health checks
  • Usage: node validate_frontend.js
  • Tests:
    • Frontend accessibility
    • Backend API endpoints
    • Data type validation

test_regressions.js

  • 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

2.2 Test Categories

A. Data Type Testing

// 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`);
}

B. API Endpoint Testing

  • All CRUD endpoints
  • Media endpoints
  • Gear rental endpoints
  • Authentication endpoints

C. Frontend Error Prevention

  • Type conversion safety
  • Null/undefined checks
  • Array safety checks

C. Regression Prevention

  • 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)

D. Tag Management Testing

  • 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

E. Map UI Testing

  • 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

3. Pre-Release Checklist

3.1 Code Changes

  • Run node validate_frontend.js
  • Run node test_regressions.js
  • Test all affected pages manually
  • Check browser console for errors

3.2 Common Issues to Check

Data Type Issues

  • Latitude/Longitude: Always strings from API, convert with Number()
  • Ratings: Can be null or number
  • Arrays: Check Array.isArray() before .map()

API Parameter Issues

  • Empty Parameters: Filter out empty strings before API calls
  • Trailing Slashes: Some endpoints require trailing slashes
  • Authentication: Check token presence for protected routes

Frontend Display Issues

  • Rating Display: Use numeric format X.X/10 instead of stars
  • Coordinate Display: Convert strings to numbers before formatting
  • Loading States: Show spinners during API calls

4. Regression Prevention

4.1 Before Making Changes

  1. Run existing tests: node test_regressions.js
  2. Note current state: Document what's working
  3. Plan changes: Identify potential impact areas

4.2 After Making Changes

  1. Run validation: node validate_frontend.js
  2. Run regression tests: node test_regressions.js
  3. Manual testing: Visit affected pages
  4. Check console: Look for JavaScript errors

4.3 Common Regression Points

Frontend Data Handling

// ❌ 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 => ...)

API Calls

// ❌ Risky - sends empty strings
api.get('/endpoint', { params: { name: '', rating: '' } })
// βœ… Safe - filters empty values
const filteredParams = Object.fromEntries(
  Object.entries(params).filter(([_, v]) => v !== '')
)

5. Environment Requirements

5.1 Virtual Environment Requirements

  • 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

5.2 Python Environment Setup

βœ… CORRECT - Always use virtual environment

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 forbidden

5.3 Node.js Environment Setup

βœ… CORRECT - Use local node_modules

npm install package_name

#### ❌ WRONG - Never install globally

npm install -g package_name  # This is forbidden

6. Testing Commands

6.1 Backend Testing

Run All Backend Tests

Navigate to backend directory

cd 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=html

Backend Test Categories

Authentication tests

python -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 history

5.2 Frontend Testing

Frontend Test Environment Setup

IMPORTANT

Frontend tests should be run from the system (not Docker containers) for best compatibility.

Prerequisites

  • Google Chrome installed on the system (/usr/bin/google-chrome)
  • Node.js and npm installed
  • Frontend dependencies installed (npm install)

Quick Health Check (Recommended)

Navigate to frontend directory

cd frontend

#### Run regression tests (API-focused, no browser required)

node tests/test_regressions.js

Full Frontend Test Suite

Navigate to frontend directory

cd 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

Frontend Test Categories

A. Regression Tests (Recommended)

  • 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

B. Validation Tests (Optional)

  • 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

C. Frontend Tests (Optional)

  • 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

Frontend Test Troubleshooting

Puppeteer Issues

If tests fail with "waitForTimeout is not a function"

This is a Puppeteer API compatibility issue

Fix by updating test files

sed -i 's/page\.waitForTimeout(/await new Promise(resolve => setTimeout(resolve, /g' tests/*.js

Chrome Not Found

Install Chrome if not available

sudo apt-get update && sudo apt-get install -y google-chrome-stable

#### Or use Chromium

sudo apt-get install -y chromium-browser

Network Issues

Ensure backend is running

docker-compose ps

#### Check backend connectivity

curl -s http://localhost:8000/health

Frontend Test Best Practices

1. Run Regression Tests First

Always start with regression tests (fastest, most reliable)

cd frontend
node tests/test_regressions.js

2. Check Expected Results

  • 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
    

3. Browser Tests (Optional)

  • Only run if Chrome is available
  • May have navigation issues in headless mode
  • Focus on regression tests for core functionality

4. Test Environment

βœ… CORRECT - Run from system with Chrome

cd frontend
node tests/test_regressions.js

#### ❌ AVOID - Running from Docker container

docker exec divemap_frontend node tests/test_regressions.js

5.3 Docker Testing

Check Container Status

Check if all containers are running

docker-compose ps

#### View container logs

docker-compose logs backend
docker-compose logs frontend

Restart Services (if needed)

Restart all services

docker-compose restart

#### Restart specific service

docker-compose restart backend
docker-compose restart frontend

5.4 API Testing

Manual API Testing

Test dive sites endpoint

curl -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 .

5.5 Complete Test Suite

Run All Tests (Recommended)

1. Start services

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

One-Command Test Runner

Create a test runner script

#!/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

5.6 Manual Testing Checklist

  • 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

6. Expected Test Results

6.1 Backend Test Results

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

6.2 Frontend Regression Test Results

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)

6.3 Frontend Browser Test Results

Expected Issues:

  • Puppeteer API Compatibility: May need to update waitForTimeout calls
  • Navigation Errors: Browser tests may fail due to navigation timing
  • Chrome Requirements: Tests require Google Chrome to be installed

Troubleshooting:

Fix Puppeteer compatibility issues

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

6.4 Overall Assessment

βœ… 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

⚠️ MINOR ISSUES:

  • 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

7. Future Improvements

7.1 Automated Testing

  • Puppeteer Integration: Full browser testing
  • Visual Regression: Screenshot comparison
  • Performance Testing: Load time monitoring

6.2 Enhanced Validation

  • TypeScript: Static type checking
  • PropTypes: React component validation
  • ESLint Rules: Enforce best practices

6.3 Continuous Integration

  • Git Hooks: Pre-commit testing
  • CI/CD Pipeline: Automated testing on push
  • Deployment Validation: Post-deploy health checks

7. Error Prevention Guidelines

7.1 Data Type Safety

// 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
}

7.2 Array Safety

// Always check if array before mapping
if (Array.isArray(items)) {
  items.map(item => ...)
}

7.3 API Parameter Safety

// Filter out empty parameters
const cleanParams = Object.fromEntries(
  Object.entries(params).filter(([_, v]) => v !== '')
);

7.4 Error Boundaries

// Wrap components in error boundaries
<ErrorBoundary>
  <Component />
</ErrorBoundary>

8. Git Commit Rules

8.1 Git Commit Requirements

  • 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

8.2 Git Workflow

βœ… CORRECT - Only commit when user requests

Wait for user to say: "commit these changes"

#### ❌ WRONG - Never commit automatically

git add . && git commit -m "auto commit"  # This is forbidden

8.3 Commit Guidelines (when user requests)

  • Use descriptive commit messages
  • Include affected files in commit message
  • Reference issue numbers if applicable
  • Test changes before committing

9. Monitoring and Alerting

9.1 Error Tracking

  • Console Errors: Monitor browser console
  • API Errors: Track 4xx/5xx responses
  • User Reports: Collect user feedback

8.2 Performance Monitoring

  • Page Load Times: Track loading performance
  • API Response Times: Monitor backend performance
  • User Experience: Track user interactions

Conclusion

This testing strategy ensures that:

  1. Regressions are caught early through automated testing
  2. Common issues are prevented through guidelines
  3. Code quality is maintained through validation
  4. User experience is protected through comprehensive testing

Remember: Always run tests before and after making changes to prevent regressions!