Skip to content

Commit ef1e4e5

Browse files
authored
test: add pytest framework and initial test cases (#18)
1 parent 258439c commit ef1e4e5

3 files changed

Lines changed: 157 additions & 0 deletions

File tree

tests/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Pytest configuration and fixtures."""
2+
3+
import os
4+
5+
import pytest
6+
7+
8+
@pytest.fixture(scope="session", autouse=True)
9+
def setup_test_env():
10+
"""Set up test environment variables for all tests."""
11+
os.environ["OPENAI_API_KEY"] = "test-key-for-testing"
12+
# Use PostgreSQL from docker-compose for testing
13+
# Default credentials match docker-compose.yml defaults
14+
# Note: Port 54320 is the host-mapped port from docker compose
15+
os.environ["DATABASE_URL"] = "postgresql+psycopg://postgres:postgres@localhost:54320/memu"

tests/test_env_validation.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""Test environment variable validation using subprocess isolation.
2+
3+
This module uses subprocess isolation to test module-level initialization errors.
4+
This approach avoids issues with shared module state and ensures clean test isolation.
5+
"""
6+
7+
import subprocess
8+
import sys
9+
from pathlib import Path
10+
11+
# Project root directory (parent of tests/)
12+
PROJECT_ROOT = Path(__file__).parent.parent
13+
14+
15+
def _run_import_test(env_vars: dict[str, str], remove_vars: list[str] | None = None) -> subprocess.CompletedProcess:
16+
"""
17+
Run a subprocess that attempts to import app.main with specified environment.
18+
19+
Args:
20+
env_vars: Environment variables to set
21+
remove_vars: Environment variables to remove (if any)
22+
23+
Returns:
24+
CompletedProcess with returncode, stdout, and stderr
25+
"""
26+
import os
27+
28+
# Start with a clean environment based on current env
29+
env = os.environ.copy()
30+
31+
# Remove specified variables
32+
if remove_vars:
33+
for var in remove_vars:
34+
env.pop(var, None)
35+
36+
# Set specified variables
37+
env.update(env_vars)
38+
39+
# Run Python subprocess that imports app.main
40+
result = subprocess.run(
41+
[sys.executable, "-c", "from app.main import app; print(app.title)"],
42+
env=env,
43+
capture_output=True,
44+
text=True,
45+
cwd=str(PROJECT_ROOT),
46+
timeout=30, # Prevent tests from hanging indefinitely
47+
)
48+
return result
49+
50+
51+
def test_app_requires_openai_api_key():
52+
"""Test that app refuses to start when OPENAI_API_KEY is not set."""
53+
result = _run_import_test(
54+
env_vars={
55+
"DATABASE_URL": "postgresql+psycopg://test:test@localhost:5432/test",
56+
},
57+
remove_vars=["OPENAI_API_KEY"],
58+
)
59+
60+
assert result.returncode != 0
61+
assert "OPENAI_API_KEY environment variable is not set or is empty" in result.stderr
62+
63+
64+
def test_app_refuses_empty_openai_api_key():
65+
"""Test that app refuses to start when OPENAI_API_KEY is empty."""
66+
result = _run_import_test(
67+
env_vars={
68+
"OPENAI_API_KEY": "",
69+
"DATABASE_URL": "postgresql+psycopg://test:test@localhost:5432/test",
70+
},
71+
)
72+
73+
assert result.returncode != 0
74+
assert "OPENAI_API_KEY environment variable is not set or is empty" in result.stderr
75+
76+
77+
def test_app_requires_database_url():
78+
"""Test that app refuses to start when DATABASE_URL is not set."""
79+
result = _run_import_test(
80+
env_vars={
81+
"OPENAI_API_KEY": "test-key",
82+
},
83+
remove_vars=["DATABASE_URL", "DATABASE_HOST", "DATABASE_USER", "DATABASE_PASSWORD", "DATABASE_NAME"],
84+
)
85+
86+
assert result.returncode != 0
87+
assert "Database configuration is incomplete" in result.stderr
88+
89+
90+
def test_app_with_individual_db_vars():
91+
"""Test that app starts with individual DATABASE_* variables."""
92+
result = _run_import_test(
93+
env_vars={
94+
"OPENAI_API_KEY": "test-key",
95+
"DATABASE_HOST": "localhost",
96+
"DATABASE_PORT": "54320",
97+
"DATABASE_USER": "test_user",
98+
"DATABASE_PASSWORD": "test_pass",
99+
"DATABASE_NAME": "test_db",
100+
},
101+
remove_vars=["DATABASE_URL"],
102+
)
103+
104+
assert result.returncode == 0
105+
assert "memU Server" in result.stdout
106+
107+
108+
def test_app_starts_with_valid_openai_api_key():
109+
"""Test that app starts successfully with valid OPENAI_API_KEY."""
110+
result = _run_import_test(
111+
env_vars={
112+
"OPENAI_API_KEY": "test-valid-key",
113+
"DATABASE_URL": "postgresql+psycopg://test:test@localhost:5432/test",
114+
},
115+
)
116+
117+
assert result.returncode == 0
118+
assert "memU Server" in result.stdout

tests/test_health.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Tests for the application's root ("/") endpoint."""
2+
3+
import pytest
4+
from fastapi.testclient import TestClient
5+
6+
7+
@pytest.fixture(scope="module")
8+
def client():
9+
"""Create FastAPI test client with proper env setup."""
10+
try:
11+
from app.main import app
12+
13+
return TestClient(app)
14+
except Exception as exc:
15+
pytest.skip(f"Could not initialize test client due to application setup error: {exc}")
16+
17+
18+
def test_root_endpoint(client):
19+
"""Test root endpoint returns welcome message."""
20+
response = client.get("/")
21+
assert response.status_code == 200
22+
data = response.json()
23+
assert "message" in data
24+
assert data["message"] == "Hello MemU user!"

0 commit comments

Comments
 (0)