feat: Mount-Schicht mit Linux-/Fake-Backend, Test und Wiederherstellung
MountBackend-Interface mit LinuxMountBackend (Argumentliste, kurzlebige Credentials-Datei) und FakeMountBackend; MountManager für Einhängen, Aushängen, Start-Wiederherstellung, Drei-Schritt-Test mit Root-Squash- Erkennung, Lebenszeichen und umount -l/Neu-Einhängen; SFTP-Anbindung mit festgehaltenem Hostschlüssel. Tests inkl. echtem SFTP-Server im Prozess. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
689ddf0d94
commit
e5ed8c4d88
+108
-7
@@ -1,12 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Callable, Iterator
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.orm import Session
|
||||
from cryptography.fernet import Fernet
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from netmover.db import make_engine, make_session_factory, upgrade
|
||||
from netmover.crypto import SecretBox
|
||||
from netmover.db import make_engine, make_session_factory, session_scope, upgrade
|
||||
from netmover.models import Credential, CredentialKind, Mount, MountType
|
||||
from netmover.mounting.fake import FakeMountBackend
|
||||
from netmover.mounting.manager import MountManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -17,9 +22,105 @@ def db_url(tmp_path: Path) -> str:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session(db_url: str) -> Iterator[Session]:
|
||||
def session_factory(db_url: str) -> Iterator[sessionmaker[Session]]:
|
||||
engine = make_engine(db_url)
|
||||
factory = make_session_factory(engine)
|
||||
with factory() as s:
|
||||
yield s
|
||||
yield make_session_factory(engine)
|
||||
engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session(session_factory: sessionmaker[Session]) -> Iterator[Session]:
|
||||
with session_factory() as s:
|
||||
yield s
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def secretbox() -> SecretBox:
|
||||
return SecretBox(Fernet.generate_key())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_backend(tmp_path: Path) -> Iterator[FakeMountBackend]:
|
||||
backend = FakeMountBackend(tmp_path / "fake")
|
||||
yield backend
|
||||
backend.release_hangs()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mount_base(tmp_path: Path) -> Path:
|
||||
return tmp_path / "mnt"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sleeps() -> list[float]:
|
||||
return []
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def notifications() -> list[tuple[str, str, str]]:
|
||||
return []
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def manager(
|
||||
session_factory: sessionmaker[Session],
|
||||
fake_backend: FakeMountBackend,
|
||||
secretbox: SecretBox,
|
||||
mount_base: Path,
|
||||
sleeps: list[float],
|
||||
notifications: list[tuple[str, str, str]],
|
||||
) -> MountManager:
|
||||
return MountManager(
|
||||
session_factory,
|
||||
fake_backend,
|
||||
secretbox,
|
||||
mount_base,
|
||||
probe_timeout=0.5,
|
||||
recover_backoff=(0, 1, 2),
|
||||
sleep=sleeps.append,
|
||||
notifier=lambda *a: notifications.append(a),
|
||||
)
|
||||
|
||||
|
||||
MountFactory = Callable[..., int]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_mount(
|
||||
session_factory: sessionmaker[Session], secretbox: SecretBox, mount_base: Path
|
||||
) -> MountFactory:
|
||||
def factory(
|
||||
name: str = "scans",
|
||||
type: MountType = MountType.CIFS,
|
||||
server: str = "nas",
|
||||
share: str = "scans",
|
||||
password: str | None = "geheim123",
|
||||
**kw: object,
|
||||
) -> int:
|
||||
with session_scope(session_factory) as s:
|
||||
cred_id = None
|
||||
if password is not None:
|
||||
c = Credential(
|
||||
name=f"cred-{name}",
|
||||
kind=CredentialKind.PASSWORD,
|
||||
username="scanner",
|
||||
secret_enc=secretbox.encrypt(password),
|
||||
)
|
||||
s.add(c)
|
||||
s.flush()
|
||||
cred_id = c.id
|
||||
m = Mount(
|
||||
name=name,
|
||||
slug=name,
|
||||
type=type,
|
||||
server=server,
|
||||
share=share,
|
||||
mountpoint=None if type is MountType.SFTP else str(mount_base.resolve() / name),
|
||||
cred_id=cred_id,
|
||||
**kw,
|
||||
)
|
||||
s.add(m)
|
||||
s.flush()
|
||||
return m.id
|
||||
|
||||
return factory
|
||||
|
||||
Reference in New Issue
Block a user