From d8756e64016c255798d45a062287e922004a9966 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 03:49:12 +0000 Subject: [PATCH 1/5] Initial plan From dd010d3ec0503dec7f3b2829992da02a7c03d5c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 03:54:40 +0000 Subject: [PATCH 2/5] Fix PR #184 feedback: split long lines, add docstring, update tests Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 10 +++++++-- test_main.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index da672176..edd8f5d0 100644 --- a/main.py +++ b/main.py @@ -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 @@ -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 " + "Control D Profile URL. Comma-separate for multiple.", ) profile_ids = [ extract_profile_id(p) for p in p_input.split(",") if p.strip() diff --git a/test_main.py b/test_main.py index 66337173..9c7c207d 100644 --- a/test_main.py +++ b/test_main.py @@ -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("User pressed Ctrl+C")) + ) + + # 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) @@ -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 From 1d2d5bb5dad3226071fcc2737d29ca2b5db11b36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 03:55:41 +0000 Subject: [PATCH 3/5] Address code review feedback: improve color reset placement and simplify exception Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 6 ++++-- test_main.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index edd8f5d0..82941900 100644 --- a/main.py +++ b/main.py @@ -1484,10 +1484,12 @@ def main(): print(f"{Colors.CYAN}ℹ Profile ID is missing.{Colors.ENDC}") print( f"{Colors.CYAN} Find it in your profile URL at " - f"https://controld.com/dashboard/profiles{Colors.ENDC}" + f"https://controld.com/dashboard/profiles" + f"{Colors.ENDC}" ) print( - f"{Colors.CYAN} (You can also paste the full profile URL).{Colors.ENDC}" + f"{Colors.CYAN} (You can also paste the full profile URL)." + f"{Colors.ENDC}" ) def validate_profile_input(value: str) -> bool: diff --git a/test_main.py b/test_main.py index 9c7c207d..eaeb7b0f 100644 --- a/test_main.py +++ b/test_main.py @@ -268,7 +268,7 @@ def test_keyboard_interrupt_during_setup(monkeypatch, capsys): # Mock input to raise KeyboardInterrupt (simulating Ctrl+C) monkeypatch.setattr( - "builtins.input", MagicMock(side_effect=KeyboardInterrupt("User pressed Ctrl+C")) + "builtins.input", MagicMock(side_effect=KeyboardInterrupt()) ) # Mock parse_args From dea7cecd326557219355cfaebe6b929758257892 Mon Sep 17 00:00:00 2001 From: Abhi Mehrotra Date: Thu, 12 Feb 2026 22:01:26 -0600 Subject: [PATCH 4/5] Update main.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 82941900..edd8f5d0 100644 --- a/main.py +++ b/main.py @@ -1484,12 +1484,10 @@ def main(): print(f"{Colors.CYAN}ℹ Profile ID is missing.{Colors.ENDC}") print( f"{Colors.CYAN} Find it in your profile URL at " - f"https://controld.com/dashboard/profiles" - f"{Colors.ENDC}" + f"https://controld.com/dashboard/profiles{Colors.ENDC}" ) print( - f"{Colors.CYAN} (You can also paste the full profile URL)." - f"{Colors.ENDC}" + f"{Colors.CYAN} (You can also paste the full profile URL).{Colors.ENDC}" ) def validate_profile_input(value: str) -> bool: From 073c539f22c6fbc111f2e130a1e3ac6bb868261a Mon Sep 17 00:00:00 2001 From: Abhi Mehrotra Date: Thu, 12 Feb 2026 22:01:48 -0600 Subject: [PATCH 5/5] Update main.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index edd8f5d0..47b135f3 100644 --- a/main.py +++ b/main.py @@ -1501,7 +1501,7 @@ def validate_profile_input(value: str) -> bool: 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 " - "Control D Profile URL. Comma-separate for multiple.", + "a Control D Profile URL. Comma-separate for multiple.", ) profile_ids = [ extract_profile_id(p) for p in p_input.split(",") if p.strip()