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()); }