269 lines
8.3 KiB
Rust
269 lines
8.3 KiB
Rust
use super::*;
|
|
|
|
fn default_options() -> SyncOptions {
|
|
SyncOptions {
|
|
full: false,
|
|
force: false,
|
|
no_embed: false,
|
|
no_docs: false,
|
|
no_events: false,
|
|
robot_mode: false,
|
|
dry_run: false,
|
|
issue_iids: vec![],
|
|
mr_iids: vec![],
|
|
project: None,
|
|
preflight_only: false,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn append_failures_skips_zeroes() {
|
|
let mut summary = "base".to_string();
|
|
append_failures(&mut summary, &[("errors", 0), ("failures", 0)]);
|
|
assert_eq!(summary, "base");
|
|
}
|
|
|
|
#[test]
|
|
fn append_failures_renders_non_zero_counts() {
|
|
let mut summary = "base".to_string();
|
|
append_failures(&mut summary, &[("errors", 2), ("failures", 1)]);
|
|
assert!(summary.contains("base"));
|
|
assert!(summary.contains("2 errors"));
|
|
assert!(summary.contains("1 failures"));
|
|
}
|
|
|
|
#[test]
|
|
fn summarize_status_enrichment_reports_skipped_when_all_skipped() {
|
|
let projects = vec![ProjectStatusEnrichment {
|
|
path: "vs/typescript-code".to_string(),
|
|
mode: "skipped".to_string(),
|
|
reason: None,
|
|
seen: 0,
|
|
enriched: 0,
|
|
cleared: 0,
|
|
without_widget: 0,
|
|
partial_errors: 0,
|
|
first_partial_error: None,
|
|
error: None,
|
|
}];
|
|
|
|
let (summary, has_errors) = summarize_status_enrichment(&projects);
|
|
assert!(summary.contains("0 statuses updated"));
|
|
assert!(summary.contains("skipped"));
|
|
assert!(!has_errors);
|
|
}
|
|
|
|
#[test]
|
|
fn summarize_status_enrichment_reports_errors() {
|
|
let projects = vec![ProjectStatusEnrichment {
|
|
path: "vs/typescript-code".to_string(),
|
|
mode: "fetched".to_string(),
|
|
reason: None,
|
|
seen: 3,
|
|
enriched: 1,
|
|
cleared: 1,
|
|
without_widget: 0,
|
|
partial_errors: 2,
|
|
first_partial_error: None,
|
|
error: Some("boom".to_string()),
|
|
}];
|
|
|
|
let (summary, has_errors) = summarize_status_enrichment(&projects);
|
|
assert!(summary.contains("1 statuses updated"));
|
|
assert!(summary.contains("1 cleared"));
|
|
assert!(summary.contains("3 seen"));
|
|
assert!(summary.contains("3 errors"));
|
|
assert!(has_errors);
|
|
}
|
|
|
|
#[test]
|
|
fn should_print_timings_only_when_enabled_and_non_empty() {
|
|
let stages = vec![StageTiming {
|
|
name: "x".to_string(),
|
|
elapsed_ms: 10,
|
|
items_processed: 0,
|
|
items_skipped: 0,
|
|
errors: 0,
|
|
rate_limit_hits: 0,
|
|
retries: 0,
|
|
project: None,
|
|
sub_stages: vec![],
|
|
}];
|
|
|
|
assert!(should_print_timings(true, &stages));
|
|
assert!(!should_print_timings(false, &stages));
|
|
assert!(!should_print_timings(true, &[]));
|
|
}
|
|
|
|
#[test]
|
|
fn issue_sub_rows_include_project_and_statuses() {
|
|
let rows = issue_sub_rows(&[ProjectSummary {
|
|
path: "vs/typescript-code".to_string(),
|
|
items_upserted: 2,
|
|
discussions_synced: 0,
|
|
events_fetched: 0,
|
|
events_failed: 0,
|
|
statuses_enriched: 1,
|
|
statuses_seen: 5,
|
|
status_errors: 0,
|
|
mr_diffs_fetched: 0,
|
|
mr_diffs_failed: 0,
|
|
}]);
|
|
|
|
assert_eq!(rows.len(), 1);
|
|
assert!(rows[0].contains("vs/typescript-code"));
|
|
assert!(rows[0].contains("2 issues"));
|
|
assert!(rows[0].contains("1 statuses updated"));
|
|
}
|
|
|
|
#[test]
|
|
fn mr_sub_rows_include_project_and_diff_failures() {
|
|
let rows = mr_sub_rows(&[ProjectSummary {
|
|
path: "vs/python-code".to_string(),
|
|
items_upserted: 3,
|
|
discussions_synced: 0,
|
|
events_fetched: 0,
|
|
events_failed: 0,
|
|
statuses_enriched: 0,
|
|
statuses_seen: 0,
|
|
status_errors: 0,
|
|
mr_diffs_fetched: 4,
|
|
mr_diffs_failed: 1,
|
|
}]);
|
|
|
|
assert_eq!(rows.len(), 1);
|
|
assert!(rows[0].contains("vs/python-code"));
|
|
assert!(rows[0].contains("3 MRs"));
|
|
assert!(rows[0].contains("4 diffs"));
|
|
assert!(rows[0].contains("1 diff failures"));
|
|
}
|
|
|
|
#[test]
|
|
fn status_sub_rows_include_project_and_skip_reason() {
|
|
let rows = status_sub_rows(&[ProjectStatusEnrichment {
|
|
path: "vs/python-code".to_string(),
|
|
mode: "skipped".to_string(),
|
|
reason: Some("disabled".to_string()),
|
|
seen: 0,
|
|
enriched: 0,
|
|
cleared: 0,
|
|
without_widget: 0,
|
|
partial_errors: 0,
|
|
first_partial_error: None,
|
|
error: None,
|
|
}]);
|
|
|
|
assert_eq!(rows.len(), 1);
|
|
assert!(rows[0].contains("vs/python-code"));
|
|
assert!(rows[0].contains("0 statuses updated"));
|
|
assert!(rows[0].contains("skipped (disabled)"));
|
|
}
|
|
|
|
#[test]
|
|
fn is_surgical_with_issues() {
|
|
let opts = SyncOptions {
|
|
issue_iids: vec![1],
|
|
..default_options()
|
|
};
|
|
assert!(opts.is_surgical());
|
|
}
|
|
|
|
#[test]
|
|
fn is_surgical_with_mrs() {
|
|
let opts = SyncOptions {
|
|
mr_iids: vec![10],
|
|
..default_options()
|
|
};
|
|
assert!(opts.is_surgical());
|
|
}
|
|
|
|
#[test]
|
|
fn is_surgical_empty() {
|
|
let opts = default_options();
|
|
assert!(!opts.is_surgical());
|
|
}
|
|
|
|
#[test]
|
|
fn max_surgical_targets_is_100() {
|
|
assert_eq!(SyncOptions::MAX_SURGICAL_TARGETS, 100);
|
|
}
|
|
|
|
#[test]
|
|
fn sync_result_default_omits_surgical_fields() {
|
|
let result = SyncResult::default();
|
|
let json = serde_json::to_value(&result).unwrap();
|
|
assert!(json.get("surgical_mode").is_none());
|
|
assert!(json.get("surgical_iids").is_none());
|
|
assert!(json.get("entity_results").is_none());
|
|
assert!(json.get("preflight_only").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn sync_result_with_surgical_fields_serializes_correctly() {
|
|
let result = SyncResult {
|
|
surgical_mode: Some(true),
|
|
surgical_iids: Some(SurgicalIids {
|
|
issues: vec![7, 42],
|
|
merge_requests: vec![10],
|
|
}),
|
|
entity_results: Some(vec![
|
|
EntitySyncResult {
|
|
entity_type: "issue".to_string(),
|
|
iid: 7,
|
|
outcome: "synced".to_string(),
|
|
error: None,
|
|
toctou_reason: None,
|
|
},
|
|
EntitySyncResult {
|
|
entity_type: "issue".to_string(),
|
|
iid: 42,
|
|
outcome: "skipped_toctou".to_string(),
|
|
error: None,
|
|
toctou_reason: Some("updated_at changed".to_string()),
|
|
},
|
|
]),
|
|
preflight_only: Some(false),
|
|
..SyncResult::default()
|
|
};
|
|
let json = serde_json::to_value(&result).unwrap();
|
|
assert_eq!(json["surgical_mode"], true);
|
|
assert_eq!(json["surgical_iids"]["issues"], serde_json::json!([7, 42]));
|
|
assert_eq!(json["entity_results"].as_array().unwrap().len(), 2);
|
|
assert_eq!(json["entity_results"][1]["outcome"], "skipped_toctou");
|
|
assert_eq!(json["preflight_only"], false);
|
|
}
|
|
|
|
#[test]
|
|
fn entity_sync_result_omits_none_fields() {
|
|
let entity = EntitySyncResult {
|
|
entity_type: "merge_request".to_string(),
|
|
iid: 10,
|
|
outcome: "synced".to_string(),
|
|
error: None,
|
|
toctou_reason: None,
|
|
};
|
|
let json = serde_json::to_value(&entity).unwrap();
|
|
assert!(json.get("error").is_none());
|
|
assert!(json.get("toctou_reason").is_none());
|
|
assert!(json.get("entity_type").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn is_surgical_with_both_issues_and_mrs() {
|
|
let opts = SyncOptions {
|
|
issue_iids: vec![1, 2],
|
|
mr_iids: vec![10],
|
|
..default_options()
|
|
};
|
|
assert!(opts.is_surgical());
|
|
}
|
|
|
|
#[test]
|
|
fn is_not_surgical_with_only_project() {
|
|
let opts = SyncOptions {
|
|
project: Some("group/repo".to_string()),
|
|
..default_options()
|
|
};
|
|
assert!(!opts.is_surgical());
|
|
}
|