From bd41995cafe5092587d79bfa81de8f88cef3614d Mon Sep 17 00:00:00 2001 From: Geoffrey Sechter Date: Sat, 28 Feb 2026 18:37:01 -0700 Subject: [PATCH 1/3] feat: comment update, resolve, and unresolve mutations Add three GraphQL mutations to complete the comment lifecycle: - commentUpdate: update a comment's body - commentResolve: resolve a comment thread (with optional resolving comment) - commentUnresolve: unresolve a previously resolved comment thread Includes CLI subcommands, usage reference, 9 offline tests, 2 SDK online tests, and 2 CLI online tests. --- .../lineark-sdk/src/generated/client_impl.rs | 39 +++ crates/lineark-sdk/src/generated/mutations.rs | 62 +++++ crates/lineark-sdk/tests/online.rs | 124 +++++++++ crates/lineark/src/commands/comments.rs | 70 ++++- crates/lineark/src/commands/usage.rs | 9 +- crates/lineark/tests/offline.rs | 92 +++++++ crates/lineark/tests/online.rs | 259 +++++++++++++++++- schema/operations.toml | 3 + 8 files changed, 654 insertions(+), 4 deletions(-) diff --git a/crates/lineark-sdk/src/generated/client_impl.rs b/crates/lineark-sdk/src/generated/client_impl.rs index 1a13719..22cce02 100644 --- a/crates/lineark-sdk/src/generated/client_impl.rs +++ b/crates/lineark-sdk/src/generated/client_impl.rs @@ -194,10 +194,49 @@ impl Client { ) -> Result { crate::generated::mutations::comment_create::(self, input).await } + /// Updates a comment. + /// + /// Full type: [`Comment`](super::types::Comment) + pub async fn comment_update< + T: serde::de::DeserializeOwned + + crate::field_selection::GraphQLFields, + >( + &self, + skip_edited_at: Option, + input: CommentUpdateInput, + id: String, + ) -> Result { + crate::generated::mutations::comment_update::(self, skip_edited_at, input, id).await + } /// Deletes a comment. pub async fn comment_delete(&self, id: String) -> Result { crate::generated::mutations::comment_delete(self, id).await } + /// Resolves a comment. + /// + /// Full type: [`Comment`](super::types::Comment) + pub async fn comment_resolve< + T: serde::de::DeserializeOwned + + crate::field_selection::GraphQLFields, + >( + &self, + resolving_comment_id: Option, + id: String, + ) -> Result { + crate::generated::mutations::comment_resolve::(self, resolving_comment_id, id).await + } + /// Unresolves a comment. + /// + /// Full type: [`Comment`](super::types::Comment) + pub async fn comment_unresolve< + T: serde::de::DeserializeOwned + + crate::field_selection::GraphQLFields, + >( + &self, + id: String, + ) -> Result { + crate::generated::mutations::comment_unresolve::(self, id).await + } /// Creates a new project. /// /// Full type: [`Project`](super::types::Project) diff --git a/crates/lineark-sdk/src/generated/mutations.rs b/crates/lineark-sdk/src/generated/mutations.rs index 97c4ec2..b03720c 100644 --- a/crates/lineark-sdk/src/generated/mutations.rs +++ b/crates/lineark-sdk/src/generated/mutations.rs @@ -67,6 +67,28 @@ pub async fn comment_create< .execute_mutation::(&query, variables, "commentCreate", "comment") .await } +/// Updates a comment. +/// +/// Full type: [`Comment`](super::types::Comment) +pub async fn comment_update< + T: serde::de::DeserializeOwned + + crate::field_selection::GraphQLFields, +>( + client: &Client, + skip_edited_at: Option, + input: CommentUpdateInput, + id: String, +) -> Result { + let variables = serde_json::json!( + { "skipEditedAt" : skip_edited_at, "input" : input, "id" : id } + ); + let query = String::from( + "mutation CommentUpdate($skipEditedAt: Boolean, $input: CommentUpdateInput!, $id: String!) { commentUpdate(skipEditedAt: $skipEditedAt, input: $input, id: $id) { success comment { ", + ) + &T::selection() + " } } }"; + client + .execute_mutation::(&query, variables, "commentUpdate", "comment") + .await +} /// Deletes a comment. pub async fn comment_delete(client: &Client, id: String) -> Result { let variables = serde_json::json!({ "id" : id }); @@ -78,6 +100,46 @@ pub async fn comment_delete(client: &Client, id: String) -> Result(&query, variables, "commentDelete") .await } +/// Resolves a comment. +/// +/// Full type: [`Comment`](super::types::Comment) +pub async fn comment_resolve< + T: serde::de::DeserializeOwned + + crate::field_selection::GraphQLFields, +>( + client: &Client, + resolving_comment_id: Option, + id: String, +) -> Result { + let variables = serde_json::json!( + { "resolvingCommentId" : resolving_comment_id, "id" : id } + ); + let query = String::from( + "mutation CommentResolve($resolvingCommentId: String, $id: String!) { commentResolve(resolvingCommentId: $resolvingCommentId, id: $id) { success comment { ", + ) + &T::selection() + " } } }"; + client + .execute_mutation::(&query, variables, "commentResolve", "comment") + .await +} +/// Unresolves a comment. +/// +/// Full type: [`Comment`](super::types::Comment) +pub async fn comment_unresolve< + T: serde::de::DeserializeOwned + + crate::field_selection::GraphQLFields, +>( + client: &Client, + id: String, +) -> Result { + let variables = serde_json::json!({ "id" : id }); + let query = String::from( + "mutation CommentUnresolve($id: String!) { commentUnresolve(id: $id) { success comment { ", + ) + &T::selection() + + " } } }"; + client + .execute_mutation::(&query, variables, "commentUnresolve", "comment") + .await +} /// Creates a new project. /// /// Full type: [`Project`](super::types::Project) diff --git a/crates/lineark-sdk/tests/online.rs b/crates/lineark-sdk/tests/online.rs index e5c4270..9e4c925 100644 --- a/crates/lineark-sdk/tests/online.rs +++ b/crates/lineark-sdk/tests/online.rs @@ -1061,6 +1061,130 @@ mod online { client.team_delete(team_id).await.unwrap(); } + // ── Comment Update ──────────────────────────────────────────────────── + + #[test_with::runtime_ignore_if(no_online_test_token)] + async fn comment_update_changes_body() { + use lineark_sdk::generated::inputs::{ + CommentCreateInput, CommentUpdateInput, IssueCreateInput, + }; + + let client = test_client(); + + // Create an issue to comment on. + let teams = client.teams::().first(1).send().await.unwrap(); + let team_id = teams.nodes[0].id.clone().unwrap(); + + let issue_input = IssueCreateInput { + title: Some("[test] SDK comment_update_changes_body".to_string()), + team_id: Some(team_id), + priority: Some(4), + ..Default::default() + }; + let issue_entity = client.issue_create::(issue_input).await.unwrap(); + let issue_id = issue_entity.id.clone().unwrap(); + let _issue_guard = IssueGuard { + token: test_token(), + id: issue_id.clone(), + }; + + // Create a comment with the original body. + let comment_input = CommentCreateInput { + body: Some("Original body".to_string()), + issue_id: Some(issue_id.clone()), + ..Default::default() + }; + let comment_entity = client + .comment_create::(comment_input) + .await + .unwrap(); + let comment_id = comment_entity.id.clone().unwrap(); + assert_eq!(comment_entity.body.as_deref(), Some("Original body")); + + // Update the comment body. + let update_input = CommentUpdateInput { + body: Some("Updated body".to_string()), + ..Default::default() + }; + let updated = client + .comment_update::(None, update_input, comment_id) + .await + .unwrap(); + assert_eq!(updated.body.as_deref(), Some("Updated body")); + + // Clean up: permanently delete the issue (cascades the comment). + client + .issue_delete::(Some(true), issue_id) + .await + .unwrap(); + } + + #[test_with::runtime_ignore_if(no_online_test_token)] + async fn comment_resolve_and_unresolve() { + use lineark_sdk::generated::inputs::{CommentCreateInput, IssueCreateInput}; + + let client = test_client(); + + // Create an issue to comment on. + let teams = client.teams::().first(1).send().await.unwrap(); + let team_id = teams.nodes[0].id.clone().unwrap(); + + let issue_input = IssueCreateInput { + title: Some("[test] SDK comment_resolve_and_unresolve".to_string()), + team_id: Some(team_id), + priority: Some(4), + ..Default::default() + }; + let issue_entity = client.issue_create::(issue_input).await.unwrap(); + let issue_id = issue_entity.id.clone().unwrap(); + let _issue_guard = IssueGuard { + token: test_token(), + id: issue_id.clone(), + }; + + // Create a comment. + let comment_input = CommentCreateInput { + body: Some("Thread to resolve".to_string()), + issue_id: Some(issue_id.clone()), + ..Default::default() + }; + let comment_entity = client + .comment_create::(comment_input) + .await + .unwrap(); + let comment_id = comment_entity.id.clone().unwrap(); + assert!( + comment_entity.resolved_at.is_none(), + "new comment should not be resolved" + ); + + // Resolve the comment thread. + let resolved = client + .comment_resolve::(None, comment_id.clone()) + .await + .unwrap(); + assert!( + resolved.resolved_at.is_some(), + "comment should have resolvedAt after resolve" + ); + + // Unresolve the comment thread. + let unresolved = client + .comment_unresolve::(comment_id) + .await + .unwrap(); + assert!( + unresolved.resolved_at.is_none(), + "comment should not have resolvedAt after unresolve" + ); + + // Clean up: permanently delete the issue. + client + .issue_delete::(Some(true), issue_id) + .await + .unwrap(); + } + // ── Error handling ────────────────────────────────────────────────────── #[test_with::runtime_ignore_if(no_online_test_token)] diff --git a/crates/lineark/src/commands/comments.rs b/crates/lineark/src/commands/comments.rs index c5c28a5..a4bcb98 100644 --- a/crates/lineark/src/commands/comments.rs +++ b/crates/lineark/src/commands/comments.rs @@ -1,5 +1,5 @@ use clap::Args; -use lineark_sdk::generated::inputs::CommentCreateInput; +use lineark_sdk::generated::inputs::{CommentCreateInput, CommentUpdateInput}; use lineark_sdk::generated::types::Comment; use lineark_sdk::{Client, GraphQLFields}; use serde::{Deserialize, Serialize}; @@ -28,6 +28,17 @@ pub enum CommentsAction { #[arg(long)] body: String, }, + /// Update a comment's body. + /// + /// Examples: + /// lineark comments update COMMENT-UUID --body "Updated text" + Update { + /// Comment UUID. + id: String, + /// New comment body in markdown format. + #[arg(long)] + body: Option, + }, /// Delete a comment. /// /// Examples: @@ -36,6 +47,26 @@ pub enum CommentsAction { /// Comment UUID. id: String, }, + /// Resolve a comment thread. + /// + /// Examples: + /// lineark comments resolve COMMENT-UUID + /// lineark comments resolve COMMENT-UUID --resolving-comment REPLY-UUID + Resolve { + /// Comment UUID (the thread root to resolve). + id: String, + /// Optional UUID of the reply comment that resolves this thread. + #[arg(long)] + resolving_comment: Option, + }, + /// Unresolve a previously resolved comment thread. + /// + /// Examples: + /// lineark comments unresolve COMMENT-UUID + Unresolve { + /// Comment UUID. + id: String, + }, } /// Lean result type for comment mutations. @@ -45,6 +76,7 @@ pub enum CommentsAction { struct CommentRef { id: Option, body: Option, + resolved_at: Option, } pub async fn run(cmd: CommentsCmd, client: &Client, format: Format) -> anyhow::Result<()> { @@ -66,6 +98,23 @@ pub async fn run(cmd: CommentsCmd, client: &Client, format: Format) -> anyhow::R output::print_one(&comment, format); } + CommentsAction::Update { id, body } => { + if body.is_none() { + anyhow::bail!("No update fields provided. Use --body to set the new comment body."); + } + + let input = CommentUpdateInput { + body, + ..Default::default() + }; + + let comment = client + .comment_update::(None, input, id) + .await + .map_err(|e| anyhow::anyhow!("{}", e))?; + + output::print_one(&comment, format); + } CommentsAction::Delete { id } => { client .comment_delete(id) @@ -74,6 +123,25 @@ pub async fn run(cmd: CommentsCmd, client: &Client, format: Format) -> anyhow::R output::print_one(&serde_json::json!({ "success": true }), format); } + CommentsAction::Resolve { + id, + resolving_comment, + } => { + let comment = client + .comment_resolve::(resolving_comment, id) + .await + .map_err(|e| anyhow::anyhow!("{}", e))?; + + output::print_one(&comment, format); + } + CommentsAction::Unresolve { id } => { + let comment = client + .comment_unresolve::(id) + .await + .map_err(|e| anyhow::anyhow!("{}", e))?; + + output::print_one(&comment, format); + } } Ok(()) } diff --git a/crates/lineark/src/commands/usage.rs b/crates/lineark/src/commands/usage.rs index ccb11c8..f53857c 100644 --- a/crates/lineark/src/commands/usage.rs +++ b/crates/lineark/src/commands/usage.rs @@ -75,8 +75,13 @@ COMMANDS: lineark issues unarchive Unarchive a previously archived issue lineark issues delete Delete (trash) an issue [--permanently] Permanently delete instead of trashing - lineark comments create --body TEXT Comment on an issue - lineark comments delete Delete a comment + lineark comments create --body TEXT Comment on an issue + lineark comments update Update a comment + --body TEXT New body in markdown + lineark comments resolve Resolve a comment thread + [--resolving-comment UUID] Reply that resolves thread + lineark comments unresolve Unresolve a comment thread + lineark comments delete Delete a comment lineark relations create Create an issue relation --blocks Source blocks target --blocked-by Source is blocked by target diff --git a/crates/lineark/tests/offline.rs b/crates/lineark/tests/offline.rs index 313fd32..07f2d42 100644 --- a/crates/lineark/tests/offline.rs +++ b/crates/lineark/tests/offline.rs @@ -887,3 +887,95 @@ fn usage_includes_comments_delete() { .success() .stdout(predicate::str::contains("comments delete")); } + +// ── Comments update/resolve/unresolve ────────────────────────────────────── + +#[test] +fn comments_help_shows_update_subcommand() { + lineark() + .args(["comments", "--help"]) + .assert() + .success() + .stdout(predicate::str::contains("update")); +} + +#[test] +fn comments_update_help_shows_flags() { + lineark() + .args(["comments", "update", "--help"]) + .assert() + .success() + .stdout(predicate::str::contains("--body")) + .stdout(predicate::str::contains("")); +} + +#[test] +fn comments_update_no_flags_prints_error() { + lineark() + .args([ + "--api-token", + "fake-token", + "comments", + "update", + "some-uuid", + ]) + .assert() + .failure() + .stderr(predicate::str::contains("No update fields provided")); +} + +#[test] +fn comments_help_shows_resolve_subcommand() { + lineark() + .args(["comments", "--help"]) + .assert() + .success() + .stdout(predicate::str::contains("resolve")); +} + +#[test] +fn comments_help_shows_unresolve_subcommand() { + lineark() + .args(["comments", "--help"]) + .assert() + .success() + .stdout(predicate::str::contains("unresolve")); +} + +#[test] +fn comments_resolve_help_shows_flags() { + lineark() + .args(["comments", "resolve", "--help"]) + .assert() + .success() + .stdout(predicate::str::contains("")) + .stdout(predicate::str::contains("--resolving-comment")); +} + +#[test] +fn comments_unresolve_help_shows_id() { + lineark() + .args(["comments", "unresolve", "--help"]) + .assert() + .success() + .stdout(predicate::str::contains("")); +} + +#[test] +fn usage_includes_comments_update() { + lineark() + .arg("usage") + .assert() + .success() + .stdout(predicate::str::contains("comments update")); +} + +#[test] +fn usage_includes_comments_resolve() { + lineark() + .arg("usage") + .assert() + .success() + .stdout(predicate::str::contains("comments resolve")) + .stdout(predicate::str::contains("comments unresolve")); +} diff --git a/crates/lineark/tests/online.rs b/crates/lineark/tests/online.rs index 9509f76..61f91ed 100644 --- a/crates/lineark/tests/online.rs +++ b/crates/lineark/tests/online.rs @@ -5,7 +5,7 @@ use assert_cmd::Command; use lineark_sdk::generated::inputs::ProjectCreateInput; -use lineark_sdk::generated::types::{Issue, IssueRelation, Project}; +use lineark_sdk::generated::types::{Comment, Issue, IssueRelation, Project, Team}; use lineark_sdk::Client; use predicates::prelude::*; @@ -3398,4 +3398,261 @@ mod online { let result: serde_json::Value = serde_json::from_str(&stdout).unwrap(); assert_eq!(result["success"].as_bool(), Some(true)); } + + // ── Comments update/resolve/unresolve ────────────────────────────────── + + #[test_with::runtime_ignore_if(no_online_test_token)] + fn comments_update_resolve_unresolve_lifecycle() { + let token = api_token(); + let client = Client::from_token(token.clone()).unwrap(); + + // Create an issue via SDK (more reliable than CLI for setup). + let teams = tokio::runtime::Runtime::new() + .unwrap() + .block_on(async { client.teams::().first(1).send().await.unwrap() }); + let team_id = teams.nodes[0].id.clone().unwrap(); + + let issue = tokio::runtime::Runtime::new().unwrap().block_on(async { + client + .issue_create::(lineark_sdk::generated::inputs::IssueCreateInput { + title: Some("[test] CLI comments_lifecycle".to_string()), + team_id: Some(team_id), + priority: Some(4), + ..Default::default() + }) + .await + .unwrap() + }); + let issue_id = issue.id.clone().unwrap(); + let _issue_guard = IssueGuard { + token: token.clone(), + id: issue_id.clone(), + }; + + // Create a comment (retry to allow issue propagation). + let comment_id = retry_with_backoff(8, || { + let output = lineark() + .args([ + "--api-token", + &token, + "--format", + "json", + "comments", + "create", + &issue_id, + "--body", + "Original body", + ]) + .output() + .unwrap(); + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).to_string()); + } + let stdout = String::from_utf8_lossy(&output.stdout); + let comment: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + Ok(comment["id"].as_str().unwrap().to_string()) + }) + .expect("comment create should succeed (after retries)"); + + // Update the comment body. + let output = lineark() + .args([ + "--api-token", + &token, + "--format", + "json", + "comments", + "update", + &comment_id, + "--body", + "Updated body", + ]) + .output() + .unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "comment update should succeed.\nstdout: {stdout}\nstderr: {stderr}" + ); + let updated: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + assert_eq!( + updated["body"].as_str(), + Some("Updated body"), + "body should be updated" + ); + + // Resolve the comment. + let output = lineark() + .args([ + "--api-token", + &token, + "--format", + "json", + "comments", + "resolve", + &comment_id, + ]) + .output() + .unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "comment resolve should succeed.\nstdout: {stdout}\nstderr: {stderr}" + ); + let resolved: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + assert!( + resolved["resolvedAt"].as_str().is_some(), + "resolvedAt should be set after resolve" + ); + + // Unresolve the comment. + let output = lineark() + .args([ + "--api-token", + &token, + "--format", + "json", + "comments", + "unresolve", + &comment_id, + ]) + .output() + .unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "comment unresolve should succeed.\nstdout: {stdout}\nstderr: {stderr}" + ); + let unresolved: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + assert!( + unresolved["resolvedAt"].is_null(), + "resolvedAt should be null after unresolve" + ); + + // Delete the comment. + let output = lineark() + .args([ + "--api-token", + &token, + "--format", + "json", + "comments", + "delete", + &comment_id, + ]) + .output() + .unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "comment delete should succeed.\nstdout: {stdout}\nstderr: {stderr}" + ); + + // Clean up: permanently delete the issue. + delete_issue(&issue_id); + } + + #[test_with::runtime_ignore_if(no_online_test_token)] + fn comments_resolve_with_resolving_comment() { + use lineark_sdk::generated::inputs::{CommentCreateInput, IssueCreateInput}; + + let token = api_token(); + let client = Client::from_token(token.clone()).unwrap(); + + // Create an issue via SDK. + let teams = tokio::runtime::Runtime::new() + .unwrap() + .block_on(async { client.teams::().first(1).send().await.unwrap() }); + let team_id = teams.nodes[0].id.clone().unwrap(); + + let issue = tokio::runtime::Runtime::new().unwrap().block_on(async { + client + .issue_create::(IssueCreateInput { + title: Some("[test] CLI comments_resolve_with_resolving_comment".to_string()), + team_id: Some(team_id), + priority: Some(4), + ..Default::default() + }) + .await + .unwrap() + }); + let issue_id = issue.id.clone().unwrap(); + let _issue_guard = IssueGuard { + token: token.clone(), + id: issue_id.clone(), + }; + + // Create a parent comment via CLI (retry to allow issue propagation). + let parent_id = retry_with_backoff(8, || { + let output = lineark() + .args([ + "--api-token", + &token, + "--format", + "json", + "comments", + "create", + &issue_id, + "--body", + "Parent comment thread", + ]) + .output() + .unwrap(); + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).to_string()); + } + let stdout = String::from_utf8_lossy(&output.stdout); + let parent: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + Ok(parent["id"].as_str().unwrap().to_string()) + }) + .expect("parent comment create should succeed (after retries)"); + + // Create a reply comment via SDK (using parent_id). + let reply = tokio::runtime::Runtime::new().unwrap().block_on(async { + client + .comment_create::(CommentCreateInput { + body: Some("Reply that resolves thread".to_string()), + issue_id: Some(issue_id.clone()), + parent_id: Some(parent_id.clone()), + ..Default::default() + }) + .await + .unwrap() + }); + let reply_id = reply.id.clone().unwrap(); + + // Resolve parent with --resolving-comment. + let output = lineark() + .args([ + "--api-token", + &token, + "--format", + "json", + "comments", + "resolve", + &parent_id, + "--resolving-comment", + &reply_id, + ]) + .output() + .unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "comment resolve with resolving-comment should succeed.\nstdout: {stdout}\nstderr: {stderr}" + ); + let resolved: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + assert!( + resolved["resolvedAt"].as_str().is_some(), + "resolvedAt should be set after resolve with resolving-comment" + ); + + // Clean up: permanently delete the issue. + delete_issue(&issue_id); + } } diff --git a/schema/operations.toml b/schema/operations.toml index bc16bda..a44a9a0 100644 --- a/schema/operations.toml +++ b/schema/operations.toml @@ -27,7 +27,10 @@ projectMilestone = true issueCreate = true issueUpdate = true commentCreate = true +commentUpdate = true commentDelete = true +commentResolve = true +commentUnresolve = true # Phase 3 — Issue lifecycle issueArchive = true From a8a6ddded8f5dc853b9be939e0161fc716d2eb66 Mon Sep 17 00:00:00 2001 From: Cadu Date: Sat, 7 Mar 2026 10:39:57 -0300 Subject: [PATCH 2/3] fix: align usage.rs comment columns to majority column 52 --- crates/lineark/src/commands/usage.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/lineark/src/commands/usage.rs b/crates/lineark/src/commands/usage.rs index 553dad9..aba9d72 100644 --- a/crates/lineark/src/commands/usage.rs +++ b/crates/lineark/src/commands/usage.rs @@ -78,13 +78,13 @@ COMMANDS: lineark issues unarchive Unarchive a previously archived issue lineark issues delete Delete (trash) an issue [--permanently] Permanently delete instead of trashing - lineark comments create --body TEXT Comment on an issue - lineark comments update Update a comment - --body TEXT New body in markdown - lineark comments resolve Resolve a comment thread - [--resolving-comment UUID] Reply that resolves thread - lineark comments unresolve Unresolve a comment thread - lineark comments delete Delete a comment + lineark comments create --body TEXT Comment on an issue + lineark comments update Update a comment + --body TEXT New body in markdown + lineark comments resolve Resolve a comment thread + [--resolving-comment UUID] Reply that resolves thread + lineark comments unresolve Unresolve a comment thread + lineark comments delete Delete a comment lineark relations create Create an issue relation --blocks Source blocks target --blocked-by Source is blocked by target From 16b2b0409fc58d54dc4a4648187f6c8a3861a7cb Mon Sep 17 00:00:00 2001 From: Cadu Date: Sat, 7 Mar 2026 13:00:56 -0300 Subject: [PATCH 3/3] chore: merge origin/main, migrate tests to TeamGuard pattern Brings in TeamGuard infrastructure from #119. Migrates comment and find-branch tests from teams[0] to create_test_team() for full self-containment. The #105 blocker no longer applies. --- crates/lineark-sdk/tests/online.rs | 22 +++++++++++----------- crates/lineark/tests/online.rs | 26 +++++++++++--------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/crates/lineark-sdk/tests/online.rs b/crates/lineark-sdk/tests/online.rs index f38caa3..2cb8674 100644 --- a/crates/lineark-sdk/tests/online.rs +++ b/crates/lineark-sdk/tests/online.rs @@ -1077,13 +1077,14 @@ mod online { }; let client = test_client(); + let (team_id, _team_guard) = create_test_team(&client).await; // Create an issue to comment on. - let teams = client.teams::().first(1).send().await.unwrap(); - let team_id = teams.nodes[0].id.clone().unwrap(); - let issue_input = IssueCreateInput { - title: Some("[test] SDK comment_update_changes_body".to_string()), + title: Some(format!( + "[test] SDK comment_update_changes_body {}", + &uuid::Uuid::new_v4().to_string()[..8] + )), team_id: Some(team_id), priority: Some(4), ..Default::default() @@ -1131,13 +1132,14 @@ mod online { use lineark_sdk::generated::inputs::{CommentCreateInput, IssueCreateInput}; let client = test_client(); + let (team_id, _team_guard) = create_test_team(&client).await; // Create an issue to comment on. - let teams = client.teams::().first(1).send().await.unwrap(); - let team_id = teams.nodes[0].id.clone().unwrap(); - let issue_input = IssueCreateInput { - title: Some("[test] SDK comment_resolve_and_unresolve".to_string()), + title: Some(format!( + "[test] SDK comment_resolve_and_unresolve {}", + &uuid::Uuid::new_v4().to_string()[..8] + )), team_id: Some(team_id), priority: Some(4), ..Default::default() @@ -1199,11 +1201,9 @@ mod online { use lineark_sdk::generated::inputs::IssueCreateInput; let client = test_client(); + let (team_id, _team_guard) = create_test_team(&client).await; // Create an issue so we can look up its branchName. - let teams = client.teams::().first(1).send().await.unwrap(); - let team_id = teams.nodes[0].id.clone().unwrap(); - let uid = &uuid::Uuid::new_v4().to_string()[..8]; let input = IssueCreateInput { title: Some(format!("[test] SDK branch search {uid}")), diff --git a/crates/lineark/tests/online.rs b/crates/lineark/tests/online.rs index 1849bc4..499a9e5 100644 --- a/crates/lineark/tests/online.rs +++ b/crates/lineark/tests/online.rs @@ -3355,17 +3355,16 @@ mod online { fn comments_update_resolve_unresolve_lifecycle() { let token = api_token(); let client = Client::from_token(token.clone()).unwrap(); + let (_team_key, team_id, _team_guard) = create_test_team(); // Create an issue via SDK (more reliable than CLI for setup). - let teams = tokio::runtime::Runtime::new() - .unwrap() - .block_on(async { client.teams::().first(1).send().await.unwrap() }); - let team_id = teams.nodes[0].id.clone().unwrap(); - let issue = tokio::runtime::Runtime::new().unwrap().block_on(async { client .issue_create::(lineark_sdk::generated::inputs::IssueCreateInput { - title: Some("[test] CLI comments_lifecycle".to_string()), + title: Some(format!( + "[test] CLI comments_lifecycle {}", + &uuid::Uuid::new_v4().to_string()[..8] + )), team_id: Some(team_id), priority: Some(4), ..Default::default() @@ -3512,17 +3511,16 @@ mod online { let token = api_token(); let client = Client::from_token(token.clone()).unwrap(); + let (_team_key, team_id, _team_guard) = create_test_team(); // Create an issue via SDK. - let teams = tokio::runtime::Runtime::new() - .unwrap() - .block_on(async { client.teams::().first(1).send().await.unwrap() }); - let team_id = teams.nodes[0].id.clone().unwrap(); - let issue = tokio::runtime::Runtime::new().unwrap().block_on(async { client .issue_create::(IssueCreateInput { - title: Some("[test] CLI comments_resolve_with_resolving_comment".to_string()), + title: Some(format!( + "[test] CLI comments_resolve_with_resolving_comment {}", + &uuid::Uuid::new_v4().to_string()[..8] + )), team_id: Some(team_id), priority: Some(4), ..Default::default() @@ -3614,13 +3612,11 @@ mod online { let token = api_token(); let client = Client::from_token(&token).unwrap(); + let (_team_key, team_id, _team_guard) = create_test_team(); // Create an issue via SDK to get the branch name. let rt = tokio::runtime::Runtime::new().unwrap(); let (issue_id, branch_name) = rt.block_on(async { - let teams = client.teams::().first(1).send().await.unwrap(); - let team_id = teams.nodes[0].id.clone().unwrap(); - let input = IssueCreateInput { title: Some(format!( "[test] CLI find-branch {}",