diff --git a/src/gitlab/mod.rs b/src/gitlab/mod.rs index d46c1d2..82eea67 100644 --- a/src/gitlab/mod.rs +++ b/src/gitlab/mod.rs @@ -10,6 +10,7 @@ pub use transformers::{ transform_discussion, transform_issue, transform_notes, }; pub use types::{ - GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabNote, GitLabNotePosition, GitLabProject, - GitLabUser, GitLabVersion, + GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabLabelEvent, GitLabLabelRef, + GitLabMergeRequestRef, GitLabMilestoneEvent, GitLabMilestoneRef, GitLabNote, + GitLabNotePosition, GitLabProject, GitLabStateEvent, GitLabUser, GitLabVersion, }; diff --git a/src/gitlab/types.rs b/src/gitlab/types.rs index 4d7d7ea..af5c8a3 100644 --- a/src/gitlab/types.rs +++ b/src/gitlab/types.rs @@ -182,6 +182,70 @@ impl GitLabLineRange { } } +// === Resource Event types (Phase B - Gate 1) === + +/// Reference to an MR in state event's source_merge_request field. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GitLabMergeRequestRef { + pub iid: i64, + pub title: Option, + pub web_url: Option, +} + +/// Reference to a label in label event's label field. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GitLabLabelRef { + pub id: i64, + pub name: String, + pub color: Option, + pub description: Option, +} + +/// Reference to a milestone in milestone event's milestone field. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GitLabMilestoneRef { + pub id: i64, + pub iid: i64, + pub title: String, +} + +/// State change event from the Resource State Events API. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GitLabStateEvent { + pub id: i64, + pub user: Option, + pub created_at: String, + pub resource_type: String, + pub resource_id: i64, + pub state: String, + pub source_commit: Option, + pub source_merge_request: Option, +} + +/// Label change event from the Resource Label Events API. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GitLabLabelEvent { + pub id: i64, + pub user: Option, + pub created_at: String, + pub resource_type: String, + pub resource_id: i64, + pub label: GitLabLabelRef, + pub action: String, +} + +/// Milestone change event from the Resource Milestone Events API. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GitLabMilestoneEvent { + pub id: i64, + pub user: Option, + pub created_at: String, + pub resource_type: String, + pub resource_id: i64, + pub milestone: GitLabMilestoneRef, + pub action: String, +} + // === Checkpoint 2: Merge Request types === /// GitLab MR references (short and full reference strings). diff --git a/tests/gitlab_types_tests.rs b/tests/gitlab_types_tests.rs index 8248dad..103e031 100644 --- a/tests/gitlab_types_tests.rs +++ b/tests/gitlab_types_tests.rs @@ -1,8 +1,10 @@ //! Tests for GitLab API response type deserialization. use lore::gitlab::types::{ - GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabMergeRequest, GitLabMilestone, GitLabNote, - GitLabNotePosition, GitLabReferences, GitLabReviewer, + GitLabAuthor, GitLabDiscussion, GitLabIssue, GitLabLabelEvent, GitLabLabelRef, + GitLabMergeRequest, GitLabMergeRequestRef, GitLabMilestone, GitLabMilestoneEvent, + GitLabMilestoneRef, GitLabNote, GitLabNotePosition, GitLabReferences, GitLabReviewer, + GitLabStateEvent, }; #[test] @@ -637,3 +639,209 @@ fn deserializes_diffnote_position_with_line_range() { assert_eq!(range.start_line(), Some(10)); assert_eq!(range.end_line(), Some(15)); } + +// === Resource Event type tests === + +#[test] +fn deserializes_state_event_closed_by_mr() { + let json = r#"{ + "id": 1001, + "user": { + "id": 42, + "username": "developer", + "name": "Dev User" + }, + "created_at": "2024-03-15T10:30:00.000Z", + "resource_type": "Issue", + "resource_id": 555, + "state": "closed", + "source_commit": null, + "source_merge_request": { + "iid": 99, + "title": "Fix the bug", + "web_url": "https://gitlab.example.com/group/project/-/merge_requests/99" + } + }"#; + + let event: GitLabStateEvent = + serde_json::from_str(json).expect("Failed to deserialize state event"); + + assert_eq!(event.id, 1001); + assert!(event.user.is_some()); + assert_eq!(event.user.as_ref().unwrap().username, "developer"); + assert_eq!(event.resource_type, "Issue"); + assert_eq!(event.resource_id, 555); + assert_eq!(event.state, "closed"); + assert!(event.source_commit.is_none()); + assert!(event.source_merge_request.is_some()); + let mr_ref = event.source_merge_request.unwrap(); + assert_eq!(mr_ref.iid, 99); + assert_eq!(mr_ref.title, Some("Fix the bug".to_string())); +} + +#[test] +fn deserializes_state_event_simple_no_user() { + let json = r#"{ + "id": 1002, + "user": null, + "created_at": "2024-03-15T10:30:00.000Z", + "resource_type": "MergeRequest", + "resource_id": 777, + "state": "merged", + "source_commit": "abc123def456", + "source_merge_request": null + }"#; + + let event: GitLabStateEvent = + serde_json::from_str(json).expect("Failed to deserialize state event without user"); + + assert_eq!(event.id, 1002); + assert!(event.user.is_none()); + assert_eq!(event.resource_type, "MergeRequest"); + assert_eq!(event.state, "merged"); + assert_eq!(event.source_commit, Some("abc123def456".to_string())); + assert!(event.source_merge_request.is_none()); +} + +#[test] +fn deserializes_label_event_add() { + let json = r##"{ + "id": 2001, + "user": { + "id": 42, + "username": "developer", + "name": "Dev User" + }, + "created_at": "2024-03-15T10:30:00.000Z", + "resource_type": "Issue", + "resource_id": 555, + "label": { + "id": 100, + "name": "bug", + "color": "#FF0000", + "description": "Bug label" + }, + "action": "add" + }"##; + + let event: GitLabLabelEvent = + serde_json::from_str(json).expect("Failed to deserialize label event"); + + assert_eq!(event.id, 2001); + assert_eq!(event.action, "add"); + assert_eq!(event.label.id, 100); + assert_eq!(event.label.name, "bug"); + assert_eq!(event.label.color, Some("#FF0000".to_string())); + assert_eq!(event.label.description, Some("Bug label".to_string())); +} + +#[test] +fn deserializes_label_event_remove_null_color() { + let json = r#"{ + "id": 2002, + "user": { + "id": 42, + "username": "developer", + "name": "Dev User" + }, + "created_at": "2024-03-15T10:30:00.000Z", + "resource_type": "MergeRequest", + "resource_id": 777, + "label": { + "id": 101, + "name": "needs-review", + "color": null, + "description": null + }, + "action": "remove" + }"#; + + let event: GitLabLabelEvent = + serde_json::from_str(json).expect("Failed to deserialize label remove event"); + + assert_eq!(event.action, "remove"); + assert!(event.label.color.is_none()); + assert!(event.label.description.is_none()); +} + +#[test] +fn deserializes_milestone_event() { + let json = r#"{ + "id": 3001, + "user": { + "id": 42, + "username": "developer", + "name": "Dev User" + }, + "created_at": "2024-03-15T10:30:00.000Z", + "resource_type": "Issue", + "resource_id": 555, + "milestone": { + "id": 200, + "iid": 5, + "title": "v1.0" + }, + "action": "add" + }"#; + + let event: GitLabMilestoneEvent = + serde_json::from_str(json).expect("Failed to deserialize milestone event"); + + assert_eq!(event.id, 3001); + assert_eq!(event.action, "add"); + assert_eq!(event.milestone.id, 200); + assert_eq!(event.milestone.iid, 5); + assert_eq!(event.milestone.title, "v1.0"); +} + +#[test] +fn deserializes_merge_request_ref() { + let json = r#"{ + "iid": 42, + "title": "Feature branch", + "web_url": "https://gitlab.example.com/group/project/-/merge_requests/42" + }"#; + + let mr_ref: GitLabMergeRequestRef = + serde_json::from_str(json).expect("Failed to deserialize MR ref"); + + assert_eq!(mr_ref.iid, 42); + assert_eq!(mr_ref.title, Some("Feature branch".to_string())); + assert_eq!( + mr_ref.web_url, + Some("https://gitlab.example.com/group/project/-/merge_requests/42".to_string()) + ); +} + +#[test] +fn deserializes_label_ref() { + let json = r##"{ + "id": 100, + "name": "bug", + "color": "#FF0000", + "description": "Bug label" + }"##; + + let label_ref: GitLabLabelRef = + serde_json::from_str(json).expect("Failed to deserialize label ref"); + + assert_eq!(label_ref.id, 100); + assert_eq!(label_ref.name, "bug"); + assert_eq!(label_ref.color, Some("#FF0000".to_string())); +} + +#[test] +fn deserializes_milestone_ref() { + let json = r#"{ + "id": 200, + "iid": 5, + "title": "v1.0" + }"#; + + let ms_ref: GitLabMilestoneRef = + serde_json::from_str(json).expect("Failed to deserialize milestone ref"); + + assert_eq!(ms_ref.id, 200); + assert_eq!(ms_ref.iid, 5); + assert_eq!(ms_ref.title, "v1.0"); +}