Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Handling DeprecationWarning 'asyncio_mode' default value
[pytest]
asyncio_mode = strict
testpaths = tests/e2e
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short --strict-markers
timeout = 300
markers =
e2e: End-to-end integration tests
slow: Tests that take longer to run
docker: Tests that require Docker
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ipython>=8.10.0
pytest
pytest-asyncio
pytest-rerunfailures
pytest-timeout
requests
wheel>=0.38.0
twine
setuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability
Expand Down
49 changes: 49 additions & 0 deletions run_e2e_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""
Simple script to run E2E tests for OPAL.
This script provides an easy way to execute the E2E test suite.
"""

import subprocess
import sys
import os


def main():
"""Run the E2E test suite."""
print("πŸš€ Starting OPAL E2E Tests...")
print("=" * 50)

# Change to the project directory
project_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(project_dir)

# Run pytest with E2E tests
cmd = [
sys.executable, "-m", "pytest",
"tests/e2e",
"-v",
"--tb=short",
"-m", "e2e"
]

try:
result = subprocess.run(cmd, check=False)

if result.returncode == 0:
print("\nβœ… All E2E tests passed!")
else:
print(f"\n❌ Some tests failed (exit code: {result.returncode})")

return result.returncode

except KeyboardInterrupt:
print("\n⚠️ Tests interrupted by user")
return 1
except Exception as e:
print(f"\nπŸ’₯ Error running tests: {e}")
return 1


if __name__ == "__main__":
sys.exit(main())
108 changes: 108 additions & 0 deletions tests/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# OPAL E2E Tests

This directory contains End-to-End (E2E) tests for OPAL using PyTest framework.

## Overview

The E2E tests verify that OPAL Server and OPAL Client work correctly together by:
- Starting Docker services automatically
- Testing health endpoints
- Validating statistics API
- Checking logs for errors
- Verifying client-server connectivity

## Prerequisites

- Python 3.7+
- Docker and Docker Compose
- Required Python packages (install with `pip install -r requirements.txt`)

## Running Tests

### Quick Start
```bash
# Run all E2E tests
pytest tests/e2e -q

# Run with verbose output
pytest tests/e2e -v

# Run specific test file
pytest tests/e2e/test_health.py -v
```

### Using the Test Runner
```bash
# Alternative way to run tests
python run_e2e_tests.py
```

## Test Structure

- `conftest.py` - PyTest fixtures and Docker service management
- `test_health.py` - Health endpoint validation
- `test_stats.py` - Statistics API and connection tests
- `test_logs.py` - Log validation and error detection

## What Gets Tested

### Health Tests
- Server `/healthcheck` endpoint returns 200
- Server `/` root endpoint returns 200
- Response time is reasonable

### Statistics Tests
- Statistics endpoints are accessible
- JSON response structure is valid
- Client-server connection status

### Log Tests
- No ERROR or CRITICAL messages in logs
- Successful startup messages present
- Both services produce logs

## Configuration

Tests use the existing `docker/docker-compose-example.yml` configuration with:
- OPAL Server on port 7002
- OPAL Client on port 7766
- OPA on port 8181

## Troubleshooting

### Port Conflicts
If tests fail due to port conflicts, ensure ports 7002, 7766, and 8181 are available.

### Docker Issues
```bash
# Clean up Docker resources
docker-compose -f docker/docker-compose-example.yml down -v

# Check Docker status
docker ps
```

### Timeout Issues
If services take longer to start, increase `HEALTH_CHECK_TIMEOUT` in `conftest.py`.

## Adding New Tests

1. Create new test files following the `test_*.py` pattern
2. Use the `@pytest.mark.e2e` and `@pytest.mark.docker` markers
3. Utilize existing fixtures from `conftest.py`
4. Follow the existing code style and patterns


## To check it's working proeprly:
1. Terminal in docker folder:
cd /Users/kartikbhardwaj/Desktop/opal/opal/docker
docker compose -f docker-compose-with-statistics.yml ps
2. Health check working:
curl http://localhost:7002/healthcheck
curl http://localhost:7002/statistics
3. E2E tests passing:
cd /Users/kartikbhardwaj/Desktop/opal/opal
source venv/bin/activate
pytest tests/e2e -q
4. Show final result:
======================== 11 passed, 1 warning in 5.61s =========================
83 changes: 83 additions & 0 deletions tests/e2e/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import os
import time
import subprocess
import requests
import pytest
from typing import Generator


# Configuration constants
DOCKER_COMPOSE_FILE = "docker/docker-compose-with-statistics.yml"
SERVER_URL = "http://localhost:7002"
CLIENT_URL = "http://localhost:7766"
HEALTH_CHECK_TIMEOUT = 60
HEALTH_CHECK_INTERVAL = 2


@pytest.fixture(scope="session")
def docker_services() -> Generator[None, None, None]:
"""Use existing Docker Compose services and ensure they're healthy."""
# Check if services are already running
_wait_for_service_health()

# Don't start or stop services - use existing ones
yield


def _wait_for_service_health():
"""Wait for OPAL server to respond to health checks."""
start_time = time.time()

while time.time() - start_time < HEALTH_CHECK_TIMEOUT:
try:
response = requests.get(f"{SERVER_URL}/healthcheck", timeout=5)
if response.status_code == 200:
# Give services a bit more time to fully initialize
time.sleep(5)
return
except requests.exceptions.RequestException:
pass

time.sleep(HEALTH_CHECK_INTERVAL)

pytest.fail(f"OPAL server failed to become healthy within {HEALTH_CHECK_TIMEOUT} seconds")


@pytest.fixture
def server_url(docker_services) -> str:
"""Provide the OPAL server base URL."""
return SERVER_URL


@pytest.fixture
def client_url(docker_services) -> str:
"""Provide the OPAL client base URL."""
return CLIENT_URL


@pytest.fixture
def get_container_logs():
"""Helper function to retrieve Docker container logs."""
def _get_logs(service_name: str) -> str:
try:
result = subprocess.run([
"docker-compose", "-f", DOCKER_COMPOSE_FILE, "logs", service_name
], capture_output=True, text=True, check=True)
return result.stdout
except subprocess.CalledProcessError as e:
return f"Error getting logs for {service_name}: {e}"

return _get_logs


@pytest.fixture
def api_client():
"""Helper for making API requests with proper error handling."""
def _make_request(url: str, method: str = "GET", **kwargs):
try:
response = requests.request(method, url, timeout=10, **kwargs)
return response
except requests.exceptions.RequestException as e:
pytest.fail(f"API request failed: {e}")

return _make_request
42 changes: 42 additions & 0 deletions tests/e2e/test_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pytest
import requests


@pytest.mark.e2e
@pytest.mark.docker
def test_server_healthcheck_endpoint(server_url, api_client):
"""Test that OPAL server /healthcheck endpoint returns 200 OK."""
response = api_client(f"{server_url}/healthcheck")

assert response.status_code == 200, f"Expected 200, got {response.status_code}"

# Verify response content
json_data = response.json()
assert json_data == {"status": "ok"}, f"Expected {{'status': 'ok'}}, got {json_data}"


@pytest.mark.e2e
@pytest.mark.docker
def test_server_root_endpoint(server_url, api_client):
"""Test that OPAL server root endpoint (/) returns 200 OK."""
response = api_client(f"{server_url}/")

assert response.status_code == 200, f"Expected 200, got {response.status_code}"

# Verify response content
json_data = response.json()
assert json_data == {"status": "ok"}, f"Expected {{'status': 'ok'}}, got {json_data}"


@pytest.mark.e2e
@pytest.mark.docker
def test_server_responds_quickly(server_url, api_client):
"""Test that server responds within reasonable time."""
import time

start_time = time.time()
response = api_client(f"{server_url}/healthcheck")
response_time = time.time() - start_time

assert response.status_code == 200
assert response_time < 5.0, f"Server took too long to respond: {response_time:.2f}s"
Loading