Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0af2166
Merge pull request #182 from ndif-team/dev
JadenFiotto-Kaufman Oct 24, 2025
d6d7c91
Merge pull request #183 from ndif-team/dev
JadenFiotto-Kaufman Oct 24, 2025
d8ea660
Merge pull request #184 from ndif-team/dev
JadenFiotto-Kaufman Oct 24, 2025
142768a
Merge pull request #185 from ndif-team/dev
JadenFiotto-Kaufman Oct 24, 2025
a1b66fa
Merge pull request #186 from ndif-team/dev
JadenFiotto-Kaufman Oct 24, 2025
794fd43
Merge pull request #187 from ndif-team/dev
JadenFiotto-Kaufman Oct 28, 2025
3e99ef6
install and configure pytest for api service
michaelwaves Oct 28, 2025
28cd6ec
pin pytest version
michaelwaves Oct 28, 2025
737e2d7
add test_ping
michaelwaves Oct 28, 2025
374074d
Merge pull request #189 from ndif-team/dev
JadenFiotto-Kaufman Oct 29, 2025
26ebf94
update readme
michaelwaves Oct 29, 2025
b52ed01
Merge pull request #190 from ndif-team/dev
JadenFiotto-Kaufman Oct 31, 2025
43b32f2
Merge pull request #192 from ndif-team/dev
JadenFiotto-Kaufman Oct 31, 2025
5c5ce15
add pandas to whitelisted modules
michaelwaves Nov 1, 2025
ec4db0d
Merge pull request #193 from michaelwaves/nnsight_issue/536
JadenFiotto-Kaufman Nov 6, 2025
72b4975
Merge pull request #205 from ndif-team/dev
JadenFiotto-Kaufman Nov 13, 2025
9fa1cb8
move pytest dependencies to pyproject.toml
michaelwaves Nov 14, 2025
9ae7c6b
move all tests to unit subdirectory for future e2e testing
michaelwaves Nov 17, 2025
b373667
add test_dependencies.py
michaelwaves Nov 17, 2025
3b07d7b
add test_cache_maintainer in test_queue_util.py
michaelwaves Nov 17, 2025
8ed0d2b
add test_providers_util.py
michaelwaves Nov 17, 2025
5abcd46
Merge branch 'ndif-team:main' into issues/172
michaelwaves Nov 17, 2025
5a699c9
add readme
michaelwaves Nov 17, 2025
d8cc7b9
update readme
michaelwaves Nov 17, 2025
46b8b57
remove uv.lock
michaelwaves Nov 18, 2025
833fdef
update readme to remove uv and make pytest commands generic
michaelwaves Nov 18, 2025
4165228
update README.md to point to pytest docs
michaelwaves Nov 20, 2025
5eb4d09
update api service README.md to include pyproject.toml deps
michaelwaves Nov 20, 2025
aaa7b7f
update pyproject.toml
michaelwaves Nov 20, 2025
47ef6d7
remove test_queue_util.py
michaelwaves Nov 20, 2025
530c29e
remove unused asyncio in test_dependencies.py
michaelwaves Nov 20, 2025
74e0241
remove test_provider_util.py
michaelwaves Nov 20, 2025
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ You should expect to see a message like `Application startup complete.` in the a
python scripts/test.py
```

For more comprehensive testing, install pytest
```sh
conda activate ndif-dev
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of assuming a package manager and instructing them how to install it, just directly link to pytest docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

pip install pytest
```
Navigate to the service you wish to test
```sh
cd src/services/api
pytest
```


This will send a test NNsight request to the API service running in the local container.

## Additional Commands
Expand Down
1 change: 1 addition & 0 deletions src/services/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.egg-info
15 changes: 15 additions & 0 deletions src/services/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## NDIF API Service

## Testing

install dependencies from requirements.in, ideally in a virtual environment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also the dependencies in the pyproject.toml


run pytests with
```bash
pytest
```

run pytests in certain file with
```bash
pytest -k test_app
```
23 changes: 23 additions & 0 deletions src/services/api/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "api"
version = "0.1.0"
requires-python = ">=3.10"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically right now we only run NDIF with python 3.12

[tool.setuptools.packages.find]
where = ["."]
include = ["src*"]

[tool.pytest.ini_options]
minversion = "6.0"
testpaths = ["tests"]
pythonpath = ["."]

[dependency-groups]
dev = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "tests" would be a more appropriate dependency group name

"httpx>=0.28.1",
"pytest>=8.4.0",
"pytest-asyncio>=1.3.0",
]
13 changes: 13 additions & 0 deletions src/services/api/tests/unit/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import os
os.environ["DEV_MODE"] = "true"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You set this here, but also directly in some of the tests, was this intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the two tests were specifically testing the behavior in dev mode so I wanted it to be explicit. The top level DEV_MODE is just to avoid start the db connection in db.py. In e2e testing we might want to set this to be false with a separate conftest.py

os.environ.setdefault("BROKER_URL", "redis://localhost:6379")

from unittest.mock import AsyncMock, Mock, patch

mock_redis_manager = Mock()
mock_redis_client = AsyncMock()
mock_socket_manager = Mock()

patch("socketio.AsyncRedisManager", return_value=mock_redis_manager).start()
patch("redis.asyncio.Redis.from_url", return_value=mock_redis_client).start()
patch("fastapi_socketio.SocketManager", return_value=mock_socket_manager).start()
11 changes: 11 additions & 0 deletions src/services/api/tests/unit/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pytest
from httpx import AsyncClient, ASGITransport
from src.app import app


@pytest.mark.asyncio
async def test_ping():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get("/ping")
assert response.status_code == 200
assert response.json() == "pong"
33 changes: 33 additions & 0 deletions src/services/api/tests/unit/test_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import asyncio
from unittest.mock import patch
from src.dependencies import check_hotswapping_access, validate_nnsight_version, validate_python_version, authenticate_api_key
import pytest
import os


@pytest.mark.asyncio
async def test_validate_python_version():
with patch("sys.version", "3.12.10"):
res = await validate_python_version("3.12")
assert res == "3.12"


@pytest.mark.asyncio
async def test_validate_nnsight_version():
os.environ["MIN_NNSIGHT_VERSION"] = "0.5.10"
res = await validate_nnsight_version("0.5.10")
assert res == "0.5.10"


@pytest.mark.asyncio
async def test_authenticate_api_key_dev_mode():
os.environ["DEV_MODE"] = "true"
res = await authenticate_api_key("test-key")
assert res == "test-key"


@pytest.mark.asyncio
async def test_check_hotswapping_access_dev_mode():
os.environ["DEV_MODE"] = "true"
res = await check_hotswapping_access("test-key")
assert res == True
10 changes: 10 additions & 0 deletions src/services/api/tests/unit/test_providers_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from unittest.mock import MagicMock, patch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repo structure is a little confusing, but this technically corresponds to src/common, so this test isn't needed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. I removed the file for now since there are no other functions to test in providers/util.py. Are there any api specific utils we should test or is this fine for now?


from src.providers.util import verify_connection


def test_verify_connection_success():
with patch("socket.create_connection") as mock_conn:
mock_conn.return_value.__enter__.return_value = MagicMock()
assert verify_connection("127.0.0.1", 8000) is True
mock_conn.assert_called_once_with(("127.0.0.1", 8000), timeout=2)
28 changes: 28 additions & 0 deletions src/services/api/tests/unit/test_queue_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import time
from functools import lru_cache
from src.queue.util import cache_maintainer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper function is getting removed in #210

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm it seems like there are a lot of changes happening to queue/util.py, maybe we should just delete test_queue_util.py for now?



def test_cache_maintainer():
calls = {"count": 0}

@cache_maintainer(1) # clear every 1 second
@lru_cache(None)
def compute(x):
calls["count"] += 1
return x * 2

# First call — calls function
assert compute(5) == 10
assert calls["count"] == 1

# Second call immediately — should use cache
assert compute(5) == 10
assert calls["count"] == 1 # NO new calls

# Wait for 1 second → decorator clears cache
time.sleep(1.1)

# Next call — should trigger a new computation
assert compute(5) == 10
assert calls["count"] == 2
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def check(self, name: str) -> bool:
WhitelistedModule(name="typing", strict=False),
WhitelistedModule(name="_operator", strict=True),
WhitelistedModule(name="operator", strict=True),
WhitelistedModule(name="pandas", strict = False),
]

# Modules allowed during deserialization
Expand Down