Skip to content

Commit 5ba972b

Browse files
authored
Merge pull request #23 from getmarkus/cm-branch-21
feat(tests): add environment markers and test-specific settings
2 parents 4b687f3 + ad04ca4 commit 5ba972b

File tree

10 files changed

+96
-20
lines changed

10 files changed

+96
-20
lines changed

.trunk/trunk.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ lint:
2323
2424
2525
26-
26+
2727
2828
2929
30-
30+
3131
3232
3333
actions:

app/interface_adapters/containers.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ class Container(containers.DeclarativeContainer):
1515
db_session = providers.Factory(Session, bind=db_engine)
1616

1717
# Repositories
18-
issue_repository = providers.Factory(
19-
(
20-
SQLModelIssueRepository
21-
if settings.execution_mode == "sqlmodel"
22-
else InMemoryIssueRepository
23-
),
24-
session=db_session if settings.execution_mode == "sqlmodel" else None,
18+
sqlmodel_repository = providers.Factory(SQLModelIssueRepository, session=db_session)
19+
20+
in_memory_repository = providers.Factory(InMemoryIssueRepository)
21+
22+
def get_model_config():
23+
return settings.model_config
24+
25+
issue_repository = providers.Selector(
26+
get_model_config,
27+
sqlmodel=sqlmodel_repository,
28+
**{"in-memory": in_memory_repository},
2529
)

app/resource_adapters/persistence/sqlmodel/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_engine(database_url: str | None = None) -> Engine:
4646
logger.info("SQLite WAL mode enabled")
4747

4848
# Initialize database if using SQLModel
49-
if settings.execution_mode == "sqlmodel" and not settings.migrate_database:
49+
if settings.model_config == "sqlmodel" and not settings.migrate_database:
5050
logger.info("Creating database tables...")
5151
SQLModel.metadata.create_all(_engine)
5252
logger.info("Database tables created successfully")

app/tests/conftest.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import os
12
from contextlib import asynccontextmanager
23

34
import pytest
5+
from _pytest.config import Config
6+
from _pytest.nodes import Item
47
from fastapi import FastAPI
58
from fastapi.testclient import TestClient
69
from sqlmodel import Session, delete
@@ -10,24 +13,63 @@
1013
from app.resource_adapters.persistence.sqlmodel.issues import Issue
1114

1215

16+
def pytest_configure(config: Config) -> None:
17+
"""Register env marker."""
18+
config.addinivalue_line(
19+
"markers", "env(name): mark test to run only on named environment"
20+
)
21+
22+
23+
def pytest_runtest_setup(item: Item) -> None:
24+
"""Set up environment for each test based on env marker."""
25+
envnames = [mark.args[0] for mark in item.iter_markers(name="env")]
26+
if envnames:
27+
# Set environment to the first env marker found
28+
os.environ["APP_ENV"] = envnames[0]
29+
else:
30+
# Default to testing environment if no env marker
31+
os.environ["APP_ENV"] = "testing"
32+
33+
34+
def pytest_unconfigure(config: Config) -> None:
35+
"""Clean up after each test."""
36+
# Remove test database if it exists
37+
if os.path.exists("test.db"):
38+
os.remove("test.db")
39+
40+
1341
@asynccontextmanager
1442
async def test_lifespan(app: FastAPI):
43+
"""Test-specific lifespan that sets up and tears down test resources."""
1544
yield
45+
# Cleanup will happen in pytest_unconfigure
1646

1747

1848
# Create test app using the factory with test lifespan
1949
app = create_app(lifespan_handler=test_lifespan)
2050

2151

2252
@pytest.fixture(name="session", autouse=True)
23-
def session_fixture():
53+
@pytest.mark.env("testing")
54+
def test_session():
55+
"""Session fixture for testing environment using test database."""
2456
with Session(get_engine()) as session:
2557
yield session
2658
statement = delete(Issue)
2759
session.exec(statement)
2860
session.commit()
2961

3062

63+
@pytest.fixture(name="session", autouse=True)
64+
@pytest.mark.env("development")
65+
def dev_session():
66+
"""Session fixture for development environment."""
67+
with Session(get_engine()) as session:
68+
yield session
69+
# In development, we might want to keep the data
70+
# or handle cleanup differently
71+
72+
3173
@pytest.fixture(name="client")
3274
def client_fixture():
3375
with TestClient(app) as client:

app/tests/test_issues.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from app.resource_adapters.persistence.sqlmodel.issues import SQLModelIssueRepository
99

1010

11+
@pytest.mark.env("testing")
1112
def test_analyze_issue_command(client: TestClient, session: Session):
1213
# Test case 1: Successful analysis
1314
issue_number = 1
@@ -25,6 +26,7 @@ def test_analyze_issue_command(client: TestClient, session: Session):
2526
assert response.issue_number == issue_number
2627

2728

29+
@pytest.mark.env("development")
2830
def test_analyze_issue_client(client: TestClient, session: Session):
2931
# Test case 1: Successful analysis
3032
issue_number = 1

app/tests/test_main.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
from main import app
55

6-
7-
86
# https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/
97
# @pytest.fixture(name="session")
108
# def session_fixture():
@@ -25,42 +23,47 @@ def client_fixture():
2523

2624
with TestClient(app) as client:
2725
yield client
28-
#app.dependency_overrides.clear()
26+
# app.dependency_overrides.clear()
2927

3028

3129
def test_root(client: TestClient):
3230
response = client.get("/")
3331
assert response.status_code == 200
3432
assert response.json()["app_name"] == "python-template"
3533

34+
3635
def test_health(client: TestClient):
3736
response = client.get("/health")
3837
assert response.status_code == 200
3938

39+
4040
def test_startup(client: TestClient):
4141
response = client.get("/startup")
4242
assert response.status_code == 200
4343

44+
4445
def test_readiness(client: TestClient):
4546
response = client.get("/readiness")
4647
assert response.status_code == 200
4748

49+
4850
def test_liveness(client: TestClient):
4951
response = client.get("/liveness")
5052
assert response.status_code == 200
5153

54+
5255
def test_smoke(client: TestClient):
5356
response = client.get("/smoke")
5457
assert response.status_code == 200
5558

59+
5660
def test_info(client: TestClient):
5761
response = client.get("/info")
5862
assert response.status_code == 200
5963
data = response.json()
6064
assert "app_name" in data
6165
assert data["app_name"] == "python-template"
6266
assert "system_time" in data
63-
assert "execution_mode" in data
67+
assert "model_config" in data
6468
assert "env_smoke_test" in data
6569
assert data["env_smoke_test"] == "configured"
66-

config.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313

1414
settings = Dynaconf(
1515
envvar_prefix="APP",
16-
settings_files=["settings.toml", ".secrets.toml"],
16+
settings_files=[
17+
"settings.toml", # Base settings
18+
".secrets.toml", # Secret settings
19+
"settings.test.toml", # Test-specific settings, loaded when APP_ENV=testing
20+
],
1721
environments=True,
1822
load_dotenv=True,
1923
validators=[
@@ -37,6 +41,6 @@ class Settings:
3741
database_url: str
3842
migrate_database: bool
3943
backend_cors_origins: List[AnyHttpUrl]
40-
execution_mode: str
44+
model_config: str
4145
env_smoke_test: str
4246
sqlite_wal_mode: bool = False

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ async def info() -> Dict[str, Any]:
153153
return {
154154
"app_name": settings.project_name,
155155
"system_time": datetime.datetime.now(),
156-
"execution_mode": settings.execution_mode,
156+
"model_config": settings.model_config,
157157
"env_smoke_test": settings.env_smoke_test,
158158
"env": settings.current_env,
159159
}

settings.test.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Test-specific settings that override settings.toml when APP_ENV=testing
2+
[default]
3+
# sqlite:///./test.db sqlite://
4+
database_url = "sqlite://"
5+
# in-memory, sqlmodel
6+
model_config = "sqlmodel"
7+
env_smoke_test = "configured"
8+
backend_cors_origins = ["http://testserver"]
9+
migrate_database = false
10+
sqlite_wal_mode = false
11+
12+
# You can still use environment-specific overrides in test settings
13+
[development]
14+
env_smoke_test = "development-test"
15+
16+
[production]
17+
env_smoke_test = "production-test"

settings.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
project_name = "python-template"
33
database_url = "sqlite:///./issues.db"
44
migrate_database = false
5-
execution_mode = "sqlmodel"
5+
# in-memory, sqlmodel
6+
model_config = "sqlmodel"
67
env_smoke_test = ""
78
sqlite_wal_mode = false
89

@@ -31,3 +32,6 @@ cors_allow_methods = [
3132
"PUT",
3233
"DELETE",
3334
] # Be more restrictive in production
35+
36+
[testing]
37+
# Test-specific settings

0 commit comments

Comments
 (0)