(Feat): Initial Commit.
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
351
tests/test_container.py
Normal file
351
tests/test_container.py
Normal file
@@ -0,0 +1,351 @@
|
||||
"""Tests for router container."""
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
from fastapi_route_loader.container import RouterContainer
|
||||
from fastapi_route_loader.events import (
|
||||
RouterEventType,
|
||||
RouterLoadedEvent,
|
||||
RouterUnloadedEvent,
|
||||
RouterUpdatedEvent,
|
||||
)
|
||||
|
||||
|
||||
class TestRouterContainer:
|
||||
"""Test router container functionality."""
|
||||
|
||||
def test_add_router(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
|
||||
container.add_router("test_router", router)
|
||||
|
||||
assert container.has_router("test_router")
|
||||
assert container.get_router("test_router") is router
|
||||
assert len(container) == 1
|
||||
|
||||
def test_add_router_duplicate_raises_error(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
|
||||
container.add_router("test_router", router)
|
||||
|
||||
with pytest.raises(ValueError, match="Router 'test_router' already exists"):
|
||||
container.add_router("test_router", router)
|
||||
|
||||
def test_add_router_dispatches_event(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event):
|
||||
events_received.append(event)
|
||||
|
||||
container.on(RouterEventType.LOADED, handler)
|
||||
container.add_router("test_router", router, {"key": "value"})
|
||||
|
||||
assert len(events_received) == 1
|
||||
event = events_received[0]
|
||||
assert isinstance(event, RouterLoadedEvent)
|
||||
assert event.router_name == "test_router"
|
||||
assert event.router is router
|
||||
assert event.metadata == {"key": "value"}
|
||||
|
||||
def test_remove_router(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
|
||||
container.add_router("test_router", router)
|
||||
removed_router = container.remove_router("test_router")
|
||||
|
||||
assert removed_router is router
|
||||
assert not container.has_router("test_router")
|
||||
assert len(container) == 0
|
||||
|
||||
def test_remove_router_not_found_raises_error(self):
|
||||
container = RouterContainer()
|
||||
|
||||
with pytest.raises(KeyError, match="Router 'nonexistent' not found"):
|
||||
container.remove_router("nonexistent")
|
||||
|
||||
def test_remove_router_dispatches_event(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event):
|
||||
events_received.append(event)
|
||||
|
||||
container.add_router("test_router", router)
|
||||
container.on(RouterEventType.UNLOADED, handler)
|
||||
container.remove_router("test_router", {"reason": "cleanup"})
|
||||
|
||||
assert len(events_received) == 1
|
||||
event = events_received[0]
|
||||
assert isinstance(event, RouterUnloadedEvent)
|
||||
assert event.router_name == "test_router"
|
||||
assert event.router is router
|
||||
assert event.metadata == {"reason": "cleanup"}
|
||||
|
||||
def test_update_router(self):
|
||||
container = RouterContainer()
|
||||
old_router = APIRouter()
|
||||
new_router = APIRouter()
|
||||
|
||||
container.add_router("test_router", old_router)
|
||||
container.update_router("test_router", new_router)
|
||||
|
||||
assert container.get_router("test_router") is new_router
|
||||
|
||||
def test_update_router_not_found_raises_error(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
|
||||
with pytest.raises(KeyError, match="Router 'nonexistent' not found"):
|
||||
container.update_router("nonexistent", router)
|
||||
|
||||
def test_update_router_dispatches_event(self):
|
||||
container = RouterContainer()
|
||||
old_router = APIRouter()
|
||||
new_router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event):
|
||||
events_received.append(event)
|
||||
|
||||
container.add_router("test_router", old_router)
|
||||
container.on(RouterEventType.UPDATED, handler)
|
||||
container.update_router("test_router", new_router, {"version": "2.0"})
|
||||
|
||||
assert len(events_received) == 1
|
||||
event = events_received[0]
|
||||
assert isinstance(event, RouterUpdatedEvent)
|
||||
assert event.router_name == "test_router"
|
||||
assert event.router is new_router
|
||||
assert event.old_router is old_router
|
||||
assert event.metadata == {"version": "2.0"}
|
||||
|
||||
def test_get_router_not_found_raises_error(self):
|
||||
container = RouterContainer()
|
||||
|
||||
with pytest.raises(KeyError, match="Router 'nonexistent' not found"):
|
||||
container.get_router("nonexistent")
|
||||
|
||||
def test_exclude_routers(self):
|
||||
container = RouterContainer()
|
||||
router1 = APIRouter()
|
||||
router2 = APIRouter()
|
||||
router3 = APIRouter()
|
||||
|
||||
container.add_router("router1", router1)
|
||||
container.add_router("router2", router2)
|
||||
container.add_router("router3", router3)
|
||||
|
||||
container.exclude("router2")
|
||||
|
||||
assert container.is_active("router1")
|
||||
assert not container.is_active("router2")
|
||||
assert container.is_active("router3")
|
||||
|
||||
active_routers = container.get_active_routers()
|
||||
assert "router1" in active_routers
|
||||
assert "router2" not in active_routers
|
||||
assert "router3" in active_routers
|
||||
|
||||
def test_include_routers(self):
|
||||
container = RouterContainer()
|
||||
router1 = APIRouter()
|
||||
router2 = APIRouter()
|
||||
router3 = APIRouter()
|
||||
|
||||
container.add_router("router1", router1)
|
||||
container.add_router("router2", router2)
|
||||
container.add_router("router3", router3)
|
||||
|
||||
container.include("router1", "router3")
|
||||
|
||||
assert container.is_active("router1")
|
||||
assert not container.is_active("router2")
|
||||
assert container.is_active("router3")
|
||||
|
||||
active_routers = container.get_active_routers()
|
||||
assert "router1" in active_routers
|
||||
assert "router2" not in active_routers
|
||||
assert "router3" in active_routers
|
||||
|
||||
def test_clear_filters(self):
|
||||
container = RouterContainer()
|
||||
router1 = APIRouter()
|
||||
router2 = APIRouter()
|
||||
|
||||
container.add_router("router1", router1)
|
||||
container.add_router("router2", router2)
|
||||
|
||||
container.exclude("router1")
|
||||
assert not container.is_active("router1")
|
||||
|
||||
container.clear_filters()
|
||||
assert container.is_active("router1")
|
||||
assert container.is_active("router2")
|
||||
|
||||
def test_get_all_routers(self):
|
||||
container = RouterContainer()
|
||||
router1 = APIRouter()
|
||||
router2 = APIRouter()
|
||||
|
||||
container.add_router("router1", router1)
|
||||
container.add_router("router2", router2)
|
||||
container.exclude("router1")
|
||||
|
||||
all_routers = container.get_all_routers()
|
||||
assert len(all_routers) == 2
|
||||
assert "router1" in all_routers
|
||||
assert "router2" in all_routers
|
||||
|
||||
def test_load_from_module(self, tmp_path):
|
||||
module_content = '''
|
||||
from fastapi import APIRouter
|
||||
|
||||
test_router = APIRouter()
|
||||
'''
|
||||
module_file = tmp_path / "test_module.py"
|
||||
module_file.write_text(module_content)
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
container = RouterContainer()
|
||||
container.load_from_module("test_module")
|
||||
|
||||
assert container.has_router("test_router")
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
if "test_module" in sys.modules:
|
||||
del sys.modules["test_module"]
|
||||
|
||||
def test_load_from_directory(self, tmp_path):
|
||||
routers_dir = tmp_path / "routers"
|
||||
routers_dir.mkdir()
|
||||
|
||||
(routers_dir / "__init__.py").write_text("")
|
||||
(routers_dir / "users.py").write_text(
|
||||
"from fastapi import APIRouter\nuser_router = APIRouter()"
|
||||
)
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
container = RouterContainer()
|
||||
container.load_from_directory(str(routers_dir), "routers")
|
||||
|
||||
assert container.has_router("users.user_router")
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
for mod in list(sys.modules.keys()):
|
||||
if mod.startswith("routers"):
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_register_to_app(self):
|
||||
container = RouterContainer()
|
||||
router1 = APIRouter()
|
||||
router2 = APIRouter()
|
||||
|
||||
container.add_router("router1", router1)
|
||||
container.add_router("router2", router2)
|
||||
container.exclude("router2")
|
||||
|
||||
app = FastAPI()
|
||||
container.register_to_app(app)
|
||||
|
||||
assert len(app.routes) > 0
|
||||
|
||||
def test_register_to_app_with_prefix(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
router.get("/")(lambda: {"message": "test"})
|
||||
|
||||
container.add_router("router", router)
|
||||
|
||||
app = FastAPI()
|
||||
container.register_to_app(app, prefix="/api")
|
||||
|
||||
route_paths = [route.path for route in app.routes]
|
||||
assert any("/api" in path for path in route_paths)
|
||||
|
||||
def test_event_subscription(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event):
|
||||
events_received.append(event)
|
||||
|
||||
container.on(None, handler)
|
||||
container.add_router("test_router", router)
|
||||
container.remove_router("test_router")
|
||||
|
||||
assert len(events_received) == 2
|
||||
|
||||
def test_event_unsubscription(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event):
|
||||
events_received.append(event)
|
||||
|
||||
container.on(RouterEventType.LOADED, handler)
|
||||
container.off(RouterEventType.LOADED, handler)
|
||||
container.add_router("test_router", router)
|
||||
|
||||
assert len(events_received) == 0
|
||||
|
||||
def test_clear_handlers(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event):
|
||||
events_received.append(event)
|
||||
|
||||
container.on(RouterEventType.LOADED, handler)
|
||||
container.clear_handlers()
|
||||
container.add_router("test_router", router)
|
||||
|
||||
assert len(events_received) == 0
|
||||
|
||||
def test_container_len(self):
|
||||
container = RouterContainer()
|
||||
assert len(container) == 0
|
||||
|
||||
container.add_router("router1", APIRouter())
|
||||
assert len(container) == 1
|
||||
|
||||
container.add_router("router2", APIRouter())
|
||||
assert len(container) == 2
|
||||
|
||||
container.remove_router("router1")
|
||||
assert len(container) == 1
|
||||
|
||||
def test_container_contains(self):
|
||||
container = RouterContainer()
|
||||
router = APIRouter()
|
||||
|
||||
container.add_router("test_router", router)
|
||||
|
||||
assert "test_router" in container
|
||||
assert "nonexistent" not in container
|
||||
|
||||
def test_container_iter(self):
|
||||
container = RouterContainer()
|
||||
|
||||
container.add_router("router1", APIRouter())
|
||||
container.add_router("router2", APIRouter())
|
||||
container.add_router("router3", APIRouter())
|
||||
|
||||
router_names = list(container)
|
||||
assert "router1" in router_names
|
||||
assert "router2" in router_names
|
||||
assert "router3" in router_names
|
||||
assert len(router_names) == 3
|
||||
190
tests/test_events.py
Normal file
190
tests/test_events.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""Tests for event system."""
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter
|
||||
|
||||
from fastapi_route_loader.events import (
|
||||
EventDispatcher,
|
||||
RouterEvent,
|
||||
RouterEventType,
|
||||
RouterLoadedEvent,
|
||||
RouterUnloadedEvent,
|
||||
RouterUpdatedEvent,
|
||||
)
|
||||
|
||||
|
||||
class TestRouterEvents:
|
||||
"""Test router event classes."""
|
||||
|
||||
def test_router_loaded_event(self):
|
||||
router = APIRouter()
|
||||
event = RouterLoadedEvent("test_router", router, {"key": "value"})
|
||||
|
||||
assert event.event_type == RouterEventType.LOADED
|
||||
assert event.router_name == "test_router"
|
||||
assert event.router is router
|
||||
assert event.metadata == {"key": "value"}
|
||||
|
||||
def test_router_loaded_event_no_metadata(self):
|
||||
router = APIRouter()
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
|
||||
assert event.event_type == RouterEventType.LOADED
|
||||
assert event.metadata == {}
|
||||
|
||||
def test_router_unloaded_event(self):
|
||||
router = APIRouter()
|
||||
event = RouterUnloadedEvent("test_router", router, {"reason": "cleanup"})
|
||||
|
||||
assert event.event_type == RouterEventType.UNLOADED
|
||||
assert event.router_name == "test_router"
|
||||
assert event.router is router
|
||||
assert event.metadata == {"reason": "cleanup"}
|
||||
|
||||
def test_router_updated_event(self):
|
||||
old_router = APIRouter()
|
||||
new_router = APIRouter()
|
||||
event = RouterUpdatedEvent(
|
||||
"test_router", new_router, old_router, {"version": "2.0"}
|
||||
)
|
||||
|
||||
assert event.event_type == RouterEventType.UPDATED
|
||||
assert event.router_name == "test_router"
|
||||
assert event.router is new_router
|
||||
assert event.old_router is old_router
|
||||
assert event.metadata == {"version": "2.0"}
|
||||
|
||||
def test_event_immutability(self):
|
||||
router = APIRouter()
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
event.router_name = "new_name"
|
||||
|
||||
|
||||
class TestEventDispatcher:
|
||||
"""Test event dispatcher."""
|
||||
|
||||
def test_subscribe_and_dispatch(self):
|
||||
dispatcher = EventDispatcher()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event: RouterEvent) -> None:
|
||||
events_received.append(event)
|
||||
|
||||
dispatcher.subscribe(RouterEventType.LOADED, handler)
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
dispatcher.dispatch(event)
|
||||
|
||||
assert len(events_received) == 1
|
||||
assert events_received[0] is event
|
||||
|
||||
def test_subscribe_to_all_events(self):
|
||||
dispatcher = EventDispatcher()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event: RouterEvent) -> None:
|
||||
events_received.append(event)
|
||||
|
||||
dispatcher.subscribe(None, handler)
|
||||
|
||||
loaded_event = RouterLoadedEvent("test_router", router)
|
||||
dispatcher.dispatch(loaded_event)
|
||||
|
||||
unloaded_event = RouterUnloadedEvent("test_router", router)
|
||||
dispatcher.dispatch(unloaded_event)
|
||||
|
||||
assert len(events_received) == 2
|
||||
assert events_received[0] is loaded_event
|
||||
assert events_received[1] is unloaded_event
|
||||
|
||||
def test_multiple_handlers(self):
|
||||
dispatcher = EventDispatcher()
|
||||
router = APIRouter()
|
||||
handler1_calls = []
|
||||
handler2_calls = []
|
||||
|
||||
def handler1(event: RouterEvent) -> None:
|
||||
handler1_calls.append(event)
|
||||
|
||||
def handler2(event: RouterEvent) -> None:
|
||||
handler2_calls.append(event)
|
||||
|
||||
dispatcher.subscribe(RouterEventType.LOADED, handler1)
|
||||
dispatcher.subscribe(RouterEventType.LOADED, handler2)
|
||||
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
dispatcher.dispatch(event)
|
||||
|
||||
assert len(handler1_calls) == 1
|
||||
assert len(handler2_calls) == 1
|
||||
|
||||
def test_unsubscribe(self):
|
||||
dispatcher = EventDispatcher()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event: RouterEvent) -> None:
|
||||
events_received.append(event)
|
||||
|
||||
dispatcher.subscribe(RouterEventType.LOADED, handler)
|
||||
dispatcher.unsubscribe(RouterEventType.LOADED, handler)
|
||||
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
dispatcher.dispatch(event)
|
||||
|
||||
assert len(events_received) == 0
|
||||
|
||||
def test_unsubscribe_global_handler(self):
|
||||
dispatcher = EventDispatcher()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event: RouterEvent) -> None:
|
||||
events_received.append(event)
|
||||
|
||||
dispatcher.subscribe(None, handler)
|
||||
dispatcher.unsubscribe(None, handler)
|
||||
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
dispatcher.dispatch(event)
|
||||
|
||||
assert len(events_received) == 0
|
||||
|
||||
def test_clear_handlers(self):
|
||||
dispatcher = EventDispatcher()
|
||||
router = APIRouter()
|
||||
events_received = []
|
||||
|
||||
def handler(event: RouterEvent) -> None:
|
||||
events_received.append(event)
|
||||
|
||||
dispatcher.subscribe(RouterEventType.LOADED, handler)
|
||||
dispatcher.subscribe(None, handler)
|
||||
dispatcher.clear()
|
||||
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
dispatcher.dispatch(event)
|
||||
|
||||
assert len(events_received) == 0
|
||||
|
||||
def test_handler_exception_does_not_stop_other_handlers(self):
|
||||
dispatcher = EventDispatcher()
|
||||
router = APIRouter()
|
||||
handler2_calls = []
|
||||
|
||||
def handler1(event: RouterEvent) -> None:
|
||||
raise ValueError("Handler error")
|
||||
|
||||
def handler2(event: RouterEvent) -> None:
|
||||
handler2_calls.append(event)
|
||||
|
||||
dispatcher.subscribe(RouterEventType.LOADED, handler1)
|
||||
dispatcher.subscribe(RouterEventType.LOADED, handler2)
|
||||
|
||||
event = RouterLoadedEvent("test_router", router)
|
||||
|
||||
with pytest.raises(ValueError, match="Handler error"):
|
||||
dispatcher.dispatch(event)
|
||||
261
tests/test_integration.py
Normal file
261
tests/test_integration.py
Normal file
@@ -0,0 +1,261 @@
|
||||
"""Integration tests for the complete library."""
|
||||
|
||||
import sys
|
||||
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from fastapi_route_loader import RouterContainer, RouterEventType
|
||||
|
||||
|
||||
class TestIntegration:
|
||||
"""Test complete integration scenarios."""
|
||||
|
||||
def test_full_workflow(self, tmp_path):
|
||||
routers_dir = tmp_path / "routers"
|
||||
routers_dir.mkdir()
|
||||
|
||||
(routers_dir / "__init__.py").write_text("")
|
||||
(routers_dir / "users.py").write_text('''
|
||||
from fastapi import APIRouter
|
||||
|
||||
users_router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
@users_router.get("/")
|
||||
def list_users():
|
||||
return {"users": []}
|
||||
|
||||
@users_router.get("/{user_id}")
|
||||
def get_user(user_id: int):
|
||||
return {"user_id": user_id}
|
||||
''')
|
||||
|
||||
(routers_dir / "posts.py").write_text('''
|
||||
from fastapi import APIRouter
|
||||
|
||||
posts_router = APIRouter(prefix="/posts", tags=["posts"])
|
||||
|
||||
@posts_router.get("/")
|
||||
def list_posts():
|
||||
return {"posts": []}
|
||||
''')
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
container = RouterContainer()
|
||||
container.load_from_directory(str(routers_dir), "routers")
|
||||
|
||||
assert container.has_router("users.users_router")
|
||||
assert container.has_router("posts.posts_router")
|
||||
|
||||
app = FastAPI()
|
||||
container.register_to_app(app)
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/users/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"users": []}
|
||||
|
||||
response = client.get("/users/123")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"user_id": 123}
|
||||
|
||||
response = client.get("/posts/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"posts": []}
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
for mod in list(sys.modules.keys()):
|
||||
if mod.startswith("routers"):
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_filtering_workflow(self, tmp_path):
|
||||
routers_dir = tmp_path / "routers"
|
||||
routers_dir.mkdir()
|
||||
|
||||
(routers_dir / "__init__.py").write_text("")
|
||||
(routers_dir / "public.py").write_text('''
|
||||
from fastapi import APIRouter
|
||||
|
||||
public_router = APIRouter(prefix="/public")
|
||||
|
||||
@public_router.get("/")
|
||||
def public_endpoint():
|
||||
return {"message": "public"}
|
||||
''')
|
||||
|
||||
(routers_dir / "admin.py").write_text('''
|
||||
from fastapi import APIRouter
|
||||
|
||||
admin_router = APIRouter(prefix="/admin")
|
||||
|
||||
@admin_router.get("/")
|
||||
def admin_endpoint():
|
||||
return {"message": "admin"}
|
||||
''')
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
container = RouterContainer()
|
||||
container.load_from_directory(str(routers_dir), "routers")
|
||||
|
||||
container.exclude("admin.admin_router")
|
||||
|
||||
app = FastAPI()
|
||||
container.register_to_app(app)
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/public/")
|
||||
assert response.status_code == 200
|
||||
|
||||
response = client.get("/admin/")
|
||||
assert response.status_code == 404
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
for mod in list(sys.modules.keys()):
|
||||
if mod.startswith("routers"):
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_event_tracking_workflow(self, tmp_path):
|
||||
routers_dir = tmp_path / "routers"
|
||||
routers_dir.mkdir()
|
||||
|
||||
(routers_dir / "__init__.py").write_text("")
|
||||
(routers_dir / "api.py").write_text('''
|
||||
from fastapi import APIRouter
|
||||
|
||||
api_router = APIRouter()
|
||||
''')
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
container = RouterContainer()
|
||||
events_log = []
|
||||
|
||||
def event_logger(event):
|
||||
events_log.append({
|
||||
"type": event.event_type.value,
|
||||
"router": event.router_name,
|
||||
})
|
||||
|
||||
container.on(None, event_logger)
|
||||
|
||||
container.load_from_directory(str(routers_dir), "routers")
|
||||
|
||||
assert len(events_log) == 1
|
||||
assert events_log[0]["type"] == "loaded"
|
||||
assert events_log[0]["router"] == "api.api_router"
|
||||
|
||||
new_router = APIRouter()
|
||||
container.update_router("api.api_router", new_router)
|
||||
|
||||
assert len(events_log) == 2
|
||||
assert events_log[1]["type"] == "updated"
|
||||
|
||||
container.remove_router("api.api_router")
|
||||
|
||||
assert len(events_log) == 3
|
||||
assert events_log[2]["type"] == "unloaded"
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
for mod in list(sys.modules.keys()):
|
||||
if mod.startswith("routers"):
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_manual_router_management(self):
|
||||
container = RouterContainer()
|
||||
|
||||
users_router = APIRouter(prefix="/users")
|
||||
|
||||
@users_router.get("/")
|
||||
def list_users():
|
||||
return {"users": []}
|
||||
|
||||
posts_router = APIRouter(prefix="/posts")
|
||||
|
||||
@posts_router.get("/")
|
||||
def list_posts():
|
||||
return {"posts": []}
|
||||
|
||||
container.add_router("users", users_router)
|
||||
container.add_router("posts", posts_router)
|
||||
|
||||
app = FastAPI()
|
||||
container.register_to_app(app)
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/users/")
|
||||
assert response.status_code == 200
|
||||
|
||||
response = client.get("/posts/")
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_include_only_specific_routers(self):
|
||||
container = RouterContainer()
|
||||
|
||||
router1 = APIRouter(prefix="/api1")
|
||||
router2 = APIRouter(prefix="/api2")
|
||||
router3 = APIRouter(prefix="/api3")
|
||||
|
||||
@router1.get("/")
|
||||
def api1():
|
||||
return {"api": 1}
|
||||
|
||||
@router2.get("/")
|
||||
def api2():
|
||||
return {"api": 2}
|
||||
|
||||
@router3.get("/")
|
||||
def api3():
|
||||
return {"api": 3}
|
||||
|
||||
container.add_router("api1", router1)
|
||||
container.add_router("api2", router2)
|
||||
container.add_router("api3", router3)
|
||||
|
||||
container.include("api1", "api3")
|
||||
|
||||
app = FastAPI()
|
||||
container.register_to_app(app)
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/api1/")
|
||||
assert response.status_code == 200
|
||||
|
||||
response = client.get("/api2/")
|
||||
assert response.status_code == 404
|
||||
|
||||
response = client.get("/api3/")
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_event_handlers_for_specific_types(self):
|
||||
container = RouterContainer()
|
||||
|
||||
loaded_count = [0]
|
||||
unloaded_count = [0]
|
||||
updated_count = [0]
|
||||
|
||||
def on_loaded(event):
|
||||
loaded_count[0] += 1
|
||||
|
||||
def on_unloaded(event):
|
||||
unloaded_count[0] += 1
|
||||
|
||||
def on_updated(event):
|
||||
updated_count[0] += 1
|
||||
|
||||
container.on(RouterEventType.LOADED, on_loaded)
|
||||
container.on(RouterEventType.UNLOADED, on_unloaded)
|
||||
container.on(RouterEventType.UPDATED, on_updated)
|
||||
|
||||
router1 = APIRouter()
|
||||
router2 = APIRouter()
|
||||
|
||||
container.add_router("router1", router1)
|
||||
assert loaded_count[0] == 1
|
||||
|
||||
container.update_router("router1", router2)
|
||||
assert updated_count[0] == 1
|
||||
|
||||
container.remove_router("router1")
|
||||
assert unloaded_count[0] == 1
|
||||
160
tests/test_loader.py
Normal file
160
tests/test_loader.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""Tests for router loader."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import APIRouter
|
||||
|
||||
from fastapi_route_loader.loader import RouterLoader
|
||||
|
||||
|
||||
class TestRouterLoader:
|
||||
"""Test router loader functionality."""
|
||||
|
||||
def test_is_router_with_api_router(self):
|
||||
router = APIRouter()
|
||||
assert RouterLoader._is_router(router) is True
|
||||
|
||||
def test_is_router_with_non_router(self):
|
||||
assert RouterLoader._is_router("not a router") is False
|
||||
assert RouterLoader._is_router(123) is False
|
||||
assert RouterLoader._is_router(None) is False
|
||||
|
||||
def test_get_module_path_without_package(self):
|
||||
base_path = Path("/app/routers")
|
||||
file_path = Path("/app/routers/users/api.py")
|
||||
module_path = RouterLoader._get_module_path(file_path, base_path, None)
|
||||
assert module_path == "users.api"
|
||||
|
||||
def test_get_module_path_with_package(self):
|
||||
base_path = Path("/app/routers")
|
||||
file_path = Path("/app/routers/users/api.py")
|
||||
module_path = RouterLoader._get_module_path(file_path, base_path, "myapp")
|
||||
assert module_path == "myapp.users.api"
|
||||
|
||||
def test_get_module_path_single_file(self):
|
||||
base_path = Path("/app/routers")
|
||||
file_path = Path("/app/routers/api.py")
|
||||
module_path = RouterLoader._get_module_path(file_path, base_path, None)
|
||||
assert module_path == "api"
|
||||
|
||||
def test_load_from_module_success(self, tmp_path):
|
||||
module_content = '''
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter()
|
||||
other_router = APIRouter()
|
||||
not_a_router = "test"
|
||||
'''
|
||||
module_file = tmp_path / "test_module.py"
|
||||
module_file.write_text(module_content)
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
routers = RouterLoader.load_from_module("test_module")
|
||||
assert "router" in routers
|
||||
assert "other_router" in routers
|
||||
assert "not_a_router" not in routers
|
||||
assert isinstance(routers["router"], APIRouter)
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
if "test_module" in sys.modules:
|
||||
del sys.modules["test_module"]
|
||||
|
||||
def test_load_from_module_import_error(self):
|
||||
with pytest.raises(ImportError, match="Failed to import module"):
|
||||
RouterLoader.load_from_module("nonexistent_module_xyz")
|
||||
|
||||
def test_load_from_directory_success(self, tmp_path):
|
||||
routers_dir = tmp_path / "routers"
|
||||
routers_dir.mkdir()
|
||||
|
||||
(routers_dir / "__init__.py").write_text("")
|
||||
(routers_dir / "users.py").write_text(
|
||||
"from fastapi import APIRouter\nuser_router = APIRouter()"
|
||||
)
|
||||
(routers_dir / "posts.py").write_text(
|
||||
"from fastapi import APIRouter\npost_router = APIRouter()"
|
||||
)
|
||||
(routers_dir / "_private.py").write_text(
|
||||
"from fastapi import APIRouter\nprivate_router = APIRouter()"
|
||||
)
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
routers = RouterLoader.load_from_directory(routers_dir, "routers")
|
||||
assert "users.user_router" in routers
|
||||
assert "posts.post_router" in routers
|
||||
assert "private.private_router" not in routers
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
for mod in list(sys.modules.keys()):
|
||||
if mod.startswith("routers"):
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_load_from_directory_not_found(self):
|
||||
with pytest.raises(FileNotFoundError, match="Directory not found"):
|
||||
RouterLoader.load_from_directory("/nonexistent/path")
|
||||
|
||||
def test_load_from_directory_not_a_directory(self, tmp_path):
|
||||
file_path = tmp_path / "not_a_dir.txt"
|
||||
file_path.write_text("test")
|
||||
|
||||
with pytest.raises(NotADirectoryError, match="Not a directory"):
|
||||
RouterLoader.load_from_directory(file_path)
|
||||
|
||||
def test_load_from_directory_nested_structure(self, tmp_path):
|
||||
routers_dir = tmp_path / "routers"
|
||||
routers_dir.mkdir()
|
||||
api_dir = routers_dir / "api"
|
||||
api_dir.mkdir()
|
||||
v1_dir = api_dir / "v1"
|
||||
v1_dir.mkdir()
|
||||
|
||||
(routers_dir / "__init__.py").write_text("")
|
||||
(api_dir / "__init__.py").write_text("")
|
||||
(v1_dir / "__init__.py").write_text("")
|
||||
(v1_dir / "users.py").write_text(
|
||||
"from fastapi import APIRouter\nusers_router = APIRouter()"
|
||||
)
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
routers = RouterLoader.load_from_directory(routers_dir, "routers")
|
||||
assert "users.users_router" in routers
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
for mod in list(sys.modules.keys()):
|
||||
if mod.startswith("routers"):
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_load_from_directory_with_import_errors(self, tmp_path):
|
||||
routers_dir = tmp_path / "routers"
|
||||
routers_dir.mkdir()
|
||||
|
||||
(routers_dir / "__init__.py").write_text("")
|
||||
(routers_dir / "good.py").write_text(
|
||||
"from fastapi import APIRouter\ngood_router = APIRouter()"
|
||||
)
|
||||
(routers_dir / "bad.py").write_text(
|
||||
"import nonexistent_module\nfrom fastapi import APIRouter\nbad_router = APIRouter()"
|
||||
)
|
||||
|
||||
sys.path.insert(0, str(tmp_path))
|
||||
try:
|
||||
routers = RouterLoader.load_from_directory(routers_dir, "routers")
|
||||
assert "good.good_router" in routers
|
||||
assert "bad.bad_router" not in routers
|
||||
finally:
|
||||
sys.path.remove(str(tmp_path))
|
||||
for mod in list(sys.modules.keys()):
|
||||
if mod.startswith("routers"):
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_load_from_directory_empty(self, tmp_path):
|
||||
routers_dir = tmp_path / "empty_routers"
|
||||
routers_dir.mkdir()
|
||||
|
||||
routers = RouterLoader.load_from_directory(routers_dir)
|
||||
assert routers == {}
|
||||
Reference in New Issue
Block a user