test(ingestion): add MR + nonzero_summary tests, close bd-1au9
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
|
||||
704
src/ingestion/merge_requests_tests.rs
Normal file
704
src/ingestion/merge_requests_tests.rs
Normal 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"));
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user