|
4 | 4 | from unittest.mock import patch |
5 | 5 |
|
6 | 6 | from cloudlockfixer.providers import ( |
7 | | - BoxProvider, DropboxProvider, GoogleDriveProvider, ICloudProvider, NextcloudProvider, OneDriveProvider, |
| 7 | + BoxProvider, DropboxProvider, GoogleDriveProvider, ICloudProvider, NextcloudProvider, |
| 8 | + OneDriveProvider, PCloudProvider, |
8 | 9 | SyncProvider, _discover_providers, _get_providers, _is_subpath, |
9 | 10 | available_providers, provider_for, |
10 | 11 | ) |
@@ -71,6 +72,10 @@ def test_icloud_is_folder_type(): |
71 | 72 | assert ICloudProvider().mount_type == "folder" |
72 | 73 |
|
73 | 74 |
|
| 75 | +def test_pcloud_is_virtual_type(): |
| 76 | + assert PCloudProvider().mount_type == "virtual" |
| 77 | + |
| 78 | + |
74 | 79 | # ── Discovery ────────────────────────────────────────────────────── |
75 | 80 |
|
76 | 81 | def test_discover_includes_provider_with_roots(monkeypatch, tmp_path): |
@@ -317,3 +322,154 @@ class _Fake: |
317 | 322 | assert _check_process("nextcloud.exe") is True, ( |
318 | 323 | "case-insensitive Suche muss Treffer finden wenn Ausgabe 'Nextcloud.exe' enthält" |
319 | 324 | ) |
| 325 | + |
| 326 | + |
| 327 | +# ── pCloud Provider ──────────────────────────────────────────────── |
| 328 | + |
| 329 | +def test_pcloud_detects_volume_with_pcloud_label(monkeypatch): |
| 330 | + """_detect_roots() muss ein Laufwerk zurückgeben, dessen Label 'pCloud' enthält.""" |
| 331 | + import ctypes as _ctypes |
| 332 | + import string |
| 333 | + |
| 334 | + # Simuliere ein einzelnes Laufwerk P: mit Label "pCloud Drive" |
| 335 | + def fake_get_logical_drives(): |
| 336 | + idx = string.ascii_uppercase.index("P") |
| 337 | + return 1 << idx |
| 338 | + |
| 339 | + def fake_get_volume_label(letter: str) -> str: |
| 340 | + return "pCloud Drive" if letter == "P" else "" |
| 341 | + |
| 342 | + monkeypatch.setattr( |
| 343 | + _ctypes.windll.kernel32, "GetLogicalDrives", fake_get_logical_drives, |
| 344 | + raising=False, |
| 345 | + ) |
| 346 | + monkeypatch.setattr( |
| 347 | + "cloudlockfixer.providers._get_volume_label", fake_get_volume_label |
| 348 | + ) |
| 349 | + import sys |
| 350 | + monkeypatch.setattr(sys, "platform", "win32") |
| 351 | + |
| 352 | + prov = PCloudProvider() |
| 353 | + roots = prov._detect_roots() |
| 354 | + assert any(str(r).startswith("P:") for r in roots), ( |
| 355 | + f"Laufwerk P: erwartet, erhalten: {roots}" |
| 356 | + ) |
| 357 | + |
| 358 | + |
| 359 | +def test_pcloud_no_roots_when_no_pcloud_volume(monkeypatch): |
| 360 | + """_detect_roots() gibt leere Liste zurück wenn kein Volume das Label 'pCloud' trägt.""" |
| 361 | + monkeypatch.setattr( |
| 362 | + "cloudlockfixer.providers._get_volume_label", lambda letter: "Local Disk" |
| 363 | + ) |
| 364 | + import sys |
| 365 | + monkeypatch.setattr(sys, "platform", "win32") |
| 366 | + |
| 367 | + prov = PCloudProvider() |
| 368 | + # Kein einziges Laufwerk hat das pCloud-Label |
| 369 | + roots = prov._detect_roots() |
| 370 | + # Da GetLogicalDrives nicht gemockt → kann Systemlaufwerke zurückgeben, |
| 371 | + # aber keines davon hat das pCloud-Label → roots muss leer sein. |
| 372 | + assert roots == [] |
| 373 | + |
| 374 | + |
| 375 | +def test_pcloud_is_running_detects_process(monkeypatch): |
| 376 | + monkeypatch.setattr( |
| 377 | + subprocess, "run", |
| 378 | + lambda *a, **k: _FakeCompleted("pCloud.exe 4321\n"), |
| 379 | + ) |
| 380 | + assert PCloudProvider().is_running() is True |
| 381 | + |
| 382 | + |
| 383 | +def test_pcloud_is_running_negative(monkeypatch): |
| 384 | + monkeypatch.setattr( |
| 385 | + subprocess, "run", |
| 386 | + lambda *a, **k: _FakeCompleted("INFO: Keine Aufgaben\n"), |
| 387 | + ) |
| 388 | + assert PCloudProvider().is_running() is False |
| 389 | + |
| 390 | + |
| 391 | +def test_pcloud_resume_tries_known_paths(monkeypatch, tmp_path): |
| 392 | + """resume() muss den ersten vorhandenen Kandidatenpfad starten.""" |
| 393 | + import subprocess as sp |
| 394 | + |
| 395 | + fake_exe = tmp_path / "pCloud.exe" |
| 396 | + fake_exe.write_text("x", encoding="utf-8") |
| 397 | + |
| 398 | + started: list[str] = [] |
| 399 | + |
| 400 | + def fake_popen(cmd): |
| 401 | + started.append(cmd[0]) |
| 402 | + |
| 403 | + monkeypatch.setattr(sp, "Popen", fake_popen) |
| 404 | + |
| 405 | + import sys |
| 406 | + monkeypatch.setattr(sys, "platform", "win32") |
| 407 | + |
| 408 | + # Patch die candidates-Liste via _LOCALAPPDATA-Pfad im Provider |
| 409 | + import os |
| 410 | + monkeypatch.setenv("LOCALAPPDATA", str(tmp_path)) |
| 411 | + |
| 412 | + # Zieldatei muss exakt am erwarteten Pfad liegen |
| 413 | + pcloud_dir = tmp_path / "Programs" / "pCloud" |
| 414 | + pcloud_dir.mkdir(parents=True) |
| 415 | + pcloud_exe = pcloud_dir / "pCloud.exe" |
| 416 | + pcloud_exe.write_text("x", encoding="utf-8") |
| 417 | + |
| 418 | + result = PCloudProvider().resume() |
| 419 | + assert result is True |
| 420 | + assert len(started) == 1 |
| 421 | + assert Path(started[0]).name == "pCloud.exe" |
| 422 | + |
| 423 | + |
| 424 | +def test_pcloud_virtual_mount_skipped_in_pause(tmp_path): |
| 425 | + """pCloud ist ein Virtual-Mount-Provider → wird in _providers_to_pause() übersprungen.""" |
| 426 | + proot = tmp_path / "pcloud" |
| 427 | + proot.mkdir() |
| 428 | + vprov = FakeVirtualProvider(roots=[proot], running=True) |
| 429 | + task = Task(chain=[Step(op="move", src=str(proot / "a"), arg=str(tmp_path / "b"))]) |
| 430 | + task.retry_count = 10 |
| 431 | + |
| 432 | + with patch("cloudlockfixer.worker.provider_for", return_value=vprov): |
| 433 | + result = _providers_to_pause([task], force_pause=True) |
| 434 | + |
| 435 | + assert vprov not in result |
| 436 | + |
| 437 | + |
| 438 | +def test_pcloud_included_in_discover_when_root_found(monkeypatch, tmp_path): |
| 439 | + """_discover_providers() muss pCloud aufnehmen wenn _roots() nicht leer ist.""" |
| 440 | + monkeypatch.setattr(PCloudProvider, "_roots", lambda self: [tmp_path / "pcloud"]) |
| 441 | + providers = _discover_providers() |
| 442 | + names = [p.name for p in providers] |
| 443 | + assert "pCloud" in names |
| 444 | + |
| 445 | + |
| 446 | +def test_pcloud_excluded_from_discover_without_roots(monkeypatch): |
| 447 | + """_discover_providers() darf pCloud nicht aufnehmen wenn kein Volume gefunden wird.""" |
| 448 | + monkeypatch.setattr(PCloudProvider, "_roots", lambda self: []) |
| 449 | + providers = _discover_providers() |
| 450 | + names = [p.name for p in providers] |
| 451 | + assert "pCloud" not in names |
| 452 | + |
| 453 | + |
| 454 | +def test_provider_for_resolves_pcloud_root(monkeypatch, tmp_path): |
| 455 | + """provider_for() muss eine Datei unter dem pCloud-Root dem pCloud-Provider zuordnen.""" |
| 456 | + import cloudlockfixer.providers as pmod |
| 457 | + |
| 458 | + pc_root = tmp_path / "pCloud" |
| 459 | + pc_root.mkdir() |
| 460 | + |
| 461 | + monkeypatch.setattr(OneDriveProvider, "_roots", lambda self: []) |
| 462 | + monkeypatch.setattr(GoogleDriveProvider, "_roots", lambda self: []) |
| 463 | + monkeypatch.setattr(DropboxProvider, "_roots", lambda self: []) |
| 464 | + monkeypatch.setattr(BoxProvider, "_roots", lambda self: []) |
| 465 | + monkeypatch.setattr(NextcloudProvider, "_roots", lambda self: []) |
| 466 | + monkeypatch.setattr(PCloudProvider, "_roots", lambda self: [pc_root]) |
| 467 | + monkeypatch.setattr(ICloudProvider, "_roots", lambda self: []) |
| 468 | + |
| 469 | + old = pmod._PROVIDERS |
| 470 | + pmod._PROVIDERS = _discover_providers() |
| 471 | + try: |
| 472 | + p = provider_for(pc_root / "document.pdf") |
| 473 | + assert p is not None and p.name == "pCloud" |
| 474 | + finally: |
| 475 | + pmod._PROVIDERS = old |
0 commit comments