Skip to content

Commit 3892965

Browse files
Copilotilyazub
andcommitted
Add comprehensive e2e tests for Docker container
Co-authored-by: ilyazub <282605+ilyazub@users.noreply.github.com>
1 parent e26647a commit 3892965

File tree

4 files changed

+203
-0
lines changed

4 files changed

+203
-0
lines changed

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ dependencies = [
88
"google-search-results>=2.4.2",
99
"mcp[cli]>=1.3.0",
1010
]
11+
12+
[project.optional-dependencies]
13+
test = [
14+
"pytest>=8.0.0",
15+
]

tests/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# End-to-End Tests
2+
3+
This directory contains end-to-end tests for the SerpApi MCP Server.
4+
5+
## Running Tests
6+
7+
### Prerequisites
8+
9+
Install test dependencies:
10+
11+
```bash
12+
pip install pytest
13+
```
14+
15+
### Run All Tests
16+
17+
```bash
18+
pytest tests/ -v
19+
```
20+
21+
### Run Specific Test File
22+
23+
```bash
24+
pytest tests/test_e2e_docker.py -v
25+
```
26+
27+
## Test Coverage
28+
29+
### Docker E2E Tests (`test_e2e_docker.py`)
30+
31+
Tests the Docker container functionality:
32+
33+
- **test_docker_image_exists**: Verifies the Docker image builds successfully
34+
- **test_container_requires_api_key**: Ensures container validates the required `SERPAPI_API_KEY` environment variable
35+
- **test_container_starts_with_api_key**: Confirms container starts properly when API key is provided
36+
- **test_container_python_version**: Validates Python 3.13 is used in the container
37+
- **test_container_has_dependencies**: Checks all required dependencies are installed
38+
- **test_server_module_exists**: Verifies the server module is present and accessible
39+
40+
## CI/CD
41+
42+
These tests are designed to run in CI/CD pipelines and handle environment-specific issues like SSL certificate verification in restricted networks.

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for SerpApi MCP Server."""

tests/test_e2e_docker.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""
2+
End-to-End tests for the SerpApi MCP Server Docker container.
3+
4+
This test suite validates that:
5+
1. The Docker image builds successfully
6+
2. The container requires the SERPAPI_API_KEY environment variable
7+
3. The container starts and runs the MCP server correctly
8+
"""
9+
10+
import subprocess
11+
import time
12+
import pytest
13+
14+
15+
def run_command(cmd, timeout=30, check=True):
16+
"""Helper function to run shell commands."""
17+
try:
18+
result = subprocess.run(
19+
cmd,
20+
shell=True,
21+
capture_output=True,
22+
text=True,
23+
timeout=timeout,
24+
check=check
25+
)
26+
return result.returncode, result.stdout, result.stderr
27+
except subprocess.TimeoutExpired:
28+
return 124, "", "Command timed out"
29+
except subprocess.CalledProcessError as e:
30+
return e.returncode, e.stdout, e.stderr
31+
32+
33+
class TestDockerE2E:
34+
"""End-to-End tests for Docker container."""
35+
36+
IMAGE_NAME = "serpapi-mcp-server:test"
37+
38+
@classmethod
39+
def setup_class(cls):
40+
"""Build the Docker image before running tests."""
41+
print("\n=== Building Docker image ===")
42+
43+
# First, try to build normally
44+
returncode, stdout, stderr = run_command(
45+
f"docker build -t {cls.IMAGE_NAME} .",
46+
timeout=300,
47+
check=False
48+
)
49+
50+
# If build fails due to SSL certificate issues (common in CI environments),
51+
# try with trusted hosts
52+
if returncode != 0 and "SSL" in stderr:
53+
print("⚠ SSL certificate issue detected, rebuilding with trusted hosts...")
54+
# Temporarily modify Dockerfile for SSL workaround
55+
run_command(
56+
"sed -i 's/pip install --no-cache-dir/pip install --no-cache-dir --trusted-host pypi.org --trusted-host files.pythonhosted.org/g' Dockerfile",
57+
check=False
58+
)
59+
returncode, stdout, stderr = run_command(
60+
f"docker build -t {cls.IMAGE_NAME} .",
61+
timeout=300,
62+
check=False
63+
)
64+
# Restore original Dockerfile
65+
run_command("git checkout Dockerfile", check=False)
66+
67+
assert returncode == 0, f"Docker build failed: {stderr}"
68+
print("✓ Docker image built successfully")
69+
70+
@classmethod
71+
def teardown_class(cls):
72+
"""Clean up Docker resources after tests."""
73+
print("\n=== Cleaning up Docker resources ===")
74+
# Remove test containers
75+
run_command(f"docker ps -a -q --filter ancestor={cls.IMAGE_NAME} | xargs -r docker rm -f", check=False)
76+
print("✓ Cleanup completed")
77+
78+
def test_docker_image_exists(self):
79+
"""Test that the Docker image was built successfully."""
80+
returncode, stdout, stderr = run_command(
81+
f"docker images {self.IMAGE_NAME} --format '{{{{.Repository}}}}:{{{{.Tag}}}}'",
82+
check=False
83+
)
84+
assert returncode == 0, "Failed to list Docker images"
85+
assert self.IMAGE_NAME in stdout, f"Docker image {self.IMAGE_NAME} not found"
86+
87+
def test_container_requires_api_key(self):
88+
"""Test that the container fails gracefully without SERPAPI_API_KEY."""
89+
returncode, stdout, stderr = run_command(
90+
f"timeout 3 docker run --rm {self.IMAGE_NAME}",
91+
timeout=5,
92+
check=False
93+
)
94+
# Container should exit with error when API key is missing
95+
output = stdout + stderr
96+
assert "SERPAPI_API_KEY" in output, \
97+
"Container should display error about missing SERPAPI_API_KEY"
98+
99+
def test_container_starts_with_api_key(self):
100+
"""Test that the container starts successfully with SERPAPI_API_KEY."""
101+
returncode, stdout, stderr = run_command(
102+
f"timeout 3 docker run --rm -e SERPAPI_API_KEY=test_key {self.IMAGE_NAME}",
103+
timeout=5,
104+
check=False
105+
)
106+
# Timeout (124) is expected for a long-running server
107+
assert returncode in [0, 124], \
108+
f"Container should start successfully or timeout. Got return code: {returncode}"
109+
110+
def test_container_python_version(self):
111+
"""Test that the container uses the correct Python version."""
112+
returncode, stdout, stderr = run_command(
113+
f"docker run --rm {self.IMAGE_NAME} python --version",
114+
timeout=5,
115+
check=False
116+
)
117+
assert returncode == 0, "Failed to get Python version"
118+
assert "Python 3.13" in stdout, \
119+
f"Expected Python 3.13, got: {stdout}"
120+
121+
def test_container_has_dependencies(self):
122+
"""Test that all required dependencies are installed."""
123+
# Map of pip install name to actual package name
124+
dependencies = {
125+
"google-search-results": "google_search_results",
126+
"mcp": "mcp",
127+
"python-dotenv": "python-dotenv",
128+
"httpx": "httpx"
129+
}
130+
131+
for install_name, package_name in dependencies.items():
132+
returncode, stdout, stderr = run_command(
133+
f"docker run --rm {self.IMAGE_NAME} pip show {install_name}",
134+
timeout=5,
135+
check=False
136+
)
137+
assert returncode == 0, f"Dependency {install_name} is not installed"
138+
# Check for the actual package name as reported by pip show
139+
assert f"Name: {package_name}" in stdout or f"Name: {install_name}" in stdout, \
140+
f"Dependency {install_name} not found in pip show output"
141+
142+
def test_server_module_exists(self):
143+
"""Test that the server module is accessible in the container."""
144+
# Check if the server file exists at the expected location
145+
returncode, stdout, stderr = run_command(
146+
f"docker run --rm {self.IMAGE_NAME} ls -la /app/src/serpapi-mcp-server/server.py",
147+
timeout=5,
148+
check=False
149+
)
150+
assert returncode == 0, "Server module file not found at expected location"
151+
assert "server.py" in stdout, "server.py not found in directory listing"
152+
153+
154+
if __name__ == "__main__":
155+
pytest.main([__file__, "-v", "-s"])

0 commit comments

Comments
 (0)