test(ingestion): add MR + nonzero_summary tests, close bd-1au9

This commit is contained in:
teernisse
2026-02-19 10:01:47 -05:00
parent 61fbc234d8
commit 386dd884ec
4 changed files with 742 additions and 57 deletions

File diff suppressed because one or more lines are too long

View File

@@ -423,59 +423,5 @@ fn parse_timestamp(ts: &str) -> Result<i64> {
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn result_default_has_zero_counts() {
let result = IngestMergeRequestsResult::default();
assert_eq!(result.fetched, 0);
assert_eq!(result.upserted, 0);
assert_eq!(result.labels_created, 0);
assert_eq!(result.assignees_linked, 0);
assert_eq!(result.reviewers_linked, 0);
}
#[test]
fn cursor_filter_allows_newer_mrs() {
let cursor = SyncCursor {
updated_at_cursor: Some(1705312800000),
tie_breaker_id: Some(100),
};
let later_ts = 1705399200000;
assert!(passes_cursor_filter_with_ts(101, later_ts, &cursor));
}
#[test]
fn cursor_filter_blocks_older_mrs() {
let cursor = SyncCursor {
updated_at_cursor: Some(1705312800000),
tie_breaker_id: Some(100),
};
let earlier_ts = 1705226400000;
assert!(!passes_cursor_filter_with_ts(99, earlier_ts, &cursor));
}
#[test]
fn cursor_filter_uses_tie_breaker_for_same_timestamp() {
let cursor = SyncCursor {
updated_at_cursor: Some(1705312800000),
tie_breaker_id: Some(100),
};
assert!(passes_cursor_filter_with_ts(101, 1705312800000, &cursor));
assert!(!passes_cursor_filter_with_ts(100, 1705312800000, &cursor));
assert!(!passes_cursor_filter_with_ts(99, 1705312800000, &cursor));
}
#[test]
fn cursor_filter_allows_all_when_no_cursor() {
let cursor = SyncCursor::default();
let old_ts = 1577836800000;
assert!(passes_cursor_filter_with_ts(1, old_ts, &cursor));
}
}
#[path = "merge_requests_tests.rs"]
mod tests;

View File

@@ -0,0 +1,704 @@
use std::path::Path;
use super::*;
use crate::core::config::{
EmbeddingConfig, GitLabConfig, LoggingConfig, ProjectConfig, ScoringConfig, StorageConfig,
SyncConfig,
};
use crate::core::db::{create_connection, run_migrations};
use crate::gitlab::types::{GitLabAuthor, GitLabReferences, GitLabReviewer};
// ─── Test Helpers ───────────────────────────────────────────────────────────
fn setup_test_db() -> Connection {
let conn = create_connection(Path::new(":memory:")).unwrap();
run_migrations(&conn).unwrap();
conn.execute(
"INSERT INTO projects (id, gitlab_project_id, path_with_namespace, web_url)
VALUES (1, 100, 'group/project', 'https://gitlab.example.com/group/project')",
[],
)
.unwrap();
conn
}
fn test_config() -> Config {
Config {
gitlab: GitLabConfig {
base_url: "https://gitlab.example.com".to_string(),
token_env_var: "GITLAB_TOKEN".to_string(),
},
projects: vec![ProjectConfig {
path: "group/project".to_string(),
}],
default_project: None,
sync: SyncConfig::default(),
storage: StorageConfig::default(),
embedding: EmbeddingConfig::default(),
logging: LoggingConfig::default(),
scoring: ScoringConfig::default(),
}
}
fn make_test_mr(id: i64, updated_at: &str) -> GitLabMergeRequest {
GitLabMergeRequest {
id,
iid: id,
project_id: 100,
title: format!("MR {}", id),
description: None,
state: "opened".to_string(),
draft: false,
work_in_progress: false,
source_branch: "feature".to_string(),
target_branch: "main".to_string(),
sha: Some("abc123".to_string()),
references: Some(GitLabReferences {
short: format!("!{}", id),
full: format!("group/project!{}", id),
}),
detailed_merge_status: Some("mergeable".to_string()),
merge_status_legacy: None,
created_at: "2024-01-01T00:00:00.000Z".to_string(),
updated_at: updated_at.to_string(),
merged_at: None,
closed_at: None,
author: GitLabAuthor {
id: 1,
username: "test".to_string(),
name: "Test".to_string(),
},
merge_user: None,
merged_by: None,
labels: vec![],
assignees: vec![],
reviewers: vec![],
web_url: "https://example.com".to_string(),
merge_commit_sha: None,
squash_commit_sha: None,
}
}
fn make_mr_with_labels(id: i64, labels: Vec<&str>) -> GitLabMergeRequest {
let mut mr = make_test_mr(id, "2024-06-01T00:00:00.000Z");
mr.labels = labels.into_iter().map(String::from).collect();
mr
}
fn make_mr_with_assignees(id: i64, assignees: Vec<(&str, &str)>) -> GitLabMergeRequest {
let mut mr = make_test_mr(id, "2024-06-01T00:00:00.000Z");
mr.assignees = assignees
.into_iter()
.enumerate()
.map(|(i, (username, name))| GitLabAuthor {
id: (i + 10) as i64,
username: username.to_string(),
name: name.to_string(),
})
.collect();
mr
}
fn make_mr_with_reviewers(id: i64, reviewers: Vec<(&str, &str)>) -> GitLabMergeRequest {
let mut mr = make_test_mr(id, "2024-06-01T00:00:00.000Z");
mr.reviewers = reviewers
.into_iter()
.enumerate()
.map(|(i, (username, name))| GitLabReviewer {
id: (i + 20) as i64,
username: username.to_string(),
name: name.to_string(),
})
.collect();
mr
}
fn count_rows(conn: &Connection, table: &str) -> i64 {
conn.query_row(&format!("SELECT COUNT(*) FROM {table}"), [], |row| {
row.get(0)
})
.unwrap()
}
// ─── Cursor Filter Tests (preserved from inline) ───────────────────────────
#[test]
fn result_default_has_zero_counts() {
let result = IngestMergeRequestsResult::default();
assert_eq!(result.fetched, 0);
assert_eq!(result.upserted, 0);
assert_eq!(result.labels_created, 0);
assert_eq!(result.assignees_linked, 0);
assert_eq!(result.reviewers_linked, 0);
}
#[test]
fn cursor_filter_allows_newer_mrs() {
let cursor = SyncCursor {
updated_at_cursor: Some(1705312800000),
tie_breaker_id: Some(100),
};
let later_ts = 1705399200000;
assert!(passes_cursor_filter_with_ts(101, later_ts, &cursor));
}
#[test]
fn cursor_filter_blocks_older_mrs() {
let cursor = SyncCursor {
updated_at_cursor: Some(1705312800000),
tie_breaker_id: Some(100),
};
let earlier_ts = 1705226400000;
assert!(!passes_cursor_filter_with_ts(99, earlier_ts, &cursor));
}
#[test]
fn cursor_filter_uses_tie_breaker_for_same_timestamp() {
let cursor = SyncCursor {
updated_at_cursor: Some(1705312800000),
tie_breaker_id: Some(100),
};
assert!(passes_cursor_filter_with_ts(101, 1705312800000, &cursor));
assert!(!passes_cursor_filter_with_ts(100, 1705312800000, &cursor));
assert!(!passes_cursor_filter_with_ts(99, 1705312800000, &cursor));
}
#[test]
fn cursor_filter_allows_all_when_no_cursor() {
let cursor = SyncCursor::default();
let old_ts = 1577836800000;
assert!(passes_cursor_filter_with_ts(1, old_ts, &cursor));
}
// ─── parse_timestamp Tests ─────────────────────────────────────────────────
#[test]
fn parse_timestamp_valid_rfc3339() {
let ts = parse_timestamp("2024-06-15T12:30:00.000Z").unwrap();
assert_eq!(ts, 1718454600000);
}
#[test]
fn parse_timestamp_invalid_format_returns_error() {
let result = parse_timestamp("not-a-date");
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("not-a-date"));
}
// ─── Sync Cursor DB Tests ──────────────────────────────────────────────────
#[test]
fn get_sync_cursor_returns_default_when_no_row() {
let conn = setup_test_db();
let cursor = get_sync_cursor(&conn, 1).unwrap();
assert!(cursor.updated_at_cursor.is_none());
assert!(cursor.tie_breaker_id.is_none());
}
#[test]
fn update_sync_cursor_creates_and_reads_back() {
let conn = setup_test_db();
update_sync_cursor(&conn, 1, 1705312800000, 42).unwrap();
let cursor = get_sync_cursor(&conn, 1).unwrap();
assert_eq!(cursor.updated_at_cursor, Some(1705312800000));
assert_eq!(cursor.tie_breaker_id, Some(42));
}
#[test]
fn update_sync_cursor_upserts_on_conflict() {
let conn = setup_test_db();
update_sync_cursor(&conn, 1, 1000, 10).unwrap();
update_sync_cursor(&conn, 1, 2000, 20).unwrap();
let cursor = get_sync_cursor(&conn, 1).unwrap();
assert_eq!(cursor.updated_at_cursor, Some(2000));
assert_eq!(cursor.tie_breaker_id, Some(20));
}
#[test]
fn reset_sync_cursor_clears_cursor() {
let conn = setup_test_db();
update_sync_cursor(&conn, 1, 1000, 10).unwrap();
reset_sync_cursor(&conn, 1).unwrap();
let cursor = get_sync_cursor(&conn, 1).unwrap();
assert!(cursor.updated_at_cursor.is_none());
assert!(cursor.tie_breaker_id.is_none());
}
#[test]
fn sync_cursors_are_project_scoped() {
let conn = setup_test_db();
conn.execute(
"INSERT INTO projects (id, gitlab_project_id, path_with_namespace, web_url)
VALUES (2, 200, 'other/project', 'https://gitlab.example.com/other/project')",
[],
)
.unwrap();
update_sync_cursor(&conn, 1, 1000, 10).unwrap();
update_sync_cursor(&conn, 2, 2000, 20).unwrap();
let c1 = get_sync_cursor(&conn, 1).unwrap();
let c2 = get_sync_cursor(&conn, 2).unwrap();
assert_eq!(c1.updated_at_cursor, Some(1000));
assert_eq!(c2.updated_at_cursor, Some(2000));
}
// ─── process_single_mr Tests ───────────────────────────────────────────────
#[test]
fn process_single_mr_inserts_basic_mr() {
let conn = setup_test_db();
let config = test_config();
let mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
let result = process_single_mr(&conn, &config, 1, &mr).unwrap();
assert_eq!(result.labels_created, 0);
assert_eq!(result.assignees_linked, 0);
assert_eq!(result.reviewers_linked, 0);
let (title, state, author, source_branch): (String, String, String, String) = conn
.query_row(
"SELECT title, state, author_username, source_branch
FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.unwrap();
assert_eq!(title, "MR 1001");
assert_eq!(state, "opened");
assert_eq!(author, "test");
assert_eq!(source_branch, "feature");
}
#[test]
fn process_single_mr_upserts_on_conflict() {
let conn = setup_test_db();
let config = test_config();
let mr_v1 = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
process_single_mr(&conn, &config, 1, &mr_v1).unwrap();
let mut mr_v2 = make_test_mr(1001, "2024-06-16T12:00:00.000Z");
mr_v2.title = "Updated MR title".to_string();
mr_v2.state = "merged".to_string();
process_single_mr(&conn, &config, 1, &mr_v2).unwrap();
assert_eq!(count_rows(&conn, "merge_requests"), 1);
let (title, state): (String, String) = conn
.query_row(
"SELECT title, state FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(title, "Updated MR title");
assert_eq!(state, "merged");
}
#[test]
fn process_single_mr_creates_labels() {
let conn = setup_test_db();
let config = test_config();
let mr = make_mr_with_labels(1001, vec!["bug", "critical"]);
let result = process_single_mr(&conn, &config, 1, &mr).unwrap();
assert_eq!(result.labels_created, 2);
assert_eq!(count_rows(&conn, "labels"), 2);
let label_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM mr_labels ml
JOIN merge_requests m ON ml.merge_request_id = m.id
WHERE m.gitlab_id = 1001",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(label_count, 2);
}
#[test]
fn process_single_mr_label_upsert_idempotent() {
let conn = setup_test_db();
let config = test_config();
let mr1 = make_mr_with_labels(1001, vec!["bug"]);
let created1 = process_single_mr(&conn, &config, 1, &mr1).unwrap();
assert_eq!(created1.labels_created, 1);
// Second MR with same label
let mr2 = make_mr_with_labels(1002, vec!["bug"]);
let created2 = process_single_mr(&conn, &config, 1, &mr2).unwrap();
assert_eq!(created2.labels_created, 0); // label already exists
// Only 1 label row, but 2 junction rows
assert_eq!(count_rows(&conn, "labels"), 1);
assert_eq!(count_rows(&conn, "mr_labels"), 2);
}
#[test]
fn process_single_mr_replaces_labels_on_update() {
let conn = setup_test_db();
let config = test_config();
let mr_v1 = make_mr_with_labels(1001, vec!["bug", "critical"]);
process_single_mr(&conn, &config, 1, &mr_v1).unwrap();
let mut mr_v2 = make_mr_with_labels(1001, vec!["bug", "fixed"]);
mr_v2.updated_at = "2024-06-02T00:00:00.000Z".to_string();
process_single_mr(&conn, &config, 1, &mr_v2).unwrap();
let labels: Vec<String> = {
let mut stmt = conn
.prepare(
"SELECT l.name FROM labels l
JOIN mr_labels ml ON l.id = ml.label_id
JOIN merge_requests m ON ml.merge_request_id = m.id
WHERE m.gitlab_id = 1001
ORDER BY l.name",
)
.unwrap();
stmt.query_map([], |row| row.get(0))
.unwrap()
.collect::<std::result::Result<Vec<_>, _>>()
.unwrap()
};
assert_eq!(labels, vec!["bug", "fixed"]);
}
#[test]
fn process_single_mr_creates_assignees() {
let conn = setup_test_db();
let config = test_config();
let mr = make_mr_with_assignees(1001, vec![("alice", "Alice"), ("bob", "Bob")]);
let result = process_single_mr(&conn, &config, 1, &mr).unwrap();
assert_eq!(result.assignees_linked, 2);
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM mr_assignees ma
JOIN merge_requests m ON ma.merge_request_id = m.id
WHERE m.gitlab_id = 1001",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(count, 2);
}
#[test]
fn process_single_mr_replaces_assignees_on_update() {
let conn = setup_test_db();
let config = test_config();
let mr_v1 = make_mr_with_assignees(1001, vec![("alice", "Alice"), ("bob", "Bob")]);
process_single_mr(&conn, &config, 1, &mr_v1).unwrap();
let mut mr_v2 = make_mr_with_assignees(1001, vec![("alice", "Alice"), ("charlie", "Charlie")]);
mr_v2.updated_at = "2024-06-02T00:00:00.000Z".to_string();
process_single_mr(&conn, &config, 1, &mr_v2).unwrap();
let assignees: Vec<String> = {
let mut stmt = conn
.prepare(
"SELECT ma.username FROM mr_assignees ma
JOIN merge_requests m ON ma.merge_request_id = m.id
WHERE m.gitlab_id = 1001
ORDER BY ma.username",
)
.unwrap();
stmt.query_map([], |row| row.get(0))
.unwrap()
.collect::<std::result::Result<Vec<_>, _>>()
.unwrap()
};
assert_eq!(assignees, vec!["alice", "charlie"]);
}
#[test]
fn process_single_mr_creates_reviewers() {
let conn = setup_test_db();
let config = test_config();
let mr = make_mr_with_reviewers(1001, vec![("reviewer1", "Reviewer One")]);
let result = process_single_mr(&conn, &config, 1, &mr).unwrap();
assert_eq!(result.reviewers_linked, 1);
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM mr_reviewers mr
JOIN merge_requests m ON mr.merge_request_id = m.id
WHERE m.gitlab_id = 1001",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(count, 1);
}
#[test]
fn process_single_mr_replaces_reviewers_on_update() {
let conn = setup_test_db();
let config = test_config();
let mr_v1 = make_mr_with_reviewers(1001, vec![("alice", "Alice"), ("bob", "Bob")]);
process_single_mr(&conn, &config, 1, &mr_v1).unwrap();
let mut mr_v2 = make_mr_with_reviewers(1001, vec![("charlie", "Charlie")]);
mr_v2.updated_at = "2024-06-02T00:00:00.000Z".to_string();
process_single_mr(&conn, &config, 1, &mr_v2).unwrap();
let reviewers: Vec<String> = {
let mut stmt = conn
.prepare(
"SELECT mr.username FROM mr_reviewers mr
JOIN merge_requests m ON mr.merge_request_id = m.id
WHERE m.gitlab_id = 1001
ORDER BY mr.username",
)
.unwrap();
stmt.query_map([], |row| row.get(0))
.unwrap()
.collect::<std::result::Result<Vec<_>, _>>()
.unwrap()
};
assert_eq!(reviewers, vec!["charlie"]);
}
#[test]
fn process_single_mr_marks_dirty() {
let conn = setup_test_db();
let config = test_config();
let mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
process_single_mr(&conn, &config, 1, &mr).unwrap();
let local_id: i64 = conn
.query_row(
"SELECT id FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| row.get(0),
)
.unwrap();
let dirty_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM dirty_sources
WHERE source_type = 'merge_request' AND source_id = ?",
[local_id],
|row| row.get(0),
)
.unwrap();
assert_eq!(dirty_count, 1);
}
#[test]
fn process_single_mr_stores_raw_payload() {
let conn = setup_test_db();
let config = test_config();
let mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
process_single_mr(&conn, &config, 1, &mr).unwrap();
let payload_id: Option<i64> = conn
.query_row(
"SELECT raw_payload_id FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| row.get(0),
)
.unwrap();
assert!(payload_id.is_some());
let payload_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM raw_payloads WHERE id = ?",
[payload_id.unwrap()],
|row| row.get(0),
)
.unwrap();
assert_eq!(payload_count, 1);
}
#[test]
fn process_single_mr_stores_merge_commit_sha() {
let conn = setup_test_db();
let config = test_config();
let mut mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
mr.state = "merged".to_string();
mr.merge_commit_sha = Some("deadbeef1234".to_string());
mr.squash_commit_sha = Some("cafebabe5678".to_string());
process_single_mr(&conn, &config, 1, &mr).unwrap();
let (merge_sha, squash_sha): (Option<String>, Option<String>) = conn
.query_row(
"SELECT merge_commit_sha, squash_commit_sha
FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.unwrap();
assert_eq!(merge_sha.as_deref(), Some("deadbeef1234"));
assert_eq!(squash_sha.as_deref(), Some("cafebabe5678"));
}
// ─── Discussion Sync Queue Tests ───────────────────────────────────────────
#[test]
fn get_mrs_needing_discussion_sync_detects_updated() {
let conn = setup_test_db();
let config = test_config();
let mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
process_single_mr(&conn, &config, 1, &mr).unwrap();
// MR was just upserted, discussions_synced_for_updated_at is NULL
let needing_sync = get_mrs_needing_discussion_sync(&conn, 1).unwrap();
assert_eq!(needing_sync.len(), 1);
assert_eq!(needing_sync[0].iid, 1001);
}
#[test]
fn get_mrs_needing_discussion_sync_skips_already_synced() {
let conn = setup_test_db();
let config = test_config();
let mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
process_single_mr(&conn, &config, 1, &mr).unwrap();
// Simulate discussion sync
let updated_at: i64 = conn
.query_row(
"SELECT updated_at FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| row.get(0),
)
.unwrap();
conn.execute(
"UPDATE merge_requests SET discussions_synced_for_updated_at = ? WHERE gitlab_id = 1001",
[updated_at],
)
.unwrap();
let needing_sync = get_mrs_needing_discussion_sync(&conn, 1).unwrap();
assert!(needing_sync.is_empty());
}
#[test]
fn get_mrs_needing_discussion_sync_is_project_scoped() {
let conn = setup_test_db();
let config = test_config();
conn.execute(
"INSERT INTO projects (id, gitlab_project_id, path_with_namespace, web_url)
VALUES (2, 200, 'other/project', 'https://gitlab.example.com/other/project')",
[],
)
.unwrap();
let mr1 = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
process_single_mr(&conn, &config, 1, &mr1).unwrap();
let mut mr2 = make_test_mr(1002, "2024-06-15T12:00:00.000Z");
mr2.project_id = 200;
process_single_mr(&conn, &config, 2, &mr2).unwrap();
// Only project 1's MR should appear
let needing_sync = get_mrs_needing_discussion_sync(&conn, 1).unwrap();
assert_eq!(needing_sync.len(), 1);
assert_eq!(needing_sync[0].iid, 1001);
}
// ─── Reset / Full Sync Tests ──────────────────────────────────────────────
#[test]
fn reset_discussion_watermarks_clears_all_for_project() {
let conn = setup_test_db();
let config = test_config();
let mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
process_single_mr(&conn, &config, 1, &mr).unwrap();
// Set some watermarks
conn.execute(
"UPDATE merge_requests SET
discussions_synced_for_updated_at = 1000,
resource_events_synced_for_updated_at = 2000,
closes_issues_synced_for_updated_at = 3000,
diffs_synced_for_updated_at = 4000
WHERE gitlab_id = 1001",
[],
)
.unwrap();
reset_discussion_watermarks(&conn, 1).unwrap();
let (disc_wm, events_wm, closes_wm, diffs_wm): (
Option<i64>,
Option<i64>,
Option<i64>,
Option<i64>,
) = conn
.query_row(
"SELECT discussions_synced_for_updated_at,
resource_events_synced_for_updated_at,
closes_issues_synced_for_updated_at,
diffs_synced_for_updated_at
FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.unwrap();
assert!(disc_wm.is_none());
assert!(events_wm.is_none());
assert!(closes_wm.is_none());
assert!(diffs_wm.is_none());
}
#[test]
fn process_single_mr_draft_and_references_stored() {
let conn = setup_test_db();
let config = test_config();
let mut mr = make_test_mr(1001, "2024-06-15T12:00:00.000Z");
mr.draft = true;
mr.references = Some(GitLabReferences {
short: "!1001".to_string(),
full: "group/project!1001".to_string(),
});
mr.detailed_merge_status = Some("checking".to_string());
process_single_mr(&conn, &config, 1, &mr).unwrap();
let (draft, refs_short, refs_full, merge_status): (
bool,
Option<String>,
Option<String>,
Option<String>,
) = conn
.query_row(
"SELECT draft, references_short, references_full, detailed_merge_status
FROM merge_requests WHERE gitlab_id = 1001",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)
.unwrap();
assert!(draft);
assert_eq!(refs_short.as_deref(), Some("!1001"));
assert_eq!(refs_full.as_deref(), Some("group/project!1001"));
assert_eq!(merge_status.as_deref(), Some("checking"));
}

View File

@@ -37,3 +37,38 @@ pub use orchestrator::{
ingest_project_issues, ingest_project_issues_with_progress, ingest_project_merge_requests,
ingest_project_merge_requests_with_progress,
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nonzero_summary_all_zero_returns_nothing() {
let result = nonzero_summary(&[("fetched", 0), ("upserted", 0)]);
assert_eq!(result, "nothing to update");
}
#[test]
fn nonzero_summary_empty_input_returns_nothing() {
let result = nonzero_summary(&[]);
assert_eq!(result, "nothing to update");
}
#[test]
fn nonzero_summary_single_nonzero() {
let result = nonzero_summary(&[("fetched", 5), ("skipped", 0)]);
assert_eq!(result, "5 fetched");
}
#[test]
fn nonzero_summary_multiple_nonzero_joined_with_middot() {
let result = nonzero_summary(&[("fetched", 10), ("upserted", 3), ("skipped", 0)]);
assert_eq!(result, "10 fetched \u{b7} 3 upserted");
}
#[test]
fn nonzero_summary_all_nonzero() {
let result = nonzero_summary(&[("a", 1), ("b", 2), ("c", 3)]);
assert_eq!(result, "1 a \u{b7} 2 b \u{b7} 3 c");
}
}