Files
gitlore/tests/diffnote_position_tests.rs
2026-01-28 15:49:14 -05:00

382 lines
12 KiB
Rust

//! Tests for DiffNote position extraction in note transformer.
use lore::gitlab::transformers::discussion::transform_notes_with_diff_position;
use lore::gitlab::types::{
GitLabAuthor, GitLabDiscussion, GitLabLineRange, GitLabLineRangePoint, GitLabNote,
GitLabNotePosition,
};
fn make_author() -> GitLabAuthor {
GitLabAuthor {
id: 1,
username: "testuser".to_string(),
name: "Test User".to_string(),
}
}
fn make_basic_note(id: i64, created_at: &str) -> GitLabNote {
GitLabNote {
id,
note_type: Some("DiscussionNote".to_string()),
body: format!("Note {}", id),
author: make_author(),
created_at: created_at.to_string(),
updated_at: created_at.to_string(),
system: false,
resolvable: false,
resolved: false,
resolved_by: None,
resolved_at: None,
position: None,
}
}
fn make_diffnote_with_position(
id: i64,
created_at: &str,
position: GitLabNotePosition,
) -> GitLabNote {
GitLabNote {
id,
note_type: Some("DiffNote".to_string()),
body: format!("DiffNote {}", id),
author: make_author(),
created_at: created_at.to_string(),
updated_at: created_at.to_string(),
system: false,
resolvable: true,
resolved: false,
resolved_by: None,
resolved_at: None,
position: Some(position),
}
}
fn make_discussion(notes: Vec<GitLabNote>) -> GitLabDiscussion {
GitLabDiscussion {
id: "abc123".to_string(),
individual_note: false,
notes,
}
}
// === DiffNote Position Field Extraction ===
#[test]
fn extracts_position_paths_from_diffnote() {
let position = GitLabNotePosition {
old_path: Some("src/old.rs".to_string()),
new_path: Some("src/new.rs".to_string()),
old_line: Some(10),
new_line: Some(15),
position_type: Some("text".to_string()),
line_range: None,
base_sha: None,
start_sha: None,
head_sha: None,
};
let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes.len(), 1);
assert_eq!(notes[0].position_old_path, Some("src/old.rs".to_string()));
assert_eq!(notes[0].position_new_path, Some("src/new.rs".to_string()));
assert_eq!(notes[0].position_old_line, Some(10));
assert_eq!(notes[0].position_new_line, Some(15));
}
#[test]
fn extracts_position_type_from_diffnote() {
let position = GitLabNotePosition {
old_path: None,
new_path: Some("image.png".to_string()),
old_line: None,
new_line: None,
position_type: Some("image".to_string()),
line_range: None,
base_sha: None,
start_sha: None,
head_sha: None,
};
let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes[0].position_type, Some("image".to_string()));
}
#[test]
fn extracts_sha_triplet_from_diffnote() {
let position = GitLabNotePosition {
old_path: Some("file.rs".to_string()),
new_path: Some("file.rs".to_string()),
old_line: Some(5),
new_line: Some(5),
position_type: Some("text".to_string()),
line_range: None,
base_sha: Some("abc123base".to_string()),
start_sha: Some("def456start".to_string()),
head_sha: Some("ghi789head".to_string()),
};
let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes[0].position_base_sha, Some("abc123base".to_string()));
assert_eq!(notes[0].position_start_sha, Some("def456start".to_string()));
assert_eq!(notes[0].position_head_sha, Some("ghi789head".to_string()));
}
#[test]
fn extracts_line_range_from_multiline_diffnote() {
let line_range = GitLabLineRange {
start: GitLabLineRangePoint {
line_code: Some("abc123_10_10".to_string()),
line_type: Some("new".to_string()),
old_line: None,
new_line: Some(10),
},
end: GitLabLineRangePoint {
line_code: Some("abc123_15_15".to_string()),
line_type: Some("new".to_string()),
old_line: None,
new_line: Some(15),
},
};
let position = GitLabNotePosition {
old_path: None,
new_path: Some("file.rs".to_string()),
old_line: None,
new_line: Some(10),
position_type: Some("text".to_string()),
line_range: Some(line_range),
base_sha: None,
start_sha: None,
head_sha: None,
};
let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes[0].position_line_range_start, Some(10));
assert_eq!(notes[0].position_line_range_end, Some(15));
}
#[test]
fn line_range_uses_old_line_fallback_when_new_line_missing() {
let line_range = GitLabLineRange {
start: GitLabLineRangePoint {
line_code: None,
line_type: Some("old".to_string()),
old_line: Some(20),
new_line: None, // missing - should fall back to old_line
},
end: GitLabLineRangePoint {
line_code: None,
line_type: Some("old".to_string()),
old_line: Some(25),
new_line: None,
},
};
let position = GitLabNotePosition {
old_path: Some("deleted.rs".to_string()),
new_path: None,
old_line: Some(20),
new_line: None,
position_type: Some("text".to_string()),
line_range: Some(line_range),
base_sha: None,
start_sha: None,
head_sha: None,
};
let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes[0].position_line_range_start, Some(20));
assert_eq!(notes[0].position_line_range_end, Some(25));
}
// === Regular Notes (non-DiffNote) ===
#[test]
fn regular_note_has_none_for_all_position_fields() {
let note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes[0].position_old_path, None);
assert_eq!(notes[0].position_new_path, None);
assert_eq!(notes[0].position_old_line, None);
assert_eq!(notes[0].position_new_line, None);
assert_eq!(notes[0].position_type, None);
assert_eq!(notes[0].position_line_range_start, None);
assert_eq!(notes[0].position_line_range_end, None);
assert_eq!(notes[0].position_base_sha, None);
assert_eq!(notes[0].position_start_sha, None);
assert_eq!(notes[0].position_head_sha, None);
}
// === Strict Timestamp Parsing ===
#[test]
fn returns_error_for_invalid_created_at_timestamp() {
let mut note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
note.created_at = "not-a-timestamp".to_string();
let discussion = make_discussion(vec![note]);
let result = transform_notes_with_diff_position(&discussion, 100);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("not-a-timestamp"));
}
#[test]
fn returns_error_for_invalid_updated_at_timestamp() {
let mut note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
note.updated_at = "garbage".to_string();
let discussion = make_discussion(vec![note]);
let result = transform_notes_with_diff_position(&discussion, 100);
assert!(result.is_err());
}
#[test]
fn returns_error_for_invalid_resolved_at_timestamp() {
let mut note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
note.resolvable = true;
note.resolved = true;
note.resolved_by = Some(make_author());
note.resolved_at = Some("bad-timestamp".to_string());
let discussion = make_discussion(vec![note]);
let result = transform_notes_with_diff_position(&discussion, 100);
assert!(result.is_err());
}
// === Mixed Discussion (DiffNote + Regular Notes) ===
#[test]
fn handles_mixed_diffnote_and_regular_notes() {
let position = GitLabNotePosition {
old_path: None,
new_path: Some("file.rs".to_string()),
old_line: None,
new_line: Some(42),
position_type: Some("text".to_string()),
line_range: None,
base_sha: None,
start_sha: None,
head_sha: None,
};
let diffnote = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let regular_note = make_basic_note(2, "2024-01-16T10:00:00.000Z");
let discussion = make_discussion(vec![diffnote, regular_note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes.len(), 2);
// First note is DiffNote with position
assert_eq!(notes[0].position_new_path, Some("file.rs".to_string()));
assert_eq!(notes[0].position_new_line, Some(42));
// Second note is regular with None position fields
assert_eq!(notes[1].position_new_path, None);
assert_eq!(notes[1].position_new_line, None);
}
// === Position Preservation ===
#[test]
fn preserves_note_position_index() {
let pos1 = GitLabNotePosition {
old_path: None,
new_path: Some("file.rs".to_string()),
old_line: None,
new_line: Some(10),
position_type: Some("text".to_string()),
line_range: None,
base_sha: None,
start_sha: None,
head_sha: None,
};
let pos2 = GitLabNotePosition {
old_path: None,
new_path: Some("file.rs".to_string()),
old_line: None,
new_line: Some(20),
position_type: Some("text".to_string()),
line_range: None,
base_sha: None,
start_sha: None,
head_sha: None,
};
let note1 = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", pos1);
let note2 = make_diffnote_with_position(2, "2024-01-16T10:00:00.000Z", pos2);
let discussion = make_discussion(vec![note1, note2]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes[0].position, 0);
assert_eq!(notes[1].position, 1);
}
// === Edge Cases ===
#[test]
fn handles_diffnote_with_empty_position_fields() {
// DiffNote exists but all position fields are None
let position = GitLabNotePosition {
old_path: None,
new_path: None,
old_line: None,
new_line: None,
position_type: None,
line_range: None,
base_sha: None,
start_sha: None,
head_sha: None,
};
let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
// All position fields should be None, not cause an error
assert_eq!(notes[0].position_old_path, None);
assert_eq!(notes[0].position_new_path, None);
}
#[test]
fn handles_file_position_type() {
let position = GitLabNotePosition {
old_path: None,
new_path: Some("binary.bin".to_string()),
old_line: None,
new_line: None,
position_type: Some("file".to_string()),
line_range: None,
base_sha: None,
start_sha: None,
head_sha: None,
};
let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
let discussion = make_discussion(vec![note]);
let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();
assert_eq!(notes[0].position_type, Some("file".to_string()));
assert_eq!(notes[0].position_new_path, Some("binary.bin".to_string()));
// File-level comments have no line numbers
assert_eq!(notes[0].position_new_line, None);
}