Skip to content

Commit 1d43077

Browse files
gauravclaude
andcommitted
Add GitHub Actions integration test pipeline.
- New .github/workflows/test.yml with parallel unit-tests and integration-tests jobs; integration job uses a Redis service container on port 6379. - New docker-compose-redis.yml for running Redis locally during development. - New tests/test_integration.py with 27 tests across 6 classes (status, get_normalized_nodes, semantic_types, curie_prefixes, setid, query, conflations), all marked pytest.mark.integration; data-loading placeholder present for Phase 2. - Updated tests/conftest.py to add session-scoped integration_client fixture with Redis availability check and TestClient context manager. - Updated tests/test_callback.py to skip test_async_query_callback (requires external callback-app container). - Updated pytest.ini to register integration and callback_integration markers and document the plain-pytest limitation from test_norm/test_setid module-level app.state mutation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 74a0c32 commit 1d43077

6 files changed

Lines changed: 486 additions & 1 deletion

File tree

.github/workflows/test.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
unit-tests:
9+
name: Unit Tests
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Set up Python 3.11
15+
uses: actions/setup-python@v5
16+
with:
17+
python-version: "3.11"
18+
cache: "pip"
19+
20+
- name: Install dependencies
21+
run: pip install -r requirements.txt
22+
23+
- name: Run unit tests
24+
run: pytest -v -m "not integration" --tb=short
25+
26+
integration-tests:
27+
name: Integration Tests
28+
runs-on: ubuntu-latest
29+
services:
30+
redis:
31+
image: redis:alpine
32+
ports:
33+
- 6379:6379
34+
options: >-
35+
--health-cmd "redis-cli ping"
36+
--health-interval 5s
37+
--health-timeout 3s
38+
--health-retries 10
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Set up Python 3.11
43+
uses: actions/setup-python@v5
44+
with:
45+
python-version: "3.11"
46+
cache: "pip"
47+
48+
- name: Install dependencies
49+
run: pip install -r requirements.txt
50+
51+
# PLACEHOLDER: create tests/data/config.json and load test data into Redis
52+
# Example (fill in once tests/data/config.json is finalized):
53+
# python -c "
54+
# import asyncio
55+
# from node_normalizer.loader import NodeLoader
56+
# loader = NodeLoader('tests/data/config.json')
57+
# asyncio.run(loader.load(100_000))
58+
# "
59+
60+
- name: Run integration tests
61+
run: pytest -v -m "integration" --tb=short

docker-compose-redis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
redis:
3+
image: redis:alpine
4+
ports:
5+
- "6379:6379"
6+
healthcheck:
7+
test: ["CMD", "redis-cli", "ping"]
8+
interval: 5s
9+
timeout: 3s
10+
retries: 10

pytest.ini

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,17 @@ log_cli=false
33
log_cli_level=DEBUG
44
log_file=tests.log
55
log_file_level=DEBUG
6-
asyncio_mode=auto
6+
asyncio_mode=auto
7+
8+
# Custom markers
9+
markers =
10+
integration: marks tests that require real Redis (run with: pytest -m integration)
11+
callback_integration: marks tests requiring the callback-app Docker container
12+
13+
# WARNING: running plain `pytest` without a -m filter may fail or produce
14+
# unpredictable results. test_norm.py and test_setid.py assign directly to
15+
# app.state.* at module-import time (before any fixture runs), which permanently
16+
# mutates the shared FastAPI app singleton. Use explicit marker filters instead:
17+
#
18+
# pytest -m "not integration" — unit tests only (no Redis required)
19+
# pytest -m "integration" — integration tests only (requires Redis + data)

tests/conftest.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import sys
22
import os
33
import time
4+
import socket
45

56
import pytest
67
import logging
78
from testcontainers.compose import DockerCompose
89
from testcontainers.core.docker_client import DockerClient
10+
from fastapi.testclient import TestClient
911

1012
from node_normalizer.util import LoggingUtil
1113

@@ -49,3 +51,44 @@ def stop():
4951
request.addfinalizer(stop)
5052

5153
return compose, nn_url, callback_url
54+
55+
56+
@pytest.fixture(scope="session")
57+
def integration_client():
58+
"""
59+
Session-scoped fixture for integration tests.
60+
61+
Requires Redis running on localhost:6379. In CI this is provided by the
62+
GitHub Actions services.redis block in .github/workflows/test.yml.
63+
Locally: docker compose -f docker-compose-redis.yml up -d
64+
65+
The TestClient context manager triggers startup_event(), which establishes
66+
real Redis connections via redis_config.yaml. All integration tests share
67+
this single session; tests must be read-only (no writes to Redis).
68+
69+
Note: startup_event also downloads the Biolink Model YAML from GitHub via
70+
bmt. This is slow on the first run; bmt caches it in ~/.cache afterward.
71+
"""
72+
# Verify Redis is reachable before starting the app; fail fast with a clear
73+
# message rather than a cryptic connection error from deep inside the app.
74+
redis_host, redis_port = "127.0.0.1", 6379
75+
try:
76+
with socket.create_connection((redis_host, redis_port), timeout=5):
77+
pass
78+
except OSError as exc:
79+
raise RuntimeError(
80+
f"Integration tests require Redis on {redis_host}:{redis_port}. "
81+
"Start it with: docker compose -f docker-compose-redis.yml up -d"
82+
) from exc
83+
84+
# PLACEHOLDER: load test data into Redis before starting the app.
85+
# Fill this in once tests/data/config.json is finalized, e.g.:
86+
#
87+
# import asyncio
88+
# from node_normalizer.loader import NodeLoader
89+
# loader = NodeLoader("tests/data/config.json")
90+
# asyncio.run(loader.load(100_000))
91+
92+
from node_normalizer.server import app
93+
with TestClient(app) as client:
94+
yield client

tests/test_callback.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
import time
44

5+
import pytest
56
import reasoner_pydantic
67
import requests
78
import fastapi
@@ -14,6 +15,7 @@
1415
premerged_response = Path(__file__).parent / "resources" / "premerged_response.json"
1516

1617

18+
@pytest.mark.skip(reason="Requires external callback-app container. See docker-compose-test.yml.")
1719
def test_async_query_callback(session):
1820

1921
# host_url = "http://r3:8080/asyncquery"

0 commit comments

Comments
 (0)