fix(backup): declare pathspec dep + rename drive_test->drive_check for green CI
- Add pathspec>=0.12 to root pyproject dependencies (was undeclared, caused ModuleNotFoundError in CI across all Python versions + Windows) - Rename drive_test.py -> drive_check.py so pytest stops collecting the module as a test file; update MODULE_NAME, PRIMARY_COMMAND, help text, README, tests - 220 backup tests pass, seedgo 100% all 37 standards Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
01023d25ba
commit
010cade60f
@@ -31,6 +31,7 @@ dependencies = [
|
||||
"requests>=2.34.2",
|
||||
"psutil>=5.9",
|
||||
"questionary>=2.0",
|
||||
"pathspec>=0.12",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -35,7 +35,7 @@ apps/
|
||||
│ ├── drive_clear.py # Drive clear (stub — DPLAN-003)
|
||||
│ ├── drive_stats.py # Drive stats (stub — DPLAN-003)
|
||||
│ ├── drive_sync.py # Drive sync (stub — DPLAN-003)
|
||||
│ ├── drive_test.py # Drive test (stub — DPLAN-003)
|
||||
│ ├── drive_check.py # Drive check (stub — DPLAN-003)
|
||||
│ ├── register.py # Project registration + @name resolution
|
||||
│ ├── restore.py # Version discovery + file restoration
|
||||
│ ├── settings.py # Settings UI (stub)
|
||||
|
||||
@@ -71,7 +71,7 @@ def print_help() -> None:
|
||||
console.print(" [green]status[/green] Show backup info and recent history")
|
||||
console.print(" [green]settings[/green] View/edit backup settings")
|
||||
console.print(" [green]drive_sync[/green] Sync backups to the remote drive")
|
||||
console.print(" [green]drive_test[/green] Test the remote drive connection")
|
||||
console.print(" [green]drive_check[/green] Test the remote drive connection")
|
||||
console.print(" [green]drive_stats[/green] Drive usage statistics")
|
||||
console.print(" [green]drive_clear[/green] Clear backups from the remote drive")
|
||||
console.print()
|
||||
|
||||
+13
-13
@@ -1,12 +1,12 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: drive_test.py
|
||||
# Description: Drive test module — verifies Google Drive connectivity via @api
|
||||
# Name: drive_check.py
|
||||
# Description: Drive check module — verifies Google Drive connectivity via @api
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-04-17
|
||||
# Modified: 2026-06-12
|
||||
# =============================================
|
||||
|
||||
"""Drive Test Module — tests Drive auth through @api gateway."""
|
||||
"""Drive Check Module — tests Drive auth through @api gateway."""
|
||||
|
||||
import sys
|
||||
|
||||
@@ -16,8 +16,8 @@ from aipass.cli.apps.modules import console
|
||||
from aipass.backup.apps.handlers.json import json_handler
|
||||
|
||||
|
||||
MODULE_NAME = "drive_test"
|
||||
PRIMARY_COMMAND = "drive_test"
|
||||
MODULE_NAME = "drive_check"
|
||||
PRIMARY_COMMAND = "drive_check"
|
||||
|
||||
|
||||
def print_introspection():
|
||||
@@ -33,7 +33,7 @@ def print_help():
|
||||
print_introspection()
|
||||
|
||||
|
||||
def run_drive_test() -> bool:
|
||||
def run_drive_check() -> bool:
|
||||
"""Test Drive auth through @api gateway.
|
||||
|
||||
Creates a DriveClient, authenticates, tests folder access, and
|
||||
@@ -57,14 +57,14 @@ def run_drive_test() -> bool:
|
||||
logger.warning(f"[backup] Drive test failed: {result['error']}")
|
||||
|
||||
json_handler.log_operation(
|
||||
"drive_test_complete",
|
||||
"drive_check_complete",
|
||||
{"success": result["success"]},
|
||||
)
|
||||
return result["success"]
|
||||
|
||||
|
||||
def handle_command(command: str, args: list) -> bool:
|
||||
"""Handle the drive-test command. Returns True if handled."""
|
||||
"""Handle the drive-check command. Returns True if handled."""
|
||||
if command != PRIMARY_COMMAND:
|
||||
return False
|
||||
|
||||
@@ -77,11 +77,11 @@ def handle_command(command: str, args: list) -> bool:
|
||||
return True
|
||||
|
||||
if args[0] == "run":
|
||||
run_drive_test()
|
||||
run_drive_check()
|
||||
return True
|
||||
|
||||
# Default: run the test
|
||||
run_drive_test()
|
||||
# Default: run the check
|
||||
run_drive_check()
|
||||
return True
|
||||
|
||||
|
||||
@@ -91,5 +91,5 @@ if __name__ == "__main__":
|
||||
if len(sys.argv) == 1:
|
||||
print_introspection()
|
||||
sys.exit(0)
|
||||
result = handle_command(PRIMARY_COMMAND, sys.argv[1:])
|
||||
sys.exit(0 if result else 1)
|
||||
handle_command(PRIMARY_COMMAND, sys.argv[1:])
|
||||
sys.exit(0)
|
||||
@@ -72,7 +72,7 @@ def _load_module_fresh(module_path: str, extra_mocks: dict | None = None):
|
||||
|
||||
SIMPLE_MODULES = [
|
||||
"aipass.backup.apps.modules.drive_sync",
|
||||
"aipass.backup.apps.modules.drive_test",
|
||||
"aipass.backup.apps.modules.drive_check",
|
||||
"aipass.backup.apps.modules.drive_stats",
|
||||
"aipass.backup.apps.modules.drive_clear",
|
||||
"aipass.backup.apps.modules.settings",
|
||||
|
||||
@@ -67,18 +67,18 @@ class TestDriveSyncModule:
|
||||
assert isinstance(result, bool)
|
||||
|
||||
|
||||
class TestDriveTestModule:
|
||||
"""Drive test stub."""
|
||||
class TestDriveCheckModule:
|
||||
"""Drive check stub."""
|
||||
|
||||
def test_drive_test_handle_command(self) -> None:
|
||||
"""drive_test handle_command returns True for primary."""
|
||||
mod = _get_drive_module("drive_test")
|
||||
def test_drive_check_handle_command(self) -> None:
|
||||
"""drive_check handle_command returns True for primary."""
|
||||
mod = _get_drive_module("drive_check")
|
||||
result = mod.handle_command(mod.PRIMARY_COMMAND, [])
|
||||
assert result is True
|
||||
|
||||
def test_drive_test_invalid_mode_returns_false(self) -> None:
|
||||
def test_drive_check_invalid_mode_returns_false(self) -> None:
|
||||
"""invalid_mode / invalid_type -- unknown returns False."""
|
||||
mod = _get_drive_module("drive_test")
|
||||
mod = _get_drive_module("drive_check")
|
||||
result = mod.handle_command("invalid_type", [])
|
||||
assert result is False
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class TestDriveClient:
|
||||
client = mod.DriveClient()
|
||||
|
||||
mock_service = MagicMock()
|
||||
mod.get_drive_service = MagicMock(return_value=mock_service)
|
||||
mod.get_drive_service = MagicMock(return_value=mock_service) # type: ignore[attr-defined]
|
||||
|
||||
result = client.authenticate()
|
||||
assert result is True
|
||||
@@ -119,7 +119,7 @@ class TestDriveClient:
|
||||
def test_authenticate_no_api(self) -> None:
|
||||
"""GOOGLE_API_AVAILABLE=False, authenticate() returns False."""
|
||||
mod = _fresh_import("aipass.backup.apps.handlers.drive.client")
|
||||
mod.GOOGLE_API_AVAILABLE = False
|
||||
mod.GOOGLE_API_AVAILABLE = False # type: ignore[attr-defined]
|
||||
client = mod.DriveClient()
|
||||
|
||||
result = client.authenticate()
|
||||
@@ -130,7 +130,7 @@ class TestDriveClient:
|
||||
"""get_drive_service returns None, authenticate() returns False."""
|
||||
mod = _fresh_import("aipass.backup.apps.handlers.drive.client")
|
||||
client = mod.DriveClient()
|
||||
mod.get_drive_service = MagicMock(return_value=None)
|
||||
mod.get_drive_service = MagicMock(return_value=None) # type: ignore[attr-defined]
|
||||
|
||||
result = client.authenticate()
|
||||
assert result is False
|
||||
@@ -140,7 +140,7 @@ class TestDriveClient:
|
||||
"""get_drive_service raises, authenticate() returns False."""
|
||||
mod = _fresh_import("aipass.backup.apps.handlers.drive.client")
|
||||
client = mod.DriveClient()
|
||||
mod.get_drive_service = MagicMock(side_effect=RuntimeError("boom"))
|
||||
mod.get_drive_service = MagicMock(side_effect=RuntimeError("boom")) # type: ignore[attr-defined]
|
||||
|
||||
result = client.authenticate()
|
||||
assert result is False
|
||||
@@ -173,7 +173,7 @@ class TestDriveClient:
|
||||
mock_service = MagicMock()
|
||||
client._drive_service = mock_service
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "folder_123", "name": "AIPass Backups"}]})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "folder_123", "name": "AIPass Backups"}]}) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_backup_folder()
|
||||
assert result == "folder_123"
|
||||
@@ -196,7 +196,7 @@ class TestDriveClient:
|
||||
return {"id": "new_folder_456"}
|
||||
return {"id": "new_folder_456", "trashed": False}
|
||||
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect)
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_backup_folder()
|
||||
assert result == "new_folder_456"
|
||||
@@ -218,7 +218,7 @@ class TestDriveClient:
|
||||
client._drive_service = mock_service
|
||||
client.backup_folder_id = "root_folder"
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "proj_folder_789", "name": "myproject"}]})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "proj_folder_789", "name": "myproject"}]}) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_project_folder("myproject")
|
||||
assert result == "proj_folder_789"
|
||||
@@ -231,7 +231,7 @@ class TestDriveClient:
|
||||
client._drive_service = MagicMock()
|
||||
client.project_folder_cache["cached_proj"] = "cached_id"
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "cached_id", "trashed": False})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "cached_id", "trashed": False}) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_project_folder("cached_proj")
|
||||
assert result == "cached_id"
|
||||
@@ -251,7 +251,7 @@ class TestDriveClient:
|
||||
return {"files": []} # Not found
|
||||
return {"id": f"folder_{call_count['n']}"} # Created
|
||||
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect)
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_nested_folder("parent_id", "a/b")
|
||||
assert result is not None
|
||||
@@ -263,7 +263,7 @@ class TestDriveClient:
|
||||
mock_service = MagicMock()
|
||||
client._drive_service = mock_service
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "file_abc", "name": "test.txt"}]})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "file_abc", "name": "test.txt"}]}) # type: ignore[attr-defined]
|
||||
|
||||
result = client._find_existing_file("test.txt", "parent_folder")
|
||||
assert result is not None
|
||||
@@ -276,7 +276,7 @@ class TestDriveClient:
|
||||
mock_service = MagicMock()
|
||||
client._drive_service = mock_service
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": []})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": []}) # type: ignore[attr-defined]
|
||||
|
||||
result = client._find_existing_file("missing.txt", "parent_folder")
|
||||
assert result is None
|
||||
@@ -288,7 +288,7 @@ class TestDriveClient:
|
||||
mock_service = MagicMock()
|
||||
client._drive_service = mock_service
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "folder_ok", "trashed": False})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "folder_ok", "trashed": False}) # type: ignore[attr-defined]
|
||||
|
||||
result = client._verify_folder_id("folder_ok")
|
||||
assert result is True
|
||||
@@ -300,7 +300,7 @@ class TestDriveClient:
|
||||
mock_service = MagicMock()
|
||||
client._drive_service = mock_service
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "folder_trash", "trashed": True})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "folder_trash", "trashed": True}) # type: ignore[attr-defined]
|
||||
|
||||
result = client._verify_folder_id("folder_trash")
|
||||
assert result is False
|
||||
@@ -312,7 +312,7 @@ class TestDriveClient:
|
||||
mock_service = MagicMock()
|
||||
client._drive_service = mock_service
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"ok": True})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"ok": True}) # type: ignore[attr-defined]
|
||||
mock_request = MagicMock()
|
||||
|
||||
result = client._api_call(mock_request)
|
||||
@@ -332,8 +332,8 @@ class TestDriveClient:
|
||||
raise RuntimeError("transient error")
|
||||
return {"retried": True}
|
||||
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect)
|
||||
mod.get_drive_service = MagicMock(return_value=MagicMock())
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect) # type: ignore[attr-defined]
|
||||
mod.get_drive_service = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined]
|
||||
|
||||
result = client._api_call(MagicMock())
|
||||
assert result == {"retried": True}
|
||||
@@ -512,7 +512,7 @@ class TestDriveUpload:
|
||||
test_file.write_text("print('hello')", encoding="utf-8")
|
||||
|
||||
# Mock api_call_with_retry to return file id
|
||||
client_mod.api_call_with_retry = MagicMock(return_value={"id": "new_file_id"})
|
||||
client_mod.api_call_with_retry = MagicMock(return_value={"id": "new_file_id"}) # type: ignore[attr-defined]
|
||||
|
||||
result = mod.upload_single_file(client, test_file, "testproj", tmp_path)
|
||||
assert result is True
|
||||
@@ -533,7 +533,7 @@ class TestDriveUpload:
|
||||
test_file = tmp_path / "existing.py"
|
||||
test_file.write_text("updated content", encoding="utf-8")
|
||||
|
||||
client_mod.api_call_with_retry = MagicMock(return_value={"id": "existing_drive_id"})
|
||||
client_mod.api_call_with_retry = MagicMock(return_value={"id": "existing_drive_id"}) # type: ignore[attr-defined]
|
||||
|
||||
result = mod.upload_single_file(client, test_file, "testproj", tmp_path)
|
||||
assert result is True
|
||||
@@ -577,8 +577,8 @@ class TestDriveUpload:
|
||||
f.write_text(f"content {i}", encoding="utf-8")
|
||||
files.append(f)
|
||||
|
||||
client_mod.api_call_with_retry = MagicMock(return_value={"id": "file_id"})
|
||||
client_mod.get_drive_service = MagicMock(return_value=mock_service)
|
||||
client_mod.api_call_with_retry = MagicMock(return_value={"id": "file_id"}) # type: ignore[attr-defined]
|
||||
client_mod.get_drive_service = MagicMock(return_value=mock_service) # type: ignore[attr-defined]
|
||||
|
||||
progress_calls = []
|
||||
|
||||
@@ -834,24 +834,24 @@ class TestDriveSync:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestDriveTestModule:
|
||||
"""Tests for drive_test module."""
|
||||
class TestDriveCheckModule:
|
||||
"""Tests for drive_check module."""
|
||||
|
||||
def test_handle_command_primary(self) -> None:
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_test")
|
||||
assert mod.handle_command("drive_test", []) is True
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_check")
|
||||
assert mod.handle_command("drive_check", []) is True
|
||||
|
||||
def test_handle_command_help(self) -> None:
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_test")
|
||||
assert mod.handle_command("drive_test", ["--help"]) is True
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_check")
|
||||
assert mod.handle_command("drive_check", ["--help"]) is True
|
||||
|
||||
def test_handle_command_wrong(self) -> None:
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_test")
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_check")
|
||||
assert mod.handle_command("wrong", []) is False
|
||||
|
||||
def test_run_drive_test_success(self) -> None:
|
||||
"""Run drive test with mocked success."""
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_test")
|
||||
def test_run_drive_check_success(self) -> None:
|
||||
"""Run drive check with mocked success."""
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_check")
|
||||
|
||||
mock_client_module = MagicMock()
|
||||
mock_client_instance = MagicMock()
|
||||
@@ -871,7 +871,7 @@ class TestDriveTestModule:
|
||||
"aipass.backup.apps.handlers.drive.test": mock_test_module,
|
||||
},
|
||||
):
|
||||
result = mod.run_drive_test()
|
||||
result = mod.run_drive_check()
|
||||
assert result is True
|
||||
|
||||
|
||||
@@ -978,7 +978,7 @@ class TestThreadSafety:
|
||||
return {"id": "proj_folder_unique"}
|
||||
return {"trashed": False}
|
||||
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect)
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect) # type: ignore[attr-defined]
|
||||
|
||||
results = []
|
||||
|
||||
@@ -1001,7 +1001,7 @@ class TestThreadSafety:
|
||||
client._drive_service = MagicMock()
|
||||
client.backup_folder_id = "already_set"
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "already_set", "trashed": False})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"id": "already_set", "trashed": False}) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_backup_folder()
|
||||
assert result == "already_set"
|
||||
@@ -1014,7 +1014,7 @@ class TestThreadSafety:
|
||||
client._drive_service = MagicMock()
|
||||
client.file_tracker = {"existing.txt": {"drive_id": "abc"}}
|
||||
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "found_folder", "name": "AIPass Backups"}]})
|
||||
mod.api_call_with_retry = MagicMock(return_value={"files": [{"id": "found_folder", "name": "AIPass Backups"}]}) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_backup_folder()
|
||||
assert result == "found_folder"
|
||||
@@ -1037,7 +1037,7 @@ class TestThreadSafety:
|
||||
return {"id": "brand_new_folder"}
|
||||
return {"id": "brand_new_folder", "trashed": False}
|
||||
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect)
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_backup_folder()
|
||||
assert result == "brand_new_folder"
|
||||
@@ -1060,7 +1060,7 @@ class TestThreadSafety:
|
||||
return {"id": "new_folder"}
|
||||
return {"id": "new_folder", "trashed": False}
|
||||
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect)
|
||||
mod.api_call_with_retry = MagicMock(side_effect=_side_effect) # type: ignore[attr-defined]
|
||||
|
||||
result = client.get_or_create_backup_folder()
|
||||
assert result == "new_folder"
|
||||
@@ -1106,11 +1106,11 @@ class TestCommandRouting:
|
||||
assert mod.handle_command("drive_sync", []) is True
|
||||
assert mod.handle_command("drive-sync", []) is False
|
||||
|
||||
def test_drive_test_routes_underscore(self) -> None:
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_test")
|
||||
assert mod.PRIMARY_COMMAND == "drive_test"
|
||||
assert mod.handle_command("drive_test", []) is True
|
||||
assert mod.handle_command("drive-test", []) is False
|
||||
def test_drive_check_routes_underscore(self) -> None:
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_check")
|
||||
assert mod.PRIMARY_COMMAND == "drive_check"
|
||||
assert mod.handle_command("drive_check", []) is True
|
||||
assert mod.handle_command("drive-check", []) is False
|
||||
|
||||
def test_drive_stats_routes_underscore(self) -> None:
|
||||
mod = _fresh_import("aipass.backup.apps.modules.drive_stats")
|
||||
|
||||
Reference in New Issue
Block a user