Files
kite/backend/tests/test_webdav_backend.py
T
curo1305 c406ab1081 test(05-04): add failing RED tests for WebDAVBackend and NextcloudBackend
- Structure tests: all 7 methods async, proper subclassing
- SSRF guard tests: localhost/127.x/10.x/192.168.x/169.254.x raise ValueError
- NotImplementedError tests for presigned methods
- _make_path path construction and percent-encoding tests
- NextcloudBackend subclass, list_folder, inherited SSRF guard
2026-05-28 21:07:18 +02:00

189 lines
6.3 KiB
Python

"""
Tests for WebDAVBackend and NextcloudBackend (Plan 05-04).
TDD RED phase — all tests fail until backend/storage/webdav_backend.py and
backend/storage/nextcloud_backend.py are implemented.
Covers:
- WebDAVBackend subclasses StorageBackend (all 7 abstract methods present)
- All 7 methods are async coroutines
- SSRF guard: construction with private/localhost URL raises ValueError
- presigned_get_url and generate_presigned_put_url raise NotImplementedError
- _make_path constructs correct WebDAV path
- NextcloudBackend subclasses WebDAVBackend
- NextcloudBackend inherits SSRF guard
- NextcloudBackend has list_folder (async)
"""
from __future__ import annotations
import inspect
import pytest
pytestmark = pytest.mark.asyncio
class TestWebDAVBackendStructure:
"""Static structure tests — no network calls required."""
def test_webdav_backend_importable(self):
from storage.webdav_backend import WebDAVBackend # noqa: F401
def test_webdav_backend_is_storage_backend_subclass(self):
from storage.base import StorageBackend
from storage.webdav_backend import WebDAVBackend
assert issubclass(WebDAVBackend, StorageBackend)
@pytest.mark.parametrize(
"method",
[
"put_object",
"get_object",
"delete_object",
"presigned_get_url",
"health_check",
"generate_presigned_put_url",
"stat_object",
],
)
def test_all_7_methods_are_async(self, method):
from storage.webdav_backend import WebDAVBackend
fn = getattr(WebDAVBackend, method)
assert inspect.iscoroutinefunction(fn), f"{method} is not async"
class TestWebDAVBackendSSRF:
"""SSRF guard tests — construction with blocked URL must raise ValueError."""
def test_localhost_raises(self):
from storage.webdav_backend import WebDAVBackend
with pytest.raises(ValueError):
WebDAVBackend("http://localhost/dav", "user", "pass")
def test_127_x_raises(self):
from storage.webdav_backend import WebDAVBackend
with pytest.raises(ValueError):
WebDAVBackend("http://127.0.0.1/dav", "user", "pass")
def test_10_x_raises(self):
from storage.webdav_backend import WebDAVBackend
with pytest.raises(ValueError):
WebDAVBackend("http://10.0.0.1/dav", "user", "pass")
def test_192_168_raises(self):
from storage.webdav_backend import WebDAVBackend
with pytest.raises(ValueError):
WebDAVBackend("http://192.168.1.1/dav", "user", "pass")
def test_169_254_raises(self):
from storage.webdav_backend import WebDAVBackend
with pytest.raises(ValueError):
WebDAVBackend("http://169.254.169.254/dav", "user", "pass")
class TestWebDAVBackendNotImplemented:
"""presigned methods must raise NotImplementedError (D-14)."""
async def test_presigned_get_url_raises(self):
from storage.webdav_backend import WebDAVBackend
backend = WebDAVBackend.__new__(WebDAVBackend)
backend._server_url = "https://8.8.8.8/dav"
with pytest.raises(NotImplementedError):
await backend.presigned_get_url("some/key")
async def test_generate_presigned_put_url_raises(self):
from storage.webdav_backend import WebDAVBackend
backend = WebDAVBackend.__new__(WebDAVBackend)
backend._server_url = "https://8.8.8.8/dav"
with pytest.raises(NotImplementedError):
await backend.generate_presigned_put_url("some/key")
class TestWebDAVMakePath:
"""_make_path must produce percent-encoded WebDAV paths (Pitfall 2)."""
def test_make_path_basic(self):
from storage.webdav_backend import WebDAVBackend
backend = WebDAVBackend.__new__(WebDAVBackend)
path = backend._make_path("user-uuid", "doc-uuid", ".pdf")
assert path == "docuvault/user-uuid/doc-uuid.pdf"
def test_make_path_encodes_special_chars(self):
from storage.webdav_backend import WebDAVBackend
backend = WebDAVBackend.__new__(WebDAVBackend)
# UUIDs are alphanumeric and hyphens — no encoding needed for typical values
# This test ensures the encoding is applied (result should not contain raw spaces)
path = backend._make_path("user id", "doc id", ".pdf")
assert " " not in path
assert "user%20id" in path or "user+id" in path or "user%2520id" in path
class TestNextcloudBackendStructure:
"""NextcloudBackend structure tests."""
def test_nextcloud_importable(self):
from storage.nextcloud_backend import NextcloudBackend # noqa: F401
def test_nextcloud_is_webdav_subclass(self):
from storage.nextcloud_backend import NextcloudBackend
from storage.webdav_backend import WebDAVBackend
assert issubclass(NextcloudBackend, WebDAVBackend)
def test_nextcloud_is_storage_backend_subclass(self):
from storage.base import StorageBackend
from storage.nextcloud_backend import NextcloudBackend
assert issubclass(NextcloudBackend, StorageBackend)
@pytest.mark.parametrize(
"method",
[
"put_object",
"get_object",
"delete_object",
"presigned_get_url",
"health_check",
"generate_presigned_put_url",
"stat_object",
],
)
def test_all_7_methods_async_on_nextcloud(self, method):
from storage.nextcloud_backend import NextcloudBackend
fn = getattr(NextcloudBackend, method)
assert inspect.iscoroutinefunction(fn), f"{method} is not async"
def test_list_folder_present_and_async(self):
from storage.nextcloud_backend import NextcloudBackend
assert hasattr(NextcloudBackend, "list_folder")
assert inspect.iscoroutinefunction(NextcloudBackend.list_folder)
class TestNextcloudBackendSSRF:
"""SSRF guard inherited from WebDAVBackend.__init__."""
def test_10_x_raises_inherited(self):
from storage.nextcloud_backend import NextcloudBackend
with pytest.raises(ValueError):
NextcloudBackend("http://10.0.0.1/dav", "user", "pass")
def test_localhost_raises_inherited(self):
from storage.nextcloud_backend import NextcloudBackend
with pytest.raises(ValueError):
NextcloudBackend("http://localhost/dav", "user", "pass")