diff --git a/src/webhook.rs b/src/webhook.rs index bc0f08f..3a289ac 100644 --- a/src/webhook.rs +++ b/src/webhook.rs @@ -726,32 +726,53 @@ fn remove_webhook_state_keys(state: &mut WebhookState, keys: Vec, url: & fn uninstall_webhook_task(task: WebhookUninstallTask) -> Result> { let key = webhook_installation_key(&task.group, &task.endpoint, &task.repo.name); - crate::logln!( - " {} {} {}", - style(if task.dry_run { - "would uninstall" - } else { - "uninstall" - }) - .red() - .bold(), - style(&task.repo.name).cyan(), - style(format!("from {}", task.endpoint.label())).dim() - ); if task.dry_run { + crate::logln!( + " {} {} {}", + style("would uninstall").red().bold(), + style(&task.repo.name).cyan(), + style(format!("from {}", task.endpoint.label())).dim() + ); return Ok(None); } let client = ProviderClient::new(&task.site)?; - client - .uninstall_webhook(&task.endpoint, &task.repo.name, &task.url) - .with_context(|| { + match client.uninstall_webhook(&task.endpoint, &task.repo.name, &task.url) { + Ok(true) => { + crate::logln!( + " {} {} {}", + style("uninstall").red().bold(), + style(&task.repo.name).cyan(), + style(format!("from {}", task.endpoint.label())).dim() + ); + Ok(Some(key)) + } + Ok(false) => { + crate::logln!( + " {} {} {}", + style("missing").yellow().bold(), + style(&task.repo.name).cyan(), + style(format!("webhook from {}", task.endpoint.label())).dim() + ); + Ok(None) + } + Err(error) if non_actionable_webhook_failure_reason(&error).is_some() => { + let reason = non_actionable_webhook_failure_reason(&error).unwrap(); + crate::logln!( + " {} {} {}", + style("skip").yellow().bold(), + style(&task.repo.name).cyan(), + style(format!("from {}: {reason}", task.endpoint.label())).dim() + ); + Ok(None) + } + Err(error) => Err(error).with_context(|| { format!( "failed to uninstall webhook for {} from {}", task.repo.name, task.endpoint.label() ) - })?; - Ok(Some(key)) + }), + } } fn non_actionable_webhook_failure_reason(error: &anyhow::Error) -> Option { diff --git a/tests/unit/provider.rs b/tests/unit/provider.rs index 68a630e..8e9a2ef 100644 --- a/tests/unit/provider.rs +++ b/tests/unit/provider.rs @@ -255,6 +255,40 @@ fn uninstall_webhook_deletes_matching_github_hook() { handle.join().unwrap(); } +#[test] +fn uninstall_webhook_reports_missing_github_hook() { + let (api_url, handle) = one_request_server( + "200 OK", + r#"[{"id":42,"config":{"url":"https://old.example.test/webhook"}}]"#, + |request| { + assert!( + request.starts_with("GET /repos/alice/repo/hooks "), + "request was {request}" + ) + }, + ); + let site = SiteConfig { + api_url: Some(api_url), + ..site(ProviderKind::Github, None) + }; + let client = ProviderClient::new(&site).unwrap(); + + let removed = client + .uninstall_webhook( + &EndpointConfig { + site: "github".to_string(), + kind: NamespaceKind::User, + namespace: "alice".to_string(), + }, + "repo", + "https://mirror.example.test/webhook", + ) + .unwrap(); + + assert!(!removed); + handle.join().unwrap(); +} + #[test] fn delete_repo_deletes_github_repo() { let (api_url, handle) = one_request_server("204 No Content", "", |request| { diff --git a/tests/unit/webhook.rs b/tests/unit/webhook.rs index 3d06243..438ab3b 100644 --- a/tests/unit/webhook.rs +++ b/tests/unit/webhook.rs @@ -450,7 +450,7 @@ fn install_task_state_cache_is_only_used_for_sync() { } #[test] -fn uninstall_task_removes_state_even_when_hook_is_missing() { +fn uninstall_task_keeps_state_when_hook_is_missing() { let (api_url, handle) = one_request_server("200 OK", "[]", |request| { assert!(request.starts_with("GET /repos/alice/repo/hooks ")) }); @@ -473,7 +473,37 @@ fn uninstall_task_removes_state_even_when_hook_is_missing() { }) .unwrap(); - assert_eq!(key.as_deref(), Some("sync-1\tgithub\tUser\talice\trepo")); + assert_eq!(key, None); + handle.join().unwrap(); +} + +#[test] +fn blocked_webhook_uninstall_is_skipped() { + let (api_url, handle) = one_request_server( + "403 Forbidden", + r#"{"message":"Repository access blocked","block":{"reason":"sensitive_data","created_at":"2021-05-18T16:29:54Z","html_url":"https://github.com/tos"}}"#, + |request| assert!(request.starts_with("GET /repos/alice/repo/hooks ")), + ); + + let key = uninstall_webhook_task(WebhookUninstallTask { + group: "sync-1".to_string(), + site: SiteConfig { + api_url: Some(api_url), + ..site("github", ProviderKind::Github) + }, + endpoint: endpoint("github", NamespaceKind::User, "alice"), + repo: RemoteRepo { + name: "repo".to_string(), + clone_url: "https://github.com/alice/repo.git".to_string(), + private: true, + description: None, + }, + url: "https://mirror.example.test/webhook".to_string(), + dry_run: false, + }) + .unwrap(); + + assert_eq!(key, None); handle.join().unwrap(); }