forked from Shifty/pyserveX
274 lines
9.3 KiB
Python
274 lines
9.3 KiB
Python
"""
|
|
Tests for path_matcher module.
|
|
|
|
Run with: pytest tests/test_path_matcher.py -v
|
|
"""
|
|
|
|
import pytest
|
|
from pyserve.path_matcher import (
|
|
FastMountedPath,
|
|
FastMountManager,
|
|
path_matches_prefix,
|
|
strip_path_prefix,
|
|
match_and_modify_path,
|
|
CYTHON_AVAILABLE,
|
|
)
|
|
|
|
|
|
class TestFastMountedPath:
|
|
def test_root_mount_matches_everything(self):
|
|
"""Root mount should match all paths."""
|
|
mount = FastMountedPath("")
|
|
|
|
assert mount.matches("/") is True
|
|
assert mount.matches("/api") is True
|
|
assert mount.matches("/api/users") is True
|
|
assert mount.matches("/anything/at/all") is True
|
|
|
|
def test_slash_root_mount_matches_everything(self):
|
|
"""'/' mount should match all paths."""
|
|
mount = FastMountedPath("/")
|
|
|
|
assert mount.matches("/") is True
|
|
assert mount.matches("/api") is True
|
|
assert mount.matches("/api/users") is True
|
|
|
|
def test_exact_path_match(self):
|
|
"""Exact path should match."""
|
|
mount = FastMountedPath("/api")
|
|
|
|
assert mount.matches("/api") is True
|
|
assert mount.matches("/api/") is True
|
|
assert mount.matches("/api/users") is True
|
|
|
|
def test_no_false_prefix_match(self):
|
|
"""/api should not match /api-v2."""
|
|
mount = FastMountedPath("/api")
|
|
|
|
assert mount.matches("/api-v2") is False
|
|
assert mount.matches("/api2") is False
|
|
assert mount.matches("/apiv2") is False
|
|
|
|
def test_shorter_path_no_match(self):
|
|
"""Request path shorter than mount path should not match."""
|
|
mount = FastMountedPath("/api/v1")
|
|
|
|
assert mount.matches("/api") is False
|
|
assert mount.matches("/ap") is False
|
|
assert mount.matches("/") is False
|
|
|
|
def test_trailing_slash_normalized(self):
|
|
"""Trailing slashes should be normalized."""
|
|
mount1 = FastMountedPath("/api/")
|
|
mount2 = FastMountedPath("/api")
|
|
|
|
assert mount1.path == "/api"
|
|
assert mount2.path == "/api"
|
|
assert mount1.matches("/api/users") is True
|
|
assert mount2.matches("/api/users") is True
|
|
|
|
def test_get_modified_path_strips_prefix(self):
|
|
"""Modified path should have prefix stripped."""
|
|
mount = FastMountedPath("/api")
|
|
|
|
assert mount.get_modified_path("/api") == "/"
|
|
assert mount.get_modified_path("/api/") == "/"
|
|
assert mount.get_modified_path("/api/users") == "/users"
|
|
assert mount.get_modified_path("/api/users/123") == "/users/123"
|
|
|
|
def test_get_modified_path_no_strip(self):
|
|
"""With strip_path=False, path should not be modified."""
|
|
mount = FastMountedPath("/api", strip_path=False)
|
|
|
|
assert mount.get_modified_path("/api/users") == "/api/users"
|
|
assert mount.get_modified_path("/api") == "/api"
|
|
|
|
def test_root_mount_modified_path(self):
|
|
"""Root mount should return original path."""
|
|
mount = FastMountedPath("")
|
|
|
|
assert mount.get_modified_path("/api/users") == "/api/users"
|
|
assert mount.get_modified_path("/") == "/"
|
|
|
|
def test_name_property(self):
|
|
"""Name should be set correctly."""
|
|
mount1 = FastMountedPath("/api")
|
|
mount2 = FastMountedPath("/api", name="API Mount")
|
|
|
|
assert mount1.name == "/api"
|
|
assert mount2.name == "API Mount"
|
|
|
|
def test_repr(self):
|
|
"""Repr should be informative."""
|
|
mount = FastMountedPath("/api", name="API")
|
|
assert "FastMountedPath" in repr(mount)
|
|
assert "/api" in repr(mount)
|
|
|
|
|
|
class TestFastMountManager:
|
|
def test_empty_manager(self):
|
|
"""Empty manager should return None."""
|
|
manager = FastMountManager()
|
|
|
|
assert manager.get_mount("/api") is None
|
|
assert manager.mount_count == 0
|
|
|
|
def test_add_mount(self):
|
|
"""Adding mounts should work."""
|
|
manager = FastMountManager()
|
|
mount = FastMountedPath("/api")
|
|
|
|
manager.add_mount(mount)
|
|
|
|
assert manager.mount_count == 1
|
|
assert manager.get_mount("/api/users") is mount
|
|
|
|
def test_longest_prefix_matching(self):
|
|
"""Longer prefixes should match first."""
|
|
manager = FastMountManager()
|
|
|
|
api_mount = FastMountedPath("/api", name="api")
|
|
api_v1_mount = FastMountedPath("/api/v1", name="api_v1")
|
|
api_v2_mount = FastMountedPath("/api/v2", name="api_v2")
|
|
|
|
manager.add_mount(api_mount)
|
|
manager.add_mount(api_v2_mount)
|
|
manager.add_mount(api_v1_mount)
|
|
|
|
assert manager.get_mount("/api/v1/users").name == "api_v1"
|
|
assert manager.get_mount("/api/v2/items").name == "api_v2"
|
|
assert manager.get_mount("/api/v3/other").name == "api"
|
|
assert manager.get_mount("/api").name == "api"
|
|
|
|
def test_remove_mount(self):
|
|
"""Removing mounts should work."""
|
|
manager = FastMountManager()
|
|
|
|
manager.add_mount(FastMountedPath("/api"))
|
|
manager.add_mount(FastMountedPath("/admin"))
|
|
|
|
assert manager.mount_count == 2
|
|
|
|
result = manager.remove_mount("/api")
|
|
|
|
assert result is True
|
|
assert manager.mount_count == 1
|
|
assert manager.get_mount("/api/users") is None
|
|
assert manager.get_mount("/admin/users") is not None
|
|
|
|
def test_remove_nonexistent_mount(self):
|
|
"""Removing nonexistent mount should return False."""
|
|
manager = FastMountManager()
|
|
|
|
result = manager.remove_mount("/api")
|
|
|
|
assert result is False
|
|
|
|
def test_list_mounts(self):
|
|
"""list_mounts should return mount info."""
|
|
manager = FastMountManager()
|
|
|
|
manager.add_mount(FastMountedPath("/api", name="API"))
|
|
manager.add_mount(FastMountedPath("/admin", name="Admin"))
|
|
|
|
mounts = manager.list_mounts()
|
|
|
|
assert len(mounts) == 2
|
|
assert all("path" in m and "name" in m and "strip_path" in m for m in mounts)
|
|
|
|
def test_mounts_property_returns_copy(self):
|
|
"""mounts property should return a copy."""
|
|
manager = FastMountManager()
|
|
manager.add_mount(FastMountedPath("/api"))
|
|
|
|
mounts1 = manager.mounts
|
|
mounts2 = manager.mounts
|
|
|
|
assert mounts1 is not mounts2
|
|
assert mounts1 == mounts2
|
|
|
|
|
|
class TestUtilityFunctions:
|
|
"""Tests for standalone utility functions."""
|
|
|
|
def test_path_matches_prefix_basic(self):
|
|
"""Basic prefix matching."""
|
|
assert path_matches_prefix("/api/users", "/api") is True
|
|
assert path_matches_prefix("/api", "/api") is True
|
|
assert path_matches_prefix("/api-v2", "/api") is False
|
|
assert path_matches_prefix("/ap", "/api") is False
|
|
|
|
def test_path_matches_prefix_root(self):
|
|
"""Root prefix matches everything."""
|
|
assert path_matches_prefix("/anything", "") is True
|
|
assert path_matches_prefix("/anything", "/") is True
|
|
|
|
def test_strip_path_prefix_basic(self):
|
|
"""Basic path stripping."""
|
|
assert strip_path_prefix("/api/users", "/api") == "/users"
|
|
assert strip_path_prefix("/api", "/api") == "/"
|
|
assert strip_path_prefix("/api/", "/api") == "/"
|
|
|
|
def test_strip_path_prefix_root(self):
|
|
"""Root prefix doesn't strip anything."""
|
|
assert strip_path_prefix("/api/users", "") == "/api/users"
|
|
assert strip_path_prefix("/api/users", "/") == "/api/users"
|
|
|
|
def test_match_and_modify_combined(self):
|
|
"""Combined match and modify operation."""
|
|
matches, path = match_and_modify_path("/api/users", "/api")
|
|
assert matches is True
|
|
assert path == "/users"
|
|
|
|
matches, path = match_and_modify_path("/api", "/api")
|
|
assert matches is True
|
|
assert path == "/"
|
|
|
|
matches, path = match_and_modify_path("/other", "/api")
|
|
assert matches is False
|
|
assert path is None
|
|
|
|
def test_match_and_modify_no_strip(self):
|
|
"""Combined operation with strip_path=False."""
|
|
matches, path = match_and_modify_path("/api/users", "/api", strip_path=False)
|
|
assert matches is True
|
|
assert path == "/api/users"
|
|
|
|
|
|
class TestCythonAvailability:
|
|
def test_cython_available_is_bool(self):
|
|
"""CYTHON_AVAILABLE should be a boolean."""
|
|
assert isinstance(CYTHON_AVAILABLE, bool)
|
|
|
|
def test_module_works_regardless(self):
|
|
"""Module should work whether Cython is available or not."""
|
|
mount = FastMountedPath("/test")
|
|
assert mount.matches("/test/path") is True
|
|
|
|
|
|
class TestPerformance:
|
|
def test_many_matches(self):
|
|
"""Should handle many match operations."""
|
|
mount = FastMountedPath("/api/v1/users")
|
|
|
|
for _ in range(10000):
|
|
assert mount.matches("/api/v1/users/123/posts") is True
|
|
assert mount.matches("/other/path") is False
|
|
|
|
def test_many_mounts(self):
|
|
"""Should handle many mounts."""
|
|
manager = FastMountManager()
|
|
|
|
for i in range(100):
|
|
manager.add_mount(FastMountedPath(f"/api/v{i}"))
|
|
|
|
assert manager.mount_count == 100
|
|
|
|
mount = manager.get_mount("/api/v50/users")
|
|
assert mount is not None
|
|
assert mount.path == "/api/v50"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|