Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1483,10 +1483,15 @@ def main():
if not profile_ids:
print(f"{Colors.CYAN}ℹ Profile ID is missing.{Colors.ENDC}")
print(
f"{Colors.CYAN} You can find this in the URL of your profile in the Control D Dashboard at https://controld.com/dashboard/profiles (or just paste the URL).{Colors.ENDC}"
f"{Colors.CYAN} Find it in your profile URL at "
f"https://controld.com/dashboard/profiles{Colors.ENDC}"
)
print(
f"{Colors.CYAN} (You can also paste the full profile URL).{Colors.ENDC}"
)

def validate_profile_input(value: str) -> bool:
"""Validate comma-separated profile IDs or URLs."""
ids = [extract_profile_id(p) for p in value.split(",") if p.strip()]
return bool(ids) and all(
validate_profile_id(pid, log_errors=False) for pid in ids
Expand All @@ -1495,7 +1500,8 @@ def validate_profile_input(value: str) -> bool:
p_input = get_validated_input(
f"{Colors.BOLD}Enter Control D Profile ID:{Colors.ENDC} ",
validate_profile_input,
"Invalid ID(s) or URL(s). Must be a valid Profile ID or a Control D Profile URL. Comma-separate for multiple.",
"Invalid ID(s) or URL(s). Must be a valid Profile ID or "
"a Control D Profile URL. Comma-separate for multiple.",
)
profile_ids = [
extract_profile_id(p) for p in p_input.split(",") if p.strip()
Expand Down
59 changes: 57 additions & 2 deletions test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,65 @@ def test_interactive_prompts_show_hints(monkeypatch, capsys):
captured = capsys.readouterr()
stdout = captured.out

assert "You can find this in the URL of your profile" in stdout
# Verify both hint URLs are displayed
assert "https://controld.com/dashboard/profiles" in stdout
assert "https://controld.com/account/manage-account" in stdout


def test_keyboard_interrupt_during_setup(monkeypatch, capsys):
"""Test that KeyboardInterrupt during setup phase exits gracefully with code 130."""
# Ensure environment is clean
monkeypatch.delenv("PROFILE", raising=False)
monkeypatch.delenv("TOKEN", raising=False)

# Prevent dotenv from loading .env file
import dotenv

monkeypatch.setattr(dotenv, "load_dotenv", lambda: None)

# Reload main with isatty=True to trigger interactive mode
m = reload_main_with_env(monkeypatch, isatty=True)

# Mock sys.stdin.isatty to return True
monkeypatch.setattr("sys.stdin.isatty", lambda: True)

# Mock input to raise KeyboardInterrupt (simulating Ctrl+C)
monkeypatch.setattr(
"builtins.input", MagicMock(side_effect=KeyboardInterrupt())
)

# Mock parse_args
mock_args = MagicMock()
mock_args.profiles = None
mock_args.folder_url = None
mock_args.dry_run = False
mock_args.no_delete = False
mock_args.plan_json = None
monkeypatch.setattr(m, "parse_args", lambda: mock_args)

# Mock exit to capture the exit code
exit_code = None

def mock_exit(code):
nonlocal exit_code
exit_code = code
raise SystemExit(code)

monkeypatch.setattr("builtins.exit", mock_exit)

# Run main and expect SystemExit
with pytest.raises(SystemExit):
m.main()

# Verify exit code is 130 (standard for Ctrl+C)
assert exit_code == 130

# Verify user-friendly cancellation message
captured = capsys.readouterr()
stderr = captured.err
assert "Sync cancelled by user" in stderr


# Case 7: verify_access_and_get_folders handles success and errors correctly
def test_verify_access_and_get_folders_success(monkeypatch):
m = reload_main_with_env(monkeypatch)
Expand Down Expand Up @@ -451,7 +506,7 @@ def test_interactive_input_extracts_id(monkeypatch, capsys):

# Verify prompt text update
captured = capsys.readouterr()
assert "(or just paste the URL)" in captured.out
assert "(You can also paste the full profile URL)" in captured.out


# Case 10: validate_profile_id respects log_errors flag
Expand Down