Skip to content
Draft
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
60 changes: 60 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Tests

on:
pull_request:

jobs:
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run unit tests
run: pytest -v -m "not integration" --tb=short

integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
services:
redis:
image: redis:alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"

- name: Install dependencies
run: pip install -r requirements.txt

- name: Load test data into Redis
run: |
python -c "
import asyncio
from node_normalizer.loader import NodeLoader
loader = NodeLoader('tests/data/config.json')
asyncio.run(loader.load(100_000))
"

- name: Run integration tests
run: pytest -v -m "integration" --tb=short
10 changes: 10 additions & 0 deletions docker-compose-redis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
redis:
image: redis:alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
13 changes: 7 additions & 6 deletions node_normalizer/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from itertools import islice
from datetime import datetime
from typing import Dict, Any
from typing import Dict, Any, Optional
import json
import hashlib
from itertools import combinations
Expand All @@ -26,8 +26,8 @@ class NodeLoader:
a redis database.
"""

def __init__(self):
self._config = self.get_config()
def __init__(self, config_file: Optional[Path] = None):
self._config = self.get_config(config_file)

self._compendium_directory: Path = Path(self._config["compendium_directory"])
self._conflation_directory: Path = Path(self._config["conflation_directory"])
Expand Down Expand Up @@ -58,11 +58,12 @@ def get_ancestors(self, input_type):
return ancs

@staticmethod
def get_config() -> Dict[str, Any]:
def get_config(config_file: Optional[Path] = None) -> Dict[str, Any]:
"""get configuration file"""
cname = Path(__file__).parents[1] / "config.json"
if config_file is None:
config_file = Path(__file__).parents[1] / "config.json"

with open(cname, "r") as json_file:
with open(config_file, "r") as json_file:
data = json.load(json_file)

return data
Expand Down
15 changes: 14 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,17 @@ log_cli=false
log_cli_level=DEBUG
log_file=tests.log
log_file_level=DEBUG
asyncio_mode=auto
asyncio_mode=auto

# Custom markers
markers =
integration: marks tests that require real Redis (run with: pytest -m integration)
callback_integration: marks tests requiring the callback-app Docker container

# WARNING: running plain `pytest` without a -m filter may fail or produce
# unpredictable results. test_norm.py and test_setid.py assign directly to
# app.state.* at module-import time (before any fixture runs), which permanently
# mutates the shared FastAPI app singleton. Use explicit marker filters instead:
#
# pytest -m "not integration" — unit tests only (no Redis required)
# pytest -m "integration" — integration tests only (requires Redis + data)
109 changes: 51 additions & 58 deletions requirements.lock
Original file line number Diff line number Diff line change
@@ -1,86 +1,79 @@
aioredis==1.3.1
annotated-types==0.7.0
anyio==3.7.1
asgiref==3.8.1
asgiref==3.7.2
async-timeout==4.0.3
attrs==23.2.0
bmt==1.4.3
bmt-lite-v3.1.0==2.2.2
bmt-lite-v3.6.0==2.3.0
certifi==2024.6.2
charset-normalizer==3.3.2
click==8.1.7
coverage==7.5.3
attrs==23.1.0
bmt==1.4.6
certifi==2023.7.22
charset-normalizer==2.1.1
click==8.3.1
coverage==7.3.2
curies==0.7.10
deepdiff==5.8.1
Deprecated==1.2.14
Deprecated==1.3.1
deprecation==2.1.0
docker==7.1.0
fastapi==0.108.0
googleapis-common-protos==1.59.1
grpcio==1.64.1
gunicorn==22.0.0
docker==6.1.3
fastapi==0.83.0
googleapis-common-protos==1.72.0
grpcio==1.78.0
gunicorn==20.1.0
h11==0.12.0
hbreader==0.9.1
hiredis==2.3.2
httpcore==0.15.0
httptools==0.6.1
httpx==0.23.0
idna==3.7
importlib-metadata==6.11.0
hiredis==2.2.3
httpcore==0.13.7
httptools==0.5.0
httpx==0.19.0
idna==3.4
importlib_metadata==8.7.1
iniconfig==2.0.0
isodate==0.6.1
json-flattener==0.1.9
jsonasobj2==1.0.4
jsonschema==4.6.2
linkml-runtime==1.8.0
opentelemetry-api==1.21.0
opentelemetry-exporter-jaeger==1.21.0
opentelemetry-exporter-jaeger-proto-grpc==1.21.0
opentelemetry-exporter-jaeger-thrift==1.21.0
opentelemetry-instrumentation==0.42b0
opentelemetry-instrumentation-asgi==0.42b0
opentelemetry-instrumentation-fastapi==0.42b0
opentelemetry-instrumentation-httpx==0.42b0
opentelemetry-sdk==1.21.0
opentelemetry-semantic-conventions==0.42b0
opentelemetry-util-http==0.42b0
linkml-runtime==1.10.0
opentelemetry-api==1.39.1
opentelemetry-exporter-otlp-proto-common==1.39.1
opentelemetry-exporter-otlp-proto-grpc==1.39.1
opentelemetry-instrumentation==0.60b1
opentelemetry-instrumentation-asgi==0.60b1
opentelemetry-instrumentation-fastapi==0.60b1
opentelemetry-instrumentation-httpx==0.60b1
opentelemetry-proto==1.39.1
opentelemetry-sdk==1.39.1
opentelemetry-semantic-conventions==0.60b1
opentelemetry-util-http==0.60b1
ordered-set==4.1.0
orjson==3.9.15
packaging==24.1
pluggy==1.5.0
orjson==3.8.10
packaging==23.2
pluggy==1.3.0
prefixcommons==0.1.12
prefixmaps==0.2.4
protobuf==4.25.3
prefixmaps==0.2.6
protobuf==6.33.5
py==1.11.0
pydantic==1.10.16
pydantic_core==2.18.4
pyparsing==3.1.2
pyrsistent==0.20.0
pydantic==1.10.13
pyparsing==3.3.2
pyrsistent==0.19.3
pytest==6.2.5
pytest-asyncio==0.18.3
pytest-cov==3.0.0
pytest-logging==2015.11.4
PyTrie==0.4.0
PyYAML==6.0.1
rdflib==7.0.0
reasoner-pydantic==4.1.5
PyYAML==6.0
rdflib==7.6.0
reasoner-pydantic==4.0.8
redis==3.5.3
redis-py-cluster==2.1.3
requests==2.31.0
requests==2.28.1
rfc3986==1.5.0
setuptools==70.1.0
six==1.16.0
sniffio==1.3.1
sniffio==1.3.0
sortedcontainers==2.4.0
starlette==0.32.0.post1
starlette==0.19.1
stringcase==1.2.0
testcontainers==3.6.1
thrift==0.20.0
toml==0.10.2
typing_extensions==4.12.2
urllib3==2.2.2
typing_extensions==4.15.0
urllib3==1.26.17
uvicorn==0.17.6
uvloop==0.19.0
wrapt==1.16.0
zipp==3.19.2
uvloop==0.17.0
websocket-client==1.6.4
wrapt==1.15.0
zipp==3.23.0
44 changes: 23 additions & 21 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
aioredis~=1.3.1
aioredis<2
click
bmt==1.4.3
deepdiff==8.6.1
fastapi~=0.108.0
bmt
deepdiff
anyio<4
fastapi==0.83.0
httptools
jsonschema~=4.6.0
pytest==6.2.5
pytest-cov==3.0.0
pytest-asyncio==0.18.3
pyyaml~=6.0
reasoner-pydantic==4.1.5
redis~=3.5.3
redis-py-cluster==2.1.3
requests==2.32.4
testcontainers==3.6.1
jsonschema
pytest
pytest-cov
pytest-asyncio
pyyaml
pydantic<2
reasoner-pydantic<5
redis
redis-py-cluster
requests
testcontainers
uvicorn
uvloop
gunicorn==23.0.0
orjson==3.9.15
httpx==0.23.0
gunicorn
orjson
httpx

# To support Open Telemetry
opentelemetry-sdk==1.27.0
opentelemetry-exporter-otlp-proto-grpc==1.27.0
opentelemetry-instrumentation-fastapi==0.48b0
opentelemetry-instrumentation-httpx==0.48b0
opentelemetry-sdk
opentelemetry-exporter-otlp-proto-grpc
opentelemetry-instrumentation-fastapi
opentelemetry-instrumentation-httpx
30 changes: 30 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Test Suite Notes

## Running tests

```bash
pytest -m "not integration" # unit tests — no Redis needed
pytest -m "integration" # integration tests — requires Redis on localhost:6379
```

Start Redis locally with:
```bash
docker compose -f docker-compose-redis.yml up -d
```

---

## Tests that do not currently pass

### `tests/test_loader.py::test_nn_load`

**Status:** Passes, but emits a `RuntimeWarning: coroutine 'NodeLoader.load_compendium' was never awaited`.

**Root cause:** `load_compendium` is an `async` method but `test_nn_load` calls it
synchronously: `assert node_loader.load_compendium(good_json, 5)`. This passes today
only because the return value of an unawaited coroutine is truthy. The actual loading
logic never executes.

**Estimated fix:** Refactor the test to `await` the call, or add a synchronous wrapper.
Low effort (~5 minutes) but requires understanding whether the test was intentionally
bypassing the async path via `_test_mode = 1`.
Loading