-
Notifications
You must be signed in to change notification settings - Fork 43
Adding integration tests #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
33804cb
39ebe44
a7d6ebd
3cffb62
9ee7955
0f4a513
0c98e02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,58 @@ | ||||||||||||||||||||||||||||||||||||||
| name: Integration Tests for Exosphere | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||||||||||||||||||
| paths: | ||||||||||||||||||||||||||||||||||||||
| - 'integration-tests/**' | ||||||||||||||||||||||||||||||||||||||
| - 'state-manager/**' | ||||||||||||||||||||||||||||||||||||||
| - 'python-sdk/**' | ||||||||||||||||||||||||||||||||||||||
| - '.github/workflows/integration-tests.yml' | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Quote “on” and fix indentation to satisfy yamllint and avoid parse surprises YAML treats -name: Integration Tests for Exosphere
-
-on:
- push:
- branches: [main]
- paths:
- - 'integration-tests/**'
- - 'state-manager/**'
- - 'python-sdk/**'
- - '.github/workflows/integration-tests.yml'
+name: Integration Tests for Exosphere
+
+'on':
+ push:
+ branches: [main]
+ paths:
+ - integration-tests/**
+ - state-manager/**
+ - python-sdk/**
+ - .github/workflows/integration-tests.yml📝 Committable suggestion
Suggested change
🧰 Tools🪛 YAMLlint (1.37.1)[warning] 3-3: truthy value should be one of [false, true] (truthy) [error] 7-7: wrong indentation: expected 12 but found 10 (indentation) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||
| test: | ||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||
| services: | ||||||||||||||||||||||||||||||||||||||
| mongodb: | ||||||||||||||||||||||||||||||||||||||
| image: mongo:7 | ||||||||||||||||||||||||||||||||||||||
| ports: | ||||||||||||||||||||||||||||||||||||||
| - 27017:27017 | ||||||||||||||||||||||||||||||||||||||
| options: >- | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainTighten job config: timeout, consistent indentation, and uv install groups
jobs:
- test:
- runs-on: ubuntu-latest
- services:
- mongodb:
- image: mongo:7
- ports:
- - 27017:27017
+ test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ services:
+ mongodb:
+ image: mongo:7
+ ports:
+ - 27017:27017
...
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
...
- - name: Install dev dependencies with uv
+ - name: Install dependencies with uv
working-directory: integration-tests
run: |
- uv sync --group dev
+ uv sync --all-groupsIf you intend to install only the default group (not dev), use just Also applies to: 30-58 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== First 100 lines of .github/workflows/integration-tests.yml =="
sed -n '1,100p' .github/workflows/integration-tests.yml
echo
echo "== uv sync occurrences =="
grep -n "uv sync" .github/workflows/integration-tests.yml || trueLength of output: 2064 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== dependency-groups occurrences =="
grep -R "dependency-groups" -n . || true
echo
echo "== pyproject.toml files =="
find . -type f | grep pyproject.toml || echo "none"
echo
echo "== setup.cfg for dependency-groups =="
grep -R "dependency_groups" -n . || trueLength of output: 734 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== List of workflow files =="
ls .github/workflows
for f in .github/workflows/*.yml; do
echo
echo "== $f first 5 lines =="
sed -n '1,5p' "$f"
done
echo
echo "== integration-tests pyproject.toml [dependency-groups] section =="
grep -n '^\[dependency-groups\]' -n integration-tests/pyproject.toml
sed -n '1,50p' integration-tests/pyproject.tomlLength of output: 6290 Tighten integration-tests workflow: add timeout, fix indentation, and adjust uv invocation
🧰 Tools🪛 YAMLlint (1.37.1)[error] 17-17: wrong indentation: expected 16 but found 14 (indentation) [error] 19-19: wrong indentation: expected 18 but found 16 (indentation) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| --health-cmd "mongosh --eval 'db.runCommand(\"ping\")'" | ||||||||||||||||||||||||||||||||||||||
| --health-interval 10s | ||||||||||||||||||||||||||||||||||||||
| --health-timeout 5s | ||||||||||||||||||||||||||||||||||||||
| --health-retries 5 | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix MongoDB healthcheck: wrong command and missing auth cause unhealthy service
Apply: - options: >-
- --health-cmd "mongosh --eval 'db.runCommand(\"ping\")'"
+ options: >-
+ --health-cmd "mongosh --username admin --password password --authenticationDatabase admin --quiet --eval 'db.adminCommand({ ping: 1 }).ok'"
--health-interval 10s
--health-timeout 5s
--health-retries 5📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||
| MONGO_INITDB_ROOT_USERNAME: admin | ||||||||||||||||||||||||||||||||||||||
| MONGO_INITDB_ROOT_PASSWORD: password | ||||||||||||||||||||||||||||||||||||||
| MONGO_INITDB_DATABASE: integration_tests | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||
| - name: Checkout code | ||||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Set up Python | ||||||||||||||||||||||||||||||||||||||
| uses: actions/setup-python@v5 | ||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||
| python-version: '3.12' | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Install uv | ||||||||||||||||||||||||||||||||||||||
| uses: astral-sh/setup-uv@v2 | ||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||
| cache: true | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Install dev dependencies with uv | ||||||||||||||||||||||||||||||||||||||
| working-directory: integration-tests | ||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||
| uv sync --group dev | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Run integration tests | ||||||||||||||||||||||||||||||||||||||
| working-directory: integration-tests | ||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||
| MONGO_URI: mongodb://admin:password@localhost:27017 | ||||||||||||||||||||||||||||||||||||||
| MONGO_DATABASE_NAME: integration_tests | ||||||||||||||||||||||||||||||||||||||
| STATE_MANAGER_SECRET: test-secret-key | ||||||||||||||||||||||||||||||||||||||
| EXOSPHERE_API_KEY: test-secret-key | ||||||||||||||||||||||||||||||||||||||
| SECRETS_ENCRYPTION_KEY: YTzpUlBGLSwm-3yKJRJTZnb0_aQuQQHyz64s8qAERVU= | ||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not commit secrets to the repo; use GitHub Secrets and add authSource to URI Hard-coded API keys and encryption keys will be leaked to forks and logs. Also add - MONGO_URI: mongodb://admin:password@localhost:27017
+ MONGO_URI: mongodb://admin:password@127.0.0.1:27017/?authSource=admin
MONGO_DATABASE_NAME: integration_tests
- STATE_MANAGER_SECRET: test-secret-key
- EXOSPHERE_API_KEY: test-secret-key
- SECRETS_ENCRYPTION_KEY: YTzpUlBGLSwm-3yKJRJTZnb0_aQuQQHyz64s8qAERVU=
+ STATE_MANAGER_SECRET: ${{ secrets.STATE_MANAGER_SECRET }}
+ EXOSPHERE_API_KEY: ${{ secrets.EXOSPHERE_API_KEY }}
+ SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}If you intentionally use throwaway defaults for local runs, write them to a .env during CI instead of committing them: - name: Run integration tests
working-directory: integration-tests
env:
- MONGO_URI: mongodb://admin:password@127.0.0.1:27017/?authSource=admin
- MONGO_DATABASE_NAME: integration_tests
- STATE_MANAGER_SECRET: ${{ secrets.STATE_MANAGER_SECRET }}
- EXOSPHERE_API_KEY: ${{ secrets.EXOSPHERE_API_KEY }}
- SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
+ MONGO_URI: mongodb://admin:password@127.0.0.1:27017/?authSource=admin
+ MONGO_DATABASE_NAME: integration_tests
+ STATE_MANAGER_SECRET: ${{ secrets.STATE_MANAGER_SECRET }}
+ EXOSPHERE_API_KEY: ${{ secrets.EXOSPHERE_API_KEY }}
+ SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
- uv run pytest
+ : "${STATE_MANAGER_SECRET:=test-secret-key}"
+ : "${EXOSPHERE_API_KEY:=test-secret-key}"
+ : "${SECRETS_ENCRYPTION_KEY:=insecure-test-key}"
+ uv run pytest🧰 Tools🪛 Gitleaks (8.27.2)56-56: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key) 🪛 Checkov (3.2.334)[MEDIUM] 52-53: Basic Auth Credentials (CKV_SECRET_4) [LOW] 56-57: Base64 High Entropy String (CKV_SECRET_6) 🪛 YAMLlint (1.37.1)[error] 52-52: wrong indentation: expected 14 but found 12 (indentation) |
||||||||||||||||||||||||||||||||||||||
| uv run pytest | ||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Add trailing newline Satisfies linters and keeps diffs cleaner. - uv run pytest
+ uv run pytest
+📝 Committable suggestion
Suggested change
🧰 Tools🪛 YAMLlint (1.37.1)[error] 58-58: no new line character at the end of file (new-line-at-end-of-file) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,6 @@ on: | |
| branches: [main] | ||
| paths: | ||
| - 'state-manager/**' | ||
| release: | ||
| types: [published] | ||
| workflow_dispatch: | ||
|
|
||
| env: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # Python | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *$py.class | ||
| *.so | ||
| .Python | ||
| build/ | ||
| develop-eggs/ | ||
| dist/ | ||
| downloads/ | ||
| eggs/ | ||
| .eggs/ | ||
| lib/ | ||
| lib64/ | ||
| parts/ | ||
| sdist/ | ||
| var/ | ||
| wheels/ | ||
| *.egg-info/ | ||
| .installed.cfg | ||
| *.egg | ||
|
|
||
| # Virtual Environment | ||
| venv/ | ||
| env/ | ||
| ENV/ | ||
| .env | ||
| .venv/ | ||
|
|
||
| # IDE | ||
| .vscode/ | ||
| *.swp | ||
| *.swo | ||
| .idea/ | ||
| *.iws | ||
| *.iml | ||
| *.ipr | ||
|
|
||
|
|
||
| # Local development | ||
| .env.local | ||
| .env.development.local | ||
| .env.test.local | ||
| .env.production.local | ||
|
|
||
| # Database | ||
| *.db | ||
| *.sqlite3 | ||
|
|
||
| # OS generated files | ||
| .DS_Store | ||
| .DS_Store? | ||
| ._* | ||
| .Spotlight-V100 | ||
| .Trashes | ||
| ehthumbs.db | ||
| Thumbs.db | ||
|
|
||
| #logs | ||
| *.log | ||
| logs/*.* | ||
| !logs/.gitkeep | ||
|
|
||
| # local files | ||
| files/ | ||
| !files/.gitkeep |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.12 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,207 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Integration Testing with Uvicorn Server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This directory contains examples of how to properly run and stop uvicorn servers for integration testing. Unlike the problematic approach of using `uvicorn.run()` (which blocks forever), these examples show you how to create real HTTP endpoints that you can send requests to. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 🚨 The Problem with `uvicorn.run()` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The original code had this issue: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.mark.asyncio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def test_basic(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uvicorn.run(app, host="127.0.0.1", port=8000) # ❌ This blocks forever! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with ClientSession() as session: # ❌ This never executes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ... test code that never runs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Problem**: `uvicorn.run()` is a blocking call that never returns, so your test code after it never executes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## ✅ Proper Solutions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 1. UvicornTestServer Class (Recommended) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The `UvicornTestServer` class in `test_basic.py` provides a clean way to start and stop uvicorn servers: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from test_basic import UvicornTestServer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create and start server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server = UvicornTestServer(app, host="127.0.0.1", port=8000) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.start() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Make HTTP requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with ClientSession() as session: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with session.get(f"{server.base_url}/health") as response: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert response.status == 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Stop server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File references don’t match the repo (class now lives in conftest.py) You reference -from test_basic import UvicornTestServer
+from conftest import UvicornTestServer📝 Committable suggestion
Suggested change
🧰 Tools🪛 LanguageTool[grammar] ~23-~23: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Features:** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Automatic port detection (avoids conflicts) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Proper startup/shutdown lifecycle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Thread-based server execution | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Waits for server to be ready | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Graceful cleanup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 2. Pytest Fixtures | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Use pytest fixtures for automatic server management: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.fixture(scope="session") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def running_server(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Server shared across all tests in the session.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server = UvicornTestServer(app) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.start() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.fixture(scope="function") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def fresh_server(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Fresh server for each test.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server = UvicornTestServer(app) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.start() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 3. Manual Server Management | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| For full control over server lifecycle: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.mark.asyncio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def test_manual_server(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server = UvicornTestServer(app) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.start() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Your test code here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with ClientSession() as session: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with session.get(f"{server.base_url}/health") as response: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert response.status == 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finally: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.stop() # Always cleanup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 🏃♂️ Running the Examples | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Install Dependencies | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cd integration-tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uv sync | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Run Tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Run all tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uv run python -m pytest test_basic.py -v | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Run specific test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uv run python -m pytest test_basic.py::test_basic_with_session_server -v -s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Run with output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uv run python -m pytest test_basic.py -v -s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Run Demo Server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Start a server you can send requests to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uv run python demo_server.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Then in another terminal: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curl http://127.0.0.1:8000/health | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curl http://127.0.0.1:8000/test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 📁 File Overview | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **`test_basic.py`** - Main test file with UvicornTestServer class and examples | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **`test_server_examples.py`** - Comprehensive examples of different testing approaches | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **`demo_server.py`** - Simple script to run a server manually | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **`pyproject.toml`** - Project dependencies | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
NiveditJain marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+124
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Remove/adjust file overview entries that aren’t shipped in this PR If 🧰 Tools🪛 LanguageTool[grammar] ~124-~124: There might be a problem here. (QB_NEW_EN_MERGED_MATCH) [grammar] ~126-~126: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~127-~127: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~128-~128: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~129-~129: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 🎯 When to Use Each Approach | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Session-Scoped Server (`running_server` fixture) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Fast test execution (server starts once) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Good for multiple tests that don't interfere | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ❌ Tests share state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **Use for**: Most integration tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Function-Scoped Server (`fresh_server` fixture) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Complete isolation between tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Clean state for each test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ❌ Slower (starts server for each test) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **Use for**: Tests that modify server state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Manual Server Management | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Full control over lifecycle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Can test server startup/shutdown | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ❌ More verbose | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **Use for**: Complex scenarios, debugging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Demo Server Script | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Perfect for development and debugging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Can send real HTTP requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Easy to test endpoints manually | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **Use for**: Development, manual testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 🔧 Key Features of UvicornTestServer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1. **Automatic Port Detection**: Finds free ports to avoid conflicts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2. **Proper Lifecycle**: Clean startup and shutdown | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3. **Thread Safety**: Runs server in background thread | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4. **Health Checking**: Waits for server to be ready | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5. **Graceful Shutdown**: Proper cleanup on exit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6. **Error Handling**: Robust error handling and timeouts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 🚀 Making HTTP Requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Once you have a running server, you can make requests using: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### With aiohttp (async) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with ClientSession() as session: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with session.get(f"{server.base_url}/health") as response: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data = await response.json() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert data["message"] == "OK" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### With httpx (async) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with httpx.AsyncClient() as client: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response = await client.get(f"{server.base_url}/health") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert response.status_code == 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### With curl (command line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curl http://127.0.0.1:8000/health | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| curl -X POST http://127.0.0.1:8000/api/data -H "Content-Type: application/json" -d '{"key": "value"}' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### With requests (sync) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response = requests.get(f"{server.base_url}/health") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert response.status_code == 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## 🎉 Success! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Now you have a proper way to run uvicorn servers for integration testing that: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Actually starts and stops properly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Provides real HTTP endpoints | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Handles cleanup automatically | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Avoids port conflicts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Works reliably in CI/CD | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| No more blocking `uvicorn.run()` calls or tests that never execute! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+207
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Fix markdown spacing and trailing newline to satisfy markdownlint Add blank lines around headings, lists, and fenced blocks; remove trailing spaces; end file with a newline. If you want, I can run a pass and push a formatted version. 🧰 Tools🪛 LanguageTool[grammar] ~1-~1: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~3-~3: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~5-~5: There might be a mistake here. (QB_NEW_EN) [grammar] ~7-~7: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~17-~17: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~19-~19: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~21-~21: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~23-~23: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~41-~41: There might be a mistake here. (QB_NEW_EN) [grammar] ~42-~42: There might be a mistake here. (QB_NEW_EN) [grammar] ~43-~43: There might be a mistake here. (QB_NEW_EN) [grammar] ~44-~44: There might be a mistake here. (QB_NEW_EN) [grammar] ~45-~45: There might be a mistake here. (QB_NEW_EN) [grammar] ~46-~46: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~48-~48: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~50-~50: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~70-~70: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~72-~72: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~89-~89: There might be a mistake here. (QB_NEW_EN) [grammar] ~91-~91: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~98-~98: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~111-~111: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~118-~118: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~124-~124: There might be a problem here. (QB_NEW_EN_MERGED_MATCH) [grammar] ~126-~126: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~127-~127: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~128-~128: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~129-~129: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~131-~131: There might be a mistake here. (QB_NEW_EN) [grammar] ~133-~133: There might be a mistake here. (QB_NEW_EN) [grammar] ~134-~134: There might be a mistake here. (QB_NEW_EN) [grammar] ~135-~135: There might be a mistake here. (QB_NEW_EN) [grammar] ~136-~136: There might be a mistake here. (QB_NEW_EN) [grammar] ~137-~137: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~139-~139: There might be a mistake here. (QB_NEW_EN) [grammar] ~140-~140: There might be a mistake here. (QB_NEW_EN) [grammar] ~141-~141: There might be a mistake here. (QB_NEW_EN) [grammar] ~142-~142: There might be a mistake here. (QB_NEW_EN) [grammar] ~143-~143: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~145-~145: There might be a mistake here. (QB_NEW_EN) [grammar] ~146-~146: There might be a mistake here. (QB_NEW_EN) [grammar] ~147-~147: There might be a mistake here. (QB_NEW_EN) [grammar] ~148-~148: There might be a mistake here. (QB_NEW_EN) [grammar] ~149-~149: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~151-~151: There might be a mistake here. (QB_NEW_EN) [grammar] ~152-~152: There might be a mistake here. (QB_NEW_EN) [grammar] ~153-~153: There might be a mistake here. (QB_NEW_EN) [grammar] ~154-~154: There might be a mistake here. (QB_NEW_EN) [grammar] ~155-~155: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~157-~157: There might be a mistake here. (QB_NEW_EN) [grammar] ~159-~159: There might be a mistake here. (QB_NEW_EN_OTHER) [grammar] ~164-~164: There might be a mistake here. (QB_NEW_EN_OTHER) [grammar] ~166-~166: There might be a mistake here. (QB_NEW_EN) [grammar] ~168-~168: There might be a mistake here. (QB_NEW_EN_OTHER) [grammar] ~170-~170: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~178-~178: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~185-~185: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~191-~191: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~198-~198: There might be a mistake here. (QB_NEW_EN) [grammar] ~200-~200: There might be a mistake here. (QB_NEW_EN) [grammar] ~201-~201: There might be a mistake here. (QB_NEW_EN) [grammar] ~202-~202: There might be a mistake here. (QB_NEW_EN) [grammar] ~203-~203: There might be a mistake here. (QB_NEW_EN) [grammar] ~204-~204: There might be a mistake here. (QB_NEW_EN) [grammar] ~205-~205: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) 🪛 markdownlint-cli2 (0.17.2)42-42: Lists should be surrounded by blank lines (MD032, blanks-around-lists) 119-119: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) 133-133: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 134-134: Lists should be surrounded by blank lines (MD032, blanks-around-lists) 139-139: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 140-140: Lists should be surrounded by blank lines (MD032, blanks-around-lists) 145-145: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 146-146: Lists should be surrounded by blank lines (MD032, blanks-around-lists) 151-151: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 152-152: Lists should be surrounded by blank lines (MD032, blanks-around-lists) 170-170: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 171-171: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) 178-178: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 179-179: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) 185-185: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 186-186: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) 191-191: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 192-192: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) 201-201: Lists should be surrounded by blank lines (MD032, blanks-around-lists) 207-207: Trailing spaces (MD009, no-trailing-spaces) 207-207: Files should end with a single newline character (MD047, single-trailing-newline) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Set minimal permissions for the workflow (defense-in-depth)
Not required but recommended.
📝 Committable suggestion
🤖 Prompt for AI Agents