""" 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"])