From 71901c77d4753ed38bed324c43d7cef41527eb3d Mon Sep 17 00:00:00 2001 From: immortal71 Date: Fri, 23 Jan 2026 05:31:22 -0800 Subject: [PATCH 1/2] feat: Add smoke tests for copi.owasp.org and cornucopia.owasp.org (fixes #1265) --- .github/workflows/run-tests.yaml | 2 + .github/workflows/smoke-tests.yaml | 62 ++++++++++ tests/scripts/README.md | 99 ++++++++++++++++ tests/scripts/smoke_tests.py | 175 +++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+) create mode 100644 .github/workflows/smoke-tests.yaml create mode 100644 tests/scripts/README.md create mode 100644 tests/scripts/smoke_tests.py diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index bc69d71d6..4fadea1bc 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -32,6 +32,8 @@ jobs: run: pipenv run python -m unittest discover -s "tests/scripts" -p "*_utest.py" - name: Run integration run: pipenv run python -m unittest discover -s "tests/scripts" -p "*_itest.py" + - name: Run smoke tests + run: pipenv run python -m unittest tests.scripts.smoke_tests -v # Test coverage reports - name: Check test coverage - run tests run: pipenv run coverage run -m unittest discover -s "tests/scripts" -p "*_*test.py" diff --git a/.github/workflows/smoke-tests.yaml b/.github/workflows/smoke-tests.yaml new file mode 100644 index 000000000..9cd867ee2 --- /dev/null +++ b/.github/workflows/smoke-tests.yaml @@ -0,0 +1,62 @@ +name: Smoke Tests +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * *' + push: + branches: + - master + paths: + - 'copi.owasp.org/**' + - 'cornucopia.owasp.org/**' + - 'tests/scripts/smoke_tests.py' + - '.github/workflows/smoke-tests.yaml' + +permissions: + contents: read + +jobs: + hardening: + name: Harden runner + uses: ./.github/workflows/hardening.yaml + + smoke-tests: + name: Run Smoke Tests + needs: hardening + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Get Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.11' + + - name: Install Python dependencies + run: pip install -r requirements.txt --require-hashes + + - name: Run smoke tests for copi.owasp.org + run: python -m unittest tests.scripts.smoke_tests.CopiSmokeTests -v + continue-on-error: false + + - name: Run smoke tests for cornucopia.owasp.org + run: python -m unittest tests.scripts.smoke_tests.CornucopiaSmokeTests -v + continue-on-error: false + + - name: Run integration smoke tests + run: python -m unittest tests.scripts.smoke_tests.IntegrationSmokeTests -v + continue-on-error: false + + - name: Summary + if: always() + run: | + echo "## Smoke Test Results" >> $GITHUB_STEP_SUMMARY + echo "Smoke tests completed for:" >> $GITHUB_STEP_SUMMARY + echo "- copi.owasp.org (Elixir/Phoenix application)" >> $GITHUB_STEP_SUMMARY + echo "- cornucopia.owasp.org (SvelteKit application)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Tests verify:" >> $GITHUB_STEP_SUMMARY + echo "- At least 2 routes working on each application" >> $GITHUB_STEP_SUMMARY + echo "- JavaScript is loading and functional" >> $GITHUB_STEP_SUMMARY + echo "- Applications respond within acceptable time" >> $GITHUB_STEP_SUMMARY diff --git a/tests/scripts/README.md b/tests/scripts/README.md new file mode 100644 index 000000000..6684ab038 --- /dev/null +++ b/tests/scripts/README.md @@ -0,0 +1,99 @@ +# Smoke Tests for OWASP Cornucopia Applications + +This directory contains smoke tests for the OWASP Cornucopia project applications. + +## Overview + +The smoke tests verify basic functionality of both deployed applications: +- **copi.owasp.org** - The Elixir/Phoenix game engine +- **cornucopia.owasp.org** - The SvelteKit card browser website + +## What Do Smoke Tests Check? + +### For copi.owasp.org (Elixir/Phoenix) +1. Homepage loads successfully (HTTP 200) +2. Game route is accessible +3. JavaScript assets are being served +4. Server responds with proper HTTP headers + +### For cornucopia.owasp.org (SvelteKit) +1. Homepage loads successfully (HTTP 200) +2. Cards browser route (`/cards`) is accessible +3. JavaScript/Svelte bundles are being served +4. Individual card detail pages are accessible (e.g., `/cards/VE2`) +5. Page structure indicates JavaScript execution capability + +### Integration Tests +1. Both applications respond within acceptable time limits +2. Both applications are simultaneously accessible + +## Running the Tests + +### Locally + +```bash +# Run all smoke tests +python -m unittest tests.scripts.smoke_tests -v + +# Run tests for specific application +python -m unittest tests.scripts.smoke_tests.CopiSmokeTests -v +python -m unittest tests.scripts.smoke_tests.CornucopiaSmokeTests -v + +# Run integration tests +python -m unittest tests.scripts.smoke_tests.IntegrationSmokeTests -v +``` + +### With pipenv + +```bash +pipenv run python -m unittest tests.scripts.smoke_tests -v +``` + +### In CI/CD + +Smoke tests run automatically: +- On every push to `master` that affects the applications +- Daily at 6 AM UTC (scheduled) +- Manually via workflow dispatch +- As part of the regular test suite + +## Test Structure + +``` +tests/ +└── scripts/ + └── smoke_tests.py # Main smoke test file +``` + +## Dependencies + +The smoke tests require: +- Python 3.11+ +- `requests` library (for HTTP requests) + +These are already included in the project's `Pipfile`. + +## Related Issue + +These smoke tests were created to address [Issue #1265](https://github.com/OWASP/cornucopia/issues/1265). + +## CI/CD Integration + +Two workflows handle smoke tests: + +1. **smoke-tests.yaml** - Dedicated smoke test workflow + - Runs on schedule (daily) + - Runs on manual trigger + - Runs when application code changes + +2. **run-tests.yaml** - Main test workflow + - Includes smoke tests alongside unit and integration tests + - Runs on pull requests + +## Expected Results + +All tests should pass when both applications are properly deployed and functioning. If smoke tests fail, it indicates: +- One or both applications are not accessible +- Routes have changed or been removed +- JavaScript is not loading properly +- Server configuration issues diff --git a/tests/scripts/smoke_tests.py b/tests/scripts/smoke_tests.py new file mode 100644 index 000000000..ea30b5703 --- /dev/null +++ b/tests/scripts/smoke_tests.py @@ -0,0 +1,175 @@ +""" +Smoke tests for copi.owasp.org and cornucopia.owasp.org applications. + +These tests verify that: +1. At least 2 routes on each application are working +2. JavaScript is functioning correctly +3. Basic functionality is available + +Issue: #1265 +""" + +import os +import unittest +import requests +import time +from urllib.parse import urljoin + + +class CopiSmokeTests(unittest.TestCase): + """Smoke tests for copi.owasp.org (Elixir/Phoenix application)""" + + BASE_URL = os.environ.get("COPI_BASE_URL", "https://copi.owasp.org") + + def test_01_homepage_loads(self) -> None: + """Test that the Copi homepage loads successfully""" + try: + response = requests.get(self.BASE_URL, timeout=30) + self.assertEqual(response.status_code, 200, f"Homepage returned status {response.status_code}") + self.assertIn("copi", response.text.lower(), "Homepage should contain 'copi' text") + except requests.exceptions.ConnectionError: + self.fail(f"Failed to connect to {self.BASE_URL} - service may be down") + except requests.exceptions.Timeout: + self.fail(f"Request to {self.BASE_URL} timed out after 30 seconds") + + def test_02_game_route_accessible(self) -> None: + """Test that a game-related route is accessible""" + url = urljoin(self.BASE_URL, "/") + try: + response = requests.get(url, timeout=30) + self.assertEqual(response.status_code, 200, f"Game route returned status {response.status_code}") + except requests.exceptions.ConnectionError: + self.fail(f"Failed to connect to {url} - service may be down") + except requests.exceptions.Timeout: + self.fail(f"Request to {url} timed out after 30 seconds") + + def test_03_javascript_loads(self) -> None: + """Test that JavaScript assets are being served""" + try: + response = requests.get(self.BASE_URL, timeout=30) + self.assertEqual(response.status_code, 200) + self.assertTrue( + ' None: + """Test that the application server is healthy and responding""" + try: + response = requests.get(self.BASE_URL, timeout=30) + self.assertEqual(response.status_code, 200) + self.assertIn('content-type', [h.lower() for h in response.headers.keys()], + "Response should include content-type header") + except requests.exceptions.ConnectionError: + self.fail(f"Failed to connect to {self.BASE_URL} - service may be down") + except requests.exceptions.Timeout: + self.fail(f"Request to {self.BASE_URL} timed out after 30 seconds") + + +class CornucopiaSmokeTests(unittest.TestCase): + """Smoke tests for cornucopia.owasp.org (SvelteKit application)""" + + BASE_URL = os.environ.get("CORNUCOPIA_BASE_URL", "https://cornucopia.owasp.org") + + def test_01_homepage_loads(self) -> None: + """Test that the Cornucopia homepage loads successfully""" + try: + response = requests.get(self.BASE_URL, timeout=30) + self.assertEqual(response.status_code, 200, f"Homepage returned status {response.status_code}") + self.assertIn("cornucopia", response.text.lower(), "Homepage should contain 'cornucopia' text") + except requests.exceptions.ConnectionError: + self.fail(f"Failed to connect to {self.BASE_URL} - service may be down") + except requests.exceptions.Timeout: + self.fail(f"Request to {self.BASE_URL} timed out after 30 seconds") + + def test_02_cards_route_accessible(self) -> None: + """Test that the cards browser route is accessible""" + url = urljoin(self.BASE_URL, "/cards") + try: + response = requests.get(url, timeout=30) + self.assertEqual(response.status_code, 200, f"Cards route returned status {response.status_code}") + except requests.exceptions.ConnectionError: + self.fail(f"Failed to connect to {url} - service may be down") + except requests.exceptions.Timeout: + self.fail(f"Request to {url} timed out after 30 seconds") + + def test_03_javascript_loads(self) -> None: + """Test that JavaScript/Svelte bundles are being served""" + try: + response = requests.get(self.BASE_URL, timeout=30) + self.assertEqual(response.status_code, 200) + self.assertTrue( + ' None: + """Test that individual card routes are accessible""" + url = urljoin(self.BASE_URL, "/cards/VE2") + try: + response = requests.get(url, timeout=30) + self.assertEqual(response.status_code, 200, f"Card detail route returned status {response.status_code}") + except requests.exceptions.ConnectionError: + self.fail(f"Failed to connect to {url} - service may be down") + except requests.exceptions.Timeout: + self.fail(f"Request to {url} timed out after 30 seconds") + + def test_05_javascript_execution_check(self) -> None: + """Test that the page structure indicates JavaScript is functional""" + try: + response = requests.get(self.BASE_URL, timeout=30) + self.assertEqual(response.status_code, 200) + content = response.text + sveltekit_markers = ( + "data-sveltekit-preload-data", + "data-sveltekit-hydrate", + "__sveltekit", + ) + has_sveltekit_markers = any(marker in content for marker in sveltekit_markers) + has_module_script = '