[O] UX
This commit is contained in:
+4
-24
@@ -313,7 +313,7 @@ impl E2eRun {
|
||||
) -> Result<()> {
|
||||
let default_whitelist = format!("^{REPO_PREFIX}{}-", self.run_id);
|
||||
let whitelist = repo_pattern.unwrap_or(&default_whitelist);
|
||||
let mut contents = String::new();
|
||||
let mut contents = "jobs = 1\n\n".to_string();
|
||||
for provider in &self.settings.providers {
|
||||
contents.push_str(&format!(
|
||||
r#"[[sites]]
|
||||
@@ -658,15 +658,7 @@ namespace = "{}"
|
||||
let listener = TcpListener::bind("127.0.0.1:0")?;
|
||||
let addr = listener.local_addr()?;
|
||||
drop(listener);
|
||||
let mut server = self.spawn_refray([
|
||||
"serve",
|
||||
"--listen",
|
||||
&addr.to_string(),
|
||||
"--secret",
|
||||
WEBHOOK_SECRET,
|
||||
"--jobs",
|
||||
"1",
|
||||
])?;
|
||||
let mut server = self.spawn_refray(["serve", "--listen", &addr.to_string()])?;
|
||||
thread::sleep(Duration::from_millis(750));
|
||||
|
||||
let (body, headers) = source.webhook_payload(&repo, WEBHOOK_SECRET);
|
||||
@@ -794,25 +786,13 @@ namespace = "{}"
|
||||
}
|
||||
|
||||
fn sync<const N: usize>(&self, args: [&str; N]) -> Result<()> {
|
||||
let mut command = vec![
|
||||
"sync",
|
||||
"--work-dir",
|
||||
self.work_dir.to_str().unwrap(),
|
||||
"--jobs",
|
||||
"1",
|
||||
];
|
||||
let mut command = vec!["sync", "--work-dir", self.work_dir.to_str().unwrap()];
|
||||
command.extend(args);
|
||||
self.refray(command)
|
||||
}
|
||||
|
||||
fn sync_expect_failure<const N: usize>(&self, args: [&str; N]) -> Result<()> {
|
||||
let mut command = vec![
|
||||
"sync",
|
||||
"--work-dir",
|
||||
self.work_dir.to_str().unwrap(),
|
||||
"--jobs",
|
||||
"1",
|
||||
];
|
||||
let mut command = vec!["sync", "--work-dir", self.work_dir.to_str().unwrap()];
|
||||
command.extend(args);
|
||||
let output = self.refray_output(command)?;
|
||||
if output.status.success() {
|
||||
|
||||
+89
-35
@@ -48,49 +48,56 @@ fn cli_accepts_sync_retry_failed() {
|
||||
assert!(args.retry_failed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_accepts_sync_jobs() {
|
||||
let cli = Cli::try_parse_from(["refray", "sync", "--jobs", "8"]).unwrap();
|
||||
|
||||
let Command::Sync(args) = cli.command else {
|
||||
panic!("parsed unexpected command");
|
||||
};
|
||||
assert_eq!(args.jobs, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_accepts_webhook_serve() {
|
||||
let cli = Cli::try_parse_from([
|
||||
"refray",
|
||||
"serve",
|
||||
"--listen",
|
||||
"127.0.0.1:9000",
|
||||
"--secret-env",
|
||||
"WEBHOOK_SECRET",
|
||||
"--jobs",
|
||||
"2",
|
||||
"--full-sync-interval-minutes",
|
||||
"30",
|
||||
])
|
||||
.unwrap();
|
||||
let cli = Cli::try_parse_from(["refray", "serve", "--listen", "127.0.0.1:9000"]).unwrap();
|
||||
|
||||
let Command::Serve(args) = cli.command else {
|
||||
panic!("parsed unexpected command");
|
||||
};
|
||||
assert_eq!(args.listen, "127.0.0.1:9000");
|
||||
assert_eq!(args.secret_env, Some("WEBHOOK_SECRET".to_string()));
|
||||
assert_eq!(args.jobs, 2);
|
||||
assert_eq!(args.full_sync_interval_minutes, Some(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_rejects_removed_jobs_args() {
|
||||
for args in [
|
||||
["refray", "sync", "--jobs", "8"].as_slice(),
|
||||
["refray", "serve", "--jobs", "2"].as_slice(),
|
||||
["refray", "webhook", "install", "--jobs", "6"].as_slice(),
|
||||
["refray", "webhook", "uninstall", "--jobs", "3"].as_slice(),
|
||||
[
|
||||
"refray",
|
||||
"webhook",
|
||||
"update",
|
||||
"https://new.example.test/webhook",
|
||||
"--jobs",
|
||||
"5",
|
||||
]
|
||||
.as_slice(),
|
||||
] {
|
||||
assert!(Cli::try_parse_from(args).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_rejects_removed_serve_args() {
|
||||
for args in [
|
||||
["refray", "serve", "--secret", "secret"].as_slice(),
|
||||
["refray", "serve", "--secret-env", "WEBHOOK_SECRET"].as_slice(),
|
||||
["refray", "serve", "--full-sync-interval-minutes", "30"].as_slice(),
|
||||
] {
|
||||
assert!(Cli::try_parse_from(args).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_accepts_webhook_install() {
|
||||
let cli = Cli::try_parse_from(["refray", "webhook", "install", "--jobs", "6"]).unwrap();
|
||||
let cli = Cli::try_parse_from(["refray", "webhook", "install"]).unwrap();
|
||||
|
||||
let Command::Webhook(WebhookCommand::Install(args)) = cli.command else {
|
||||
panic!("parsed unexpected command");
|
||||
};
|
||||
assert_eq!(args.jobs, 6);
|
||||
assert!(!args.dry_run);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -124,20 +131,37 @@ fn cli_rejects_removed_webhook_install_args() {
|
||||
|
||||
#[test]
|
||||
fn cli_accepts_webhook_uninstall() {
|
||||
let cli = Cli::try_parse_from(["refray", "webhook", "uninstall", "--dry-run", "--jobs", "3"])
|
||||
.unwrap();
|
||||
let cli = Cli::try_parse_from(["refray", "webhook", "uninstall", "--dry-run"]).unwrap();
|
||||
|
||||
let Command::Webhook(WebhookCommand::Uninstall(args)) = cli.command else {
|
||||
panic!("parsed unexpected command");
|
||||
};
|
||||
assert_eq!(args.url, None);
|
||||
assert!(args.dry_run);
|
||||
assert_eq!(args.jobs, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_accepts_webhook_uninstall_url() {
|
||||
let cli = Cli::try_parse_from([
|
||||
"refray",
|
||||
"webhook",
|
||||
"uninstall",
|
||||
"https://old.example.test/webhook",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let Command::Webhook(WebhookCommand::Uninstall(args)) = cli.command else {
|
||||
panic!("parsed unexpected command");
|
||||
};
|
||||
assert_eq!(
|
||||
args.url,
|
||||
Some("https://old.example.test/webhook".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_rejects_removed_webhook_uninstall_args() {
|
||||
for args in [
|
||||
["refray", "webhook", "uninstall", "repo-one"].as_slice(),
|
||||
[
|
||||
"refray",
|
||||
"webhook",
|
||||
@@ -160,6 +184,39 @@ fn cli_rejects_removed_webhook_uninstall_args() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_rejects_invalid_webhook_uninstall_url() {
|
||||
for args in [
|
||||
["refray", "webhook", "uninstall", "repo-one"].as_slice(),
|
||||
[
|
||||
"refray",
|
||||
"webhook",
|
||||
"uninstall",
|
||||
"ftp://example.test/webhook",
|
||||
]
|
||||
.as_slice(),
|
||||
] {
|
||||
assert!(Cli::try_parse_from(args).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn webhook_uninstall_url_uses_arg_before_config() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
webhook: None,
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
let url = resolve_uninstall_webhook_url(
|
||||
&config,
|
||||
Some("https://old.example.test/webhook".to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(url, "https://old.example.test/webhook");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_accepts_webhook_update() {
|
||||
let cli = Cli::try_parse_from([
|
||||
@@ -167,8 +224,6 @@ fn cli_accepts_webhook_update() {
|
||||
"webhook",
|
||||
"update",
|
||||
"https://new.example.test/webhook",
|
||||
"--jobs",
|
||||
"5",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
@@ -176,7 +231,6 @@ fn cli_accepts_webhook_update() {
|
||||
panic!("parsed unexpected command");
|
||||
};
|
||||
assert_eq!(args.url, "https://new.example.test/webhook");
|
||||
assert_eq!(args.jobs, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -4,6 +4,8 @@ use super::*;
|
||||
fn parses_token_forms() {
|
||||
let config: Config = toml::from_str(
|
||||
r#"
|
||||
jobs = 8
|
||||
|
||||
[webhook]
|
||||
install = true
|
||||
url = "https://mirror.example.test/webhook"
|
||||
@@ -40,6 +42,7 @@ fn parses_token_forms() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(config.jobs, 8);
|
||||
assert_eq!(config.sites.len(), 1);
|
||||
assert_eq!(config.mirrors[0].endpoints.len(), 2);
|
||||
assert_eq!(
|
||||
@@ -65,9 +68,17 @@ fn parses_token_forms() {
|
||||
assert_eq!(webhook.full_sync_interval_minutes, Some(60));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_defaults_jobs() {
|
||||
let config: Config = toml::from_str("").unwrap();
|
||||
|
||||
assert_eq!(config.jobs, DEFAULT_JOBS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_rejects_unknown_sites_and_single_endpoint_groups() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![site("github", ProviderKind::Github)],
|
||||
mirrors: vec![MirrorConfig {
|
||||
name: "broken".to_string(),
|
||||
@@ -90,6 +101,7 @@ fn validation_rejects_unknown_sites_and_single_endpoint_groups() {
|
||||
assert!(err.contains("at least two endpoints"));
|
||||
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![site("github", ProviderKind::Github)],
|
||||
mirrors: vec![MirrorConfig {
|
||||
name: "broken".to_string(),
|
||||
@@ -185,6 +197,7 @@ fn repo_name_filter_applies_whitelist_then_blacklist() {
|
||||
#[test]
|
||||
fn validation_rejects_invalid_repo_filter_regex() {
|
||||
let mut config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![site("github", ProviderKind::Github)],
|
||||
mirrors: vec![mirror_config()],
|
||||
webhook: None,
|
||||
@@ -196,6 +209,22 @@ fn validation_rejects_invalid_repo_filter_regex() {
|
||||
assert!(err.contains("invalid repo_whitelist regex"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_rejects_zero_jobs() {
|
||||
let mut config = Config {
|
||||
jobs: 0,
|
||||
sites: vec![site("github", ProviderKind::Github)],
|
||||
mirrors: vec![mirror_config()],
|
||||
webhook: None,
|
||||
};
|
||||
|
||||
let err = validate_config(&config).unwrap_err().to_string();
|
||||
|
||||
assert!(err.contains("jobs must be at least 1"));
|
||||
config.jobs = DEFAULT_JOBS;
|
||||
validate_config(&config).unwrap();
|
||||
}
|
||||
|
||||
fn mirror_config() -> MirrorConfig {
|
||||
MirrorConfig {
|
||||
name: "personal".to_string(),
|
||||
|
||||
@@ -139,6 +139,7 @@ fn wizard_can_enable_webhooks() {
|
||||
#[test]
|
||||
fn wizard_reuses_existing_credentials_for_same_instance() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![SiteConfig {
|
||||
name: "github".to_string(),
|
||||
provider: ProviderKind::Github,
|
||||
@@ -177,6 +178,7 @@ fn wizard_reuses_existing_credentials_for_same_instance() {
|
||||
#[test]
|
||||
fn wizard_starts_existing_config_at_sync_group_menu() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![
|
||||
SiteConfig {
|
||||
name: "github".to_string(),
|
||||
@@ -234,6 +236,7 @@ fn wizard_starts_existing_config_at_sync_group_menu() {
|
||||
#[test]
|
||||
fn wizard_can_ask_to_run_full_sync_after_config() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: Vec::new(),
|
||||
mirrors: vec![MirrorConfig {
|
||||
name: "sync-1".to_string(),
|
||||
@@ -274,6 +277,7 @@ fn wizard_skips_full_sync_prompt_without_sync_groups() {
|
||||
#[test]
|
||||
fn wizard_edits_existing_sync_group_from_menu() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![
|
||||
SiteConfig {
|
||||
name: "github".to_string(),
|
||||
@@ -371,6 +375,7 @@ fn wizard_edits_existing_sync_group_from_menu() {
|
||||
#[test]
|
||||
fn wizard_prefills_existing_sync_group_when_editing() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![
|
||||
SiteConfig {
|
||||
name: "github".to_string(),
|
||||
@@ -435,6 +440,7 @@ fn wizard_prefills_existing_sync_group_when_editing() {
|
||||
#[test]
|
||||
fn wizard_deletes_existing_sync_group_from_menu() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![
|
||||
SiteConfig {
|
||||
name: "github".to_string(),
|
||||
@@ -494,6 +500,7 @@ fn wizard_deletes_existing_sync_group_from_menu() {
|
||||
#[test]
|
||||
fn wizard_can_go_back_from_delete_menu() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![
|
||||
SiteConfig {
|
||||
name: "github".to_string(),
|
||||
|
||||
@@ -100,6 +100,7 @@ fn parses_gitlab_push_payload() {
|
||||
#[test]
|
||||
fn matches_jobs_by_provider_and_namespace() {
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![
|
||||
site("github", ProviderKind::Github),
|
||||
site("gitea", ProviderKind::Gitea),
|
||||
@@ -147,6 +148,7 @@ fn matching_jobs_respects_repo_name_filters() {
|
||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||
};
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![site("github", ProviderKind::Github)],
|
||||
mirrors: vec![mirror.clone()],
|
||||
webhook: None,
|
||||
@@ -161,6 +163,7 @@ fn matching_jobs_respects_repo_name_filters() {
|
||||
|
||||
mirror.repo_whitelist.clear();
|
||||
let config = Config {
|
||||
jobs: crate::config::DEFAULT_JOBS,
|
||||
sites: vec![site("github", ProviderKind::Github)],
|
||||
mirrors: vec![mirror],
|
||||
webhook: None,
|
||||
@@ -358,6 +361,50 @@ fn uninstall_task_removes_state_even_when_hook_is_missing() {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uninstall_state_cleanup_only_removes_matching_url() {
|
||||
let endpoint = endpoint("github", NamespaceKind::User, "alice");
|
||||
let key = webhook_installation_key("sync-1", &endpoint, "repo");
|
||||
let mut state = WebhookState::default();
|
||||
state.installations.insert(
|
||||
key.clone(),
|
||||
WebhookInstallation {
|
||||
group: "sync-1".to_string(),
|
||||
endpoint: endpoint.clone(),
|
||||
repo: "repo".to_string(),
|
||||
url: "https://current.example.test/webhook".to_string(),
|
||||
},
|
||||
);
|
||||
state.skipped.insert(
|
||||
key.clone(),
|
||||
SkippedWebhookInstallation {
|
||||
group: "sync-1".to_string(),
|
||||
endpoint,
|
||||
repo: "repo".to_string(),
|
||||
url: "https://current.example.test/webhook".to_string(),
|
||||
reason: "provider blocked access".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
remove_webhook_state_keys(
|
||||
&mut state,
|
||||
vec![key.clone()],
|
||||
"https://old.example.test/webhook",
|
||||
);
|
||||
|
||||
assert!(state.installations.contains_key(&key));
|
||||
assert!(state.skipped.contains_key(&key));
|
||||
|
||||
remove_webhook_state_keys(
|
||||
&mut state,
|
||||
vec![key.clone()],
|
||||
"https://current.example.test/webhook",
|
||||
);
|
||||
|
||||
assert!(!state.installations.contains_key(&key));
|
||||
assert!(!state.skipped.contains_key(&key));
|
||||
}
|
||||
|
||||
fn site(name: &str, provider: ProviderKind) -> SiteConfig {
|
||||
SiteConfig {
|
||||
name: name.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user