382 lines
12 KiB
Rust
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);
|
|
}
|