test: add comprehensive WebDAV tests with real server and Redis integration
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,7 @@ pytest-cov>=4.1.0
|
|||||||
pytest-asyncio>=0.23.0
|
pytest-asyncio>=0.23.0
|
||||||
pytest-mock>=3.12.0
|
pytest-mock>=3.12.0
|
||||||
httpx>=0.26.0 # For async test client
|
httpx>=0.26.0 # For async test client
|
||||||
|
testcontainers>=3.7.1 # For integration tests with real containers
|
||||||
|
|
||||||
# Code quality
|
# Code quality
|
||||||
flake8>=7.0.0
|
flake8>=7.0.0
|
||||||
|
|||||||
@@ -177,3 +177,4 @@ def pytest_configure(config):
|
|||||||
config.addinivalue_line("markers", "requires_external: Tests requiring external services")
|
config.addinivalue_line("markers", "requires_external: Tests requiring external services")
|
||||||
config.addinivalue_line("markers", "requires_db: Tests requiring database")
|
config.addinivalue_line("markers", "requires_db: Tests requiring database")
|
||||||
config.addinivalue_line("markers", "requires_redis: Tests requiring Redis")
|
config.addinivalue_line("markers", "requires_redis: Tests requiring Redis")
|
||||||
|
config.addinivalue_line("markers", "requires_docker: Tests requiring Docker")
|
||||||
|
|||||||
@@ -0,0 +1,427 @@
|
|||||||
|
"""
|
||||||
|
Integration tests for WebDAV upload with real server.
|
||||||
|
|
||||||
|
These tests spin up a real WebDAV server in a Docker container and test
|
||||||
|
actual file uploads against it, then verify the files were uploaded successfully.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from app.tasks.upload_to_webdav import upload_to_webdav
|
||||||
|
|
||||||
|
# Import testcontainers - required for these tests
|
||||||
|
pytest.importorskip("testcontainers", reason="testcontainers not installed")
|
||||||
|
from testcontainers.core.container import DockerContainer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
@pytest.mark.requires_docker
|
||||||
|
class TestWebDAVIntegration:
|
||||||
|
"""Integration tests with real WebDAV server."""
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def webdav_server(self):
|
||||||
|
"""
|
||||||
|
Start a real WebDAV server in a Docker container.
|
||||||
|
|
||||||
|
Uses bytemark/webdav image which provides a simple WebDAV server.
|
||||||
|
"""
|
||||||
|
# Start WebDAV container
|
||||||
|
container = DockerContainer("bytemark/webdav:latest")
|
||||||
|
container.with_exposed_ports(80)
|
||||||
|
container.with_env("AUTH_TYPE", "Basic")
|
||||||
|
container.with_env("USERNAME", "testuser")
|
||||||
|
container.with_env("PASSWORD", "testpass")
|
||||||
|
|
||||||
|
# Start the container
|
||||||
|
container.start()
|
||||||
|
|
||||||
|
# Wait for server to be ready
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# Get the mapped port
|
||||||
|
host = container.get_container_host_ip()
|
||||||
|
port = container.get_exposed_port(80)
|
||||||
|
|
||||||
|
server_info = {
|
||||||
|
"container": container,
|
||||||
|
"host": host,
|
||||||
|
"port": port,
|
||||||
|
"url": f"http://{host}:{port}",
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "testpass"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify server is accessible
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
server_info["url"],
|
||||||
|
auth=(server_info["username"], server_info["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
assert response.status_code in [200, 301, 302, 401], \
|
||||||
|
f"WebDAV server not ready, got status {response.status_code}"
|
||||||
|
except Exception as e:
|
||||||
|
container.stop()
|
||||||
|
pytest.fail(f"Failed to connect to WebDAV server: {e}")
|
||||||
|
|
||||||
|
yield server_info
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
container.stop()
|
||||||
|
|
||||||
|
def test_upload_file_to_real_webdav_server(self, webdav_server, sample_text_file):
|
||||||
|
"""Test uploading a file to a real WebDAV server."""
|
||||||
|
with patch("app.tasks.upload_to_webdav.settings") as mock_settings, \
|
||||||
|
patch("app.tasks.upload_to_webdav.log_task_progress"):
|
||||||
|
|
||||||
|
# Configure settings to point to real WebDAV server
|
||||||
|
mock_settings.webdav_url = webdav_server["url"] + "/"
|
||||||
|
mock_settings.webdav_username = webdav_server["username"]
|
||||||
|
mock_settings.webdav_password = webdav_server["password"]
|
||||||
|
mock_settings.webdav_folder = ""
|
||||||
|
mock_settings.webdav_verify_ssl = False # HTTP server
|
||||||
|
mock_settings.http_request_timeout = 30
|
||||||
|
|
||||||
|
# Upload the file
|
||||||
|
result = upload_to_webdav.apply(
|
||||||
|
args=[sample_text_file],
|
||||||
|
kwargs={"file_id": 1}
|
||||||
|
).get()
|
||||||
|
|
||||||
|
# Verify upload succeeded
|
||||||
|
assert result["status"] == "Completed"
|
||||||
|
assert result["file"] == sample_text_file
|
||||||
|
assert "url" in result
|
||||||
|
|
||||||
|
# Verify the file actually exists on the server
|
||||||
|
filename = os.path.basename(sample_text_file)
|
||||||
|
file_url = f"{webdav_server['url']}/{filename}"
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
file_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200, \
|
||||||
|
f"File not found on server: {response.status_code}"
|
||||||
|
|
||||||
|
# Verify file content matches
|
||||||
|
with open(sample_text_file, "rb") as f:
|
||||||
|
expected_content = f.read()
|
||||||
|
|
||||||
|
assert response.content == expected_content, \
|
||||||
|
"Uploaded file content does not match original"
|
||||||
|
|
||||||
|
def test_upload_to_subfolder(self, webdav_server, sample_text_file):
|
||||||
|
"""Test uploading a file to a subfolder on WebDAV server."""
|
||||||
|
with patch("app.tasks.upload_to_webdav.settings") as mock_settings, \
|
||||||
|
patch("app.tasks.upload_to_webdav.log_task_progress"):
|
||||||
|
|
||||||
|
# Create a test folder first
|
||||||
|
folder_name = "test-uploads"
|
||||||
|
folder_url = f"{webdav_server['url']}/{folder_name}"
|
||||||
|
|
||||||
|
# Create folder using MKCOL method
|
||||||
|
requests.request(
|
||||||
|
"MKCOL",
|
||||||
|
folder_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure settings
|
||||||
|
mock_settings.webdav_url = webdav_server["url"] + "/"
|
||||||
|
mock_settings.webdav_username = webdav_server["username"]
|
||||||
|
mock_settings.webdav_password = webdav_server["password"]
|
||||||
|
mock_settings.webdav_folder = folder_name
|
||||||
|
mock_settings.webdav_verify_ssl = False
|
||||||
|
mock_settings.http_request_timeout = 30
|
||||||
|
|
||||||
|
# Upload the file
|
||||||
|
result = upload_to_webdav.apply(
|
||||||
|
args=[sample_text_file],
|
||||||
|
kwargs={"file_id": 2}
|
||||||
|
).get()
|
||||||
|
|
||||||
|
# Verify upload succeeded
|
||||||
|
assert result["status"] == "Completed"
|
||||||
|
|
||||||
|
# Verify the file exists in the subfolder
|
||||||
|
filename = os.path.basename(sample_text_file)
|
||||||
|
file_url = f"{webdav_server['url']}/{folder_name}/{filename}"
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
file_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200, \
|
||||||
|
f"File not found in subfolder: {response.status_code}"
|
||||||
|
|
||||||
|
def test_upload_pdf_file(self, webdav_server, sample_pdf_path):
|
||||||
|
"""Test uploading a PDF file to WebDAV server."""
|
||||||
|
with patch("app.tasks.upload_to_webdav.settings") as mock_settings, \
|
||||||
|
patch("app.tasks.upload_to_webdav.log_task_progress"):
|
||||||
|
|
||||||
|
mock_settings.webdav_url = webdav_server["url"] + "/"
|
||||||
|
mock_settings.webdav_username = webdav_server["username"]
|
||||||
|
mock_settings.webdav_password = webdav_server["password"]
|
||||||
|
mock_settings.webdav_folder = ""
|
||||||
|
mock_settings.webdav_verify_ssl = False
|
||||||
|
mock_settings.http_request_timeout = 30
|
||||||
|
|
||||||
|
# Upload the PDF
|
||||||
|
result = upload_to_webdav.apply(
|
||||||
|
args=[sample_pdf_path],
|
||||||
|
kwargs={"file_id": 3}
|
||||||
|
).get()
|
||||||
|
|
||||||
|
# Verify upload succeeded
|
||||||
|
assert result["status"] == "Completed"
|
||||||
|
|
||||||
|
# Verify the file exists
|
||||||
|
filename = os.path.basename(sample_pdf_path)
|
||||||
|
file_url = f"{webdav_server['url']}/{filename}"
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
file_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# Verify it's a PDF (check magic bytes)
|
||||||
|
assert response.content.startswith(b'%PDF'), \
|
||||||
|
"Uploaded file is not a valid PDF"
|
||||||
|
|
||||||
|
def test_upload_with_wrong_credentials(self, webdav_server, sample_text_file):
|
||||||
|
"""Test that upload fails with wrong credentials."""
|
||||||
|
with patch("app.tasks.upload_to_webdav.settings") as mock_settings, \
|
||||||
|
patch("app.tasks.upload_to_webdav.log_task_progress"):
|
||||||
|
|
||||||
|
mock_settings.webdav_url = webdav_server["url"] + "/"
|
||||||
|
mock_settings.webdav_username = "wronguser"
|
||||||
|
mock_settings.webdav_password = "wrongpass"
|
||||||
|
mock_settings.webdav_folder = ""
|
||||||
|
mock_settings.webdav_verify_ssl = False
|
||||||
|
mock_settings.http_request_timeout = 30
|
||||||
|
|
||||||
|
# Upload should fail with 401 Unauthorized
|
||||||
|
with pytest.raises(Exception, match="Failed to upload.*401"):
|
||||||
|
upload_to_webdav.apply(
|
||||||
|
args=[sample_text_file],
|
||||||
|
kwargs={"file_id": 4}
|
||||||
|
).get()
|
||||||
|
|
||||||
|
def test_upload_multiple_files(self, webdav_server, sample_text_file, tmp_path):
|
||||||
|
"""Test uploading multiple files sequentially."""
|
||||||
|
with patch("app.tasks.upload_to_webdav.settings") as mock_settings, \
|
||||||
|
patch("app.tasks.upload_to_webdav.log_task_progress"):
|
||||||
|
|
||||||
|
mock_settings.webdav_url = webdav_server["url"] + "/"
|
||||||
|
mock_settings.webdav_username = webdav_server["username"]
|
||||||
|
mock_settings.webdav_password = webdav_server["password"]
|
||||||
|
mock_settings.webdav_folder = ""
|
||||||
|
mock_settings.webdav_verify_ssl = False
|
||||||
|
mock_settings.http_request_timeout = 30
|
||||||
|
|
||||||
|
# Create additional test files
|
||||||
|
file1 = tmp_path / "test1.txt"
|
||||||
|
file1.write_text("Test file 1")
|
||||||
|
|
||||||
|
file2 = tmp_path / "test2.txt"
|
||||||
|
file2.write_text("Test file 2")
|
||||||
|
|
||||||
|
file3 = tmp_path / "test3.txt"
|
||||||
|
file3.write_text("Test file 3")
|
||||||
|
|
||||||
|
# Upload all files
|
||||||
|
files = [str(file1), str(file2), str(file3)]
|
||||||
|
uploaded_files = []
|
||||||
|
|
||||||
|
for idx, file_path in enumerate(files, start=1):
|
||||||
|
result = upload_to_webdav.apply(
|
||||||
|
args=[file_path],
|
||||||
|
kwargs={"file_id": idx + 10}
|
||||||
|
).get()
|
||||||
|
|
||||||
|
assert result["status"] == "Completed"
|
||||||
|
uploaded_files.append(os.path.basename(file_path))
|
||||||
|
|
||||||
|
# Verify all files exist on server
|
||||||
|
for filename in uploaded_files:
|
||||||
|
file_url = f"{webdav_server['url']}/{filename}"
|
||||||
|
response = requests.get(
|
||||||
|
file_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
assert response.status_code == 200, \
|
||||||
|
f"File {filename} not found on server"
|
||||||
|
|
||||||
|
def test_overwrite_existing_file(self, webdav_server, sample_text_file, tmp_path):
|
||||||
|
"""Test that uploading a file with the same name overwrites the existing one."""
|
||||||
|
with patch("app.tasks.upload_to_webdav.settings") as mock_settings, \
|
||||||
|
patch("app.tasks.upload_to_webdav.log_task_progress"):
|
||||||
|
|
||||||
|
mock_settings.webdav_url = webdav_server["url"] + "/"
|
||||||
|
mock_settings.webdav_username = webdav_server["username"]
|
||||||
|
mock_settings.webdav_password = webdav_server["password"]
|
||||||
|
mock_settings.webdav_folder = ""
|
||||||
|
mock_settings.webdav_verify_ssl = False
|
||||||
|
mock_settings.http_request_timeout = 30
|
||||||
|
|
||||||
|
# Create two files with same name but different content
|
||||||
|
file1 = tmp_path / "duplicate.txt"
|
||||||
|
file1.write_text("Original content")
|
||||||
|
|
||||||
|
# Upload first version
|
||||||
|
result1 = upload_to_webdav.apply(
|
||||||
|
args=[str(file1)],
|
||||||
|
kwargs={"file_id": 20}
|
||||||
|
).get()
|
||||||
|
assert result1["status"] == "Completed"
|
||||||
|
|
||||||
|
# Verify first version
|
||||||
|
file_url = f"{webdav_server['url']}/duplicate.txt"
|
||||||
|
response = requests.get(
|
||||||
|
file_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
assert response.text == "Original content"
|
||||||
|
|
||||||
|
# Update file content
|
||||||
|
file1.write_text("Updated content - version 2")
|
||||||
|
|
||||||
|
# Upload second version
|
||||||
|
result2 = upload_to_webdav.apply(
|
||||||
|
args=[str(file1)],
|
||||||
|
kwargs={"file_id": 21}
|
||||||
|
).get()
|
||||||
|
assert result2["status"] == "Completed"
|
||||||
|
|
||||||
|
# Verify file was overwritten
|
||||||
|
response = requests.get(
|
||||||
|
file_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
assert response.text == "Updated content - version 2"
|
||||||
|
|
||||||
|
def test_large_file_upload(self, webdav_server, tmp_path):
|
||||||
|
"""Test uploading a larger file (1MB)."""
|
||||||
|
with patch("app.tasks.upload_to_webdav.settings") as mock_settings, \
|
||||||
|
patch("app.tasks.upload_to_webdav.log_task_progress"):
|
||||||
|
|
||||||
|
mock_settings.webdav_url = webdav_server["url"] + "/"
|
||||||
|
mock_settings.webdav_username = webdav_server["username"]
|
||||||
|
mock_settings.webdav_password = webdav_server["password"]
|
||||||
|
mock_settings.webdav_folder = ""
|
||||||
|
mock_settings.webdav_verify_ssl = False
|
||||||
|
mock_settings.http_request_timeout = 60 # Longer timeout for large file
|
||||||
|
|
||||||
|
# Create a 1MB test file
|
||||||
|
large_file = tmp_path / "large_file.bin"
|
||||||
|
large_file.write_bytes(b"X" * (1024 * 1024)) # 1MB of X's
|
||||||
|
|
||||||
|
# Upload the large file
|
||||||
|
result = upload_to_webdav.apply(
|
||||||
|
args=[str(large_file)],
|
||||||
|
kwargs={"file_id": 30}
|
||||||
|
).get()
|
||||||
|
|
||||||
|
assert result["status"] == "Completed"
|
||||||
|
|
||||||
|
# Verify file exists and has correct size
|
||||||
|
file_url = f"{webdav_server['url']}/large_file.bin"
|
||||||
|
response = requests.get(
|
||||||
|
file_url,
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert len(response.content) == 1024 * 1024, \
|
||||||
|
f"File size mismatch: expected 1MB, got {len(response.content)} bytes"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
@pytest.mark.requires_docker
|
||||||
|
class TestWebDAVServerVerification:
|
||||||
|
"""Tests to verify WebDAV server behavior."""
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def webdav_server(self):
|
||||||
|
"""Start WebDAV server for verification tests."""
|
||||||
|
container = DockerContainer("bytemark/webdav:latest")
|
||||||
|
container.with_exposed_ports(80)
|
||||||
|
container.with_env("AUTH_TYPE", "Basic")
|
||||||
|
container.with_env("USERNAME", "admin")
|
||||||
|
container.with_env("PASSWORD", "admin123")
|
||||||
|
|
||||||
|
container.start()
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
host = container.get_container_host_ip()
|
||||||
|
port = container.get_exposed_port(80)
|
||||||
|
|
||||||
|
server_info = {
|
||||||
|
"container": container,
|
||||||
|
"url": f"http://{host}:{port}",
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin123"
|
||||||
|
}
|
||||||
|
|
||||||
|
yield server_info
|
||||||
|
container.stop()
|
||||||
|
|
||||||
|
def test_webdav_server_basic_auth(self, webdav_server):
|
||||||
|
"""Verify WebDAV server requires authentication."""
|
||||||
|
# Request without auth should fail
|
||||||
|
response = requests.get(webdav_server["url"], timeout=5)
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
# Request with auth should succeed
|
||||||
|
response = requests.get(
|
||||||
|
webdav_server["url"],
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
assert response.status_code in [200, 301, 302]
|
||||||
|
|
||||||
|
def test_webdav_put_method(self, webdav_server, tmp_path):
|
||||||
|
"""Verify WebDAV server accepts PUT requests."""
|
||||||
|
test_file = tmp_path / "put_test.txt"
|
||||||
|
test_file.write_text("PUT method test")
|
||||||
|
|
||||||
|
with open(test_file, "rb") as f:
|
||||||
|
response = requests.put(
|
||||||
|
f"{webdav_server['url']}/put_test.txt",
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
data=f,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code in [200, 201, 204]
|
||||||
|
|
||||||
|
def test_webdav_propfind_method(self, webdav_server):
|
||||||
|
"""Verify WebDAV server supports PROPFIND (directory listing)."""
|
||||||
|
response = requests.request(
|
||||||
|
"PROPFIND",
|
||||||
|
webdav_server["url"],
|
||||||
|
auth=(webdav_server["username"], webdav_server["password"]),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# PROPFIND may return 207 Multi-Status, 200 OK, or 403 Forbidden
|
||||||
|
# depending on server configuration
|
||||||
|
assert response.status_code in [200, 207, 403]
|
||||||
Reference in New Issue
Block a user