mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-02 20:22:15 +02:00
FTP bugs have been the #1 recurring issue across releases (0.1.8+). This adds a real implicit FTPS mock server and 67 test cases covering every known failure mode — connection, upload, download, delete, storage info, model-specific SSL behavior, async wrappers, and failure injection. New files: - mock_ftp_server.py: implicit FTPS server on pyftpdlib with failure injection - conftest.py: FTP test fixtures (certs, server, client factory) - test_bambu_ftp.py: 67 tests across 10 test classes Also adds pyOpenSSL to requirements-dev.txt (needed by pyftpdlib TLS_FTPHandler in the Docker test image).
100 lines
2.9 KiB
Python
100 lines
2.9 KiB
Python
"""Test fixtures for FTP service tests.
|
|
|
|
Provides a real implicit FTPS server (via mock_ftp_server) and client factory
|
|
for integration-style testing of BambuFTPClient against a live server.
|
|
"""
|
|
|
|
import socket
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from backend.app.services.bambu_ftp import BambuFTPClient
|
|
from backend.app.services.virtual_printer.certificate import CertificateService
|
|
from backend.tests.unit.services.mock_ftp_server import MockBambuFTPServer
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def ftp_certs(tmp_path_factory):
|
|
"""Generate self-signed TLS certificates once per test session."""
|
|
cert_dir = tmp_path_factory.mktemp("ftp_certs")
|
|
svc = CertificateService(cert_dir, serial="TEST_FTP_SERVER")
|
|
cert_path, key_path = svc.generate_certificates()
|
|
return str(cert_path), str(key_path)
|
|
|
|
|
|
def _find_free_port() -> int:
|
|
"""Find a free TCP port on localhost."""
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.bind(("127.0.0.1", 0))
|
|
return s.getsockname()[1]
|
|
|
|
|
|
@pytest.fixture()
|
|
def ftp_root(tmp_path):
|
|
"""Create temp directory with standard Bambu printer directory structure."""
|
|
for d in ("cache", "timelapse", "model", "data", "data/Metadata"):
|
|
(tmp_path / d).mkdir(parents=True, exist_ok=True)
|
|
return tmp_path
|
|
|
|
|
|
@pytest.fixture()
|
|
def ftp_server(ftp_certs, ftp_root):
|
|
"""Start a mock implicit FTPS server, yield it, stop on cleanup."""
|
|
cert_path, key_path = ftp_certs
|
|
port = _find_free_port()
|
|
server = MockBambuFTPServer(
|
|
host="127.0.0.1",
|
|
port=port,
|
|
root_dir=str(ftp_root),
|
|
cert_path=cert_path,
|
|
key_path=key_path,
|
|
access_code="12345678",
|
|
)
|
|
server.start()
|
|
yield server
|
|
server.stop()
|
|
|
|
|
|
@pytest.fixture()
|
|
def ftp_client_factory(ftp_server):
|
|
"""Factory that creates BambuFTPClient instances pointed at the mock server."""
|
|
|
|
def _make_client(
|
|
printer_model: str = "X1C",
|
|
force_prot_c: bool = False,
|
|
access_code: str = "12345678",
|
|
timeout: float = 10.0,
|
|
) -> BambuFTPClient:
|
|
client = BambuFTPClient(
|
|
ip_address="127.0.0.1",
|
|
access_code=access_code,
|
|
timeout=timeout,
|
|
printer_model=printer_model,
|
|
force_prot_c=force_prot_c,
|
|
)
|
|
# Override port to point at mock server
|
|
client.FTP_PORT = ftp_server.port
|
|
return client
|
|
|
|
return _make_client
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_ftp_mode_cache():
|
|
"""Clear BambuFTPClient mode cache before and after each test."""
|
|
BambuFTPClient._mode_cache.clear()
|
|
yield
|
|
BambuFTPClient._mode_cache.clear()
|
|
|
|
|
|
@pytest.fixture()
|
|
def patch_ftp_port(ftp_server):
|
|
"""Patch FTP_PORT at class level for async wrapper tests.
|
|
|
|
Async wrappers create their own BambuFTPClient instances internally,
|
|
so we need to patch the class-level default port.
|
|
"""
|
|
with patch.object(BambuFTPClient, "FTP_PORT", ftp_server.port):
|
|
yield ftp_server
|