Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
3e53540
feat: Add smoke tests for copi.owasp.org and cornucopia.owasp.org (fi…
immortal71 Jan 23, 2026
b6b121b
fixed: Address Copilot review feedback - removed hardening, use pipen…
immortal71 Jan 23, 2026
76cc490
refactor: Updated smoke tests to only test copi.owasp.org against loc…
immortal71 Jan 31, 2026
30d80eb
refactor: Address maintainer feedback for smoke tests PR
immortal71 Feb 7, 2026
287b698
fix: Remove smoke tests from main test workflow
immortal71 Feb 7, 2026
fcb57ea
fix: Format smoke_tests.py to comply with Black style guide
immortal71 Feb 7, 2026
c5e6a94
fix: Reformat smoke_tests.py to comply with Black line length require…
immortal71 Feb 7, 2026
b7df2c5
Fix Black formatting for smoke_tests.py
immortal71 Feb 8, 2026
ef182ff
fix: Reformat smoke_tests.py to comply with Black 120 char line length
immortal71 Feb 8, 2026
2492015
fix: Apply Black formatting with line-length=120 to smoke_tests.py
immortal71 Feb 8, 2026
855b986
Update smoke_tests.py to match commit f592480dd389405dec73e0ea14bda02…
immortal71 Feb 8, 2026
1bf5b31
fix: Restore smoke_tests.py to exact format from passing commit f592480
immortal71 Feb 8, 2026
1b13141
fix: Reformat smoke_tests.py with Black line-length=120 to match CI c…
immortal71 Feb 8, 2026
df1248d
fix: Apply Black formatting to smoke_tests.py
immortal71 Feb 8, 2026
4fef7a9
fix: Apply Black formatting with --line-length=120 to smoke_tests.py
immortal71 Feb 8, 2026
7377ead
fix: Apply Black formatting with --line-length=120 to smoke_tests.py
immortal71 Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/smoke-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Smoke Tests
on:
workflow_dispatch:
schedule:
- cron: '0 6 * * *'
push:
branches:
- master
paths:
- 'copi.owasp.org/**'
- 'tests/scripts/smoke_tests.py'
- '.github/workflows/smoke-tests.yaml'

permissions:
contents: read

jobs:
smoke-tests:
name: Run Smoke Tests
runs-on: ubuntu-latest
env:
COPI_BASE_URL: "http://127.0.0.1:4000"
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.12'
cache: 'pipenv'

- name: Install dependencies
run: |
pip install -r requirements.txt --require-hashes
pipenv install --ignore-pipfile --dev

- name: Start DB and Copi application containers
run: |
set -e
echo "Creating docker network for smoke tests"
docker network create copi-net || true

echo "Starting Postgres container"
docker run -d --name copi-postgres --network copi-net \
-e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=copi \
postgres:15

echo "Building Copi Docker image"
docker build -t copi-test-image copi.owasp.org

echo "Generating SECRET_KEY_BASE"
SECRET_KEY_BASE=$(python - <<'PY'
import secrets
print(secrets.token_hex(64))
PY
)

echo "Starting Copi application container"
docker run -d --name copi-app --network copi-net -p 4000:4000 \
-e DATABASE_URL=ecto://postgres:postgres@copi-postgres:5432/copi \
-e SECRET_KEY_BASE="$SECRET_KEY_BASE" \
copi-test-image

echo "Waiting for Copi to become healthy on ${COPI_BASE_URL}"
for i in {1..30}; do
if curl -sSfL ${COPI_BASE_URL} >/dev/null; then
echo "Copi is up"
exit 0
fi
echo "Waiting for Copi... ($i)"
sleep 2
done
echo "Copi did not start in time"
docker logs copi-app || true
exit 1

- name: Run smoke tests for copi.owasp.org
run: pipenv run python -m unittest tests.scripts.smoke_tests.CopiSmokeTests -v
continue-on-error: false

- name: Cleanup containers
if: always()
run: |
docker stop copi-app copi-postgres || true
docker rm copi-app copi-postgres || true
docker network rm copi-net || true

- name: Summary
if: always()
run: |
echo "## Smoke Test Results" >> $GITHUB_STEP_SUMMARY
echo "Smoke tests completed for copi.owasp.org (Elixir/Phoenix application)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Tests ran against Dockerized application on localhost:4000" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Tests verify:" >> $GITHUB_STEP_SUMMARY
echo "- Homepage and cards route are accessible" >> $GITHUB_STEP_SUMMARY
echo "- JavaScript is loading and functional" >> $GITHUB_STEP_SUMMARY
echo "- Server is healthy and responding with proper headers" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Note: cornucopia.owasp.org has built-in Vite smoke tests" >> $GITHUB_STEP_SUMMARY
71 changes: 71 additions & 0 deletions tests/scripts/smoke_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
Smoke tests for copi.owasp.org application.

These tests run against a Dockerized copi.owasp.org application in the CI pipeline (localhost).
They verify that:
1. At least 2 routes on the application are working
2. JavaScript is functioning correctly
3. Basic functionality is available

Note: We do not need smoke tests for cornucopia.owasp.org because Vite comes with
built-in smoke tests that fire up the server and check that all internal links
on the website go to live pages.

Issue: #1265
"""

import os
import unittest
import requests
from urllib.parse import urljoin


class CopiSmokeTests(unittest.TestCase):
"""Smoke tests for copi.owasp.org (Elixir/Phoenix application)"""

# Default to localhost so CI can run against a Dockerized app on localhost
BASE_URL = os.environ.get("COPI_BASE_URL", "http://127.0.0.1:4000")

def _make_request(self, url: str, timeout: int = 30) -> requests.Response:
"""Helper method to make HTTP requests with error handling"""
try:
return requests.get(url, timeout=timeout)
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 {timeout} seconds")

def test_01_homepage_loads(self) -> None:
"""Test that the Copi homepage loads successfully"""
response = self._make_request(self.BASE_URL)
self.assertEqual(response.status_code, 200, f"Homepage returned status {response.status_code}")
self.assertIn("copi", response.text.lower(), "Homepage should contain 'copi' text")

def test_02_cards_route_accessible(self) -> None:
"""Test that the cards route is accessible"""
url = urljoin(self.BASE_URL, "/cards")
response = self._make_request(url)
self.assertEqual(response.status_code, 200, f"Cards route returned status {response.status_code}")

def test_03_javascript_loads(self) -> None:
"""Test that JavaScript assets are being served"""
response = self._make_request(self.BASE_URL)
self.assertEqual(response.status_code, 200)
self.assertTrue(
"<script" in response.text or "app.js" in response.text or "phoenix" in response.text.lower(),
"JavaScript should be loaded on the page",
)

def test_04_health_check(self) -> None:
"""Test that the application server is healthy and responding"""
response = self._make_request(self.BASE_URL)
self.assertEqual(response.status_code, 200)
self.assertIn(
"content-type",
[h.lower() for h in response.headers.keys()],
"Response should include content-type header",
)


if __name__ == "__main__":
unittest.main()
Loading