use lineark_sdk::generated::types::*;
use lineark_sdk::Client;
use lineark_test_utils::*;
fn test_client() -> Client {
Client::from_token(test_token()).expect("failed to create test client")
}
test_with::tokio_runner!(online);
#[test_with::module]
mod online {
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn viewer_returns_authenticated_user() {
let client = test_client();
let user = client.whoami::<User>().await.unwrap();
assert!(user.id.is_some(), "viewer should have an id");
assert!(user.email.is_some(), "viewer should have an email");
assert!(user.active.is_some(), "viewer should have active status");
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn viewer_fields_deserialize_correctly() {
let client = test_client();
let user = client.whoami::<User>().await.unwrap();
assert!(user.id.is_some());
assert!(user.name.is_some());
assert!(user.email.is_some());
let active = user.active.expect("viewer should have active field");
assert!(active, "test user should be active");
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn teams_returns_at_least_one_team() {
let client = test_client();
let _team = create_test_team(&client).await;
let conn = client.teams::<Team>().first(10).send().await.unwrap();
assert!(
!conn.nodes.is_empty(),
"workspace should have at least one team"
);
let team = &conn.nodes[0];
assert!(team.id.is_some());
assert!(team.name.is_some());
assert!(team.key.is_some());
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn team_by_id() {
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let team = client.team::<Team>(team_id.clone()).await.unwrap();
assert_eq!(team.id, Some(team_id));
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn team_fields_deserialize_correctly() {
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let team = client.team::<Team>(team_id).await.unwrap();
assert!(team.id.is_some());
assert!(team.key.is_some());
assert!(team.name.is_some());
let key = team.key.as_ref().unwrap();
assert!(!key.is_empty());
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn users_returns_at_least_one_user() {
let client = test_client();
let conn = client.users::<User>().last(10).send().await.unwrap();
assert!(
!conn.nodes.is_empty(),
"workspace should have at least one user"
);
let user = &conn.nodes[0];
assert!(user.id.is_some());
assert!(user.name.is_some());
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn projects_returns_connection() {
let client = test_client();
let conn = client.projects::<Project>().first(10).send().await.unwrap();
let _ = conn.page_info;
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issues_returns_connection() {
let client = test_client();
let conn = client.issues::<Issue>().first(5).send().await.unwrap();
for issue in &conn.nodes {
assert!(issue.id.is_some());
}
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_labels_returns_connection() {
let client = test_client();
let conn = client
.issue_labels::<IssueLabel>()
.first(10)
.send()
.await
.unwrap();
for label in &conn.nodes {
assert!(label.id.is_some());
assert!(label.name.is_some());
}
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_label_create_update_and_delete() {
use lineark_sdk::generated::inputs::{IssueLabelCreateInput, IssueLabelUpdateInput};
let client = test_client();
let unique = format!(
"[test] sdk-label {}",
&uuid::Uuid::new_v4().to_string()[..8]
);
let input = IssueLabelCreateInput {
name: unique.clone(),
color: Some("#eb5757".to_string()).into(),
..Default::default()
};
let label = retry_create(|| {
let input = input.clone();
async { client.issue_label_create::<IssueLabel>(None, input).await }
})
.await;
let label_id = label.id.clone().unwrap();
let _label_guard = LabelGuard {
token: test_token(),
id: label_id.clone(),
};
assert!(!label_id.is_empty());
assert_eq!(label.name, Some(unique));
assert_eq!(label.color, Some("#eb5757".to_string()));
let update_input = IssueLabelUpdateInput {
color: Some("#4ea7fc".to_string()).into(),
..Default::default()
};
let updated = client
.issue_label_update::<IssueLabel>(None, update_input, label_id.clone())
.await
.unwrap();
assert!(updated.id.is_some());
assert_eq!(updated.color, Some("#4ea7fc".to_string()));
client.issue_label_delete(label_id).await.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn cycles_returns_connection() {
let client = test_client();
let conn = client.cycles::<Cycle>().first(10).send().await.unwrap();
for cycle in &conn.nodes {
assert!(cycle.id.is_some());
}
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn workflow_states_returns_connection() {
let client = test_client();
let conn = client
.workflow_states::<WorkflowState>()
.first(50)
.send()
.await
.unwrap();
assert!(
!conn.nodes.is_empty(),
"workspace should have workflow states"
);
let state = &conn.nodes[0];
assert!(state.id.is_some());
assert!(state.name.is_some());
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn search_issues_returns_connection() {
let client = test_client();
let conn = client
.search_issues::<IssueSearchResult>("test")
.first(5)
.send()
.await
.unwrap();
for issue in &conn.nodes {
assert!(issue.id.is_some());
}
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn first_limits_result_count() {
let client = test_client();
let all = client
.workflow_states::<WorkflowState>()
.first(50)
.send()
.await
.unwrap();
assert!(
all.nodes.len() >= 2,
"need at least 2 workflow states to test first(), got {}",
all.nodes.len()
);
let limited = client
.workflow_states::<WorkflowState>()
.first(1)
.send()
.await
.unwrap();
assert_eq!(
limited.nodes.len(),
1,
"first(1) should return exactly 1 item"
);
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn last_returns_different_item_than_first() {
let client = test_client();
let all = client
.workflow_states::<WorkflowState>()
.first(50)
.send()
.await
.unwrap();
if all.nodes.len() < 2 {
return;
}
let from_first = client
.workflow_states::<WorkflowState>()
.first(1)
.send()
.await
.unwrap();
let from_last = client
.workflow_states::<WorkflowState>()
.last(1)
.send()
.await
.unwrap();
assert_eq!(from_first.nodes.len(), 1);
assert_eq!(from_last.nodes.len(), 1);
assert_ne!(
from_first.nodes[0].id, from_last.nodes[0].id,
"first(1) and last(1) should return different workflow states"
);
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn after_cursor_paginates_to_next_page() {
let client = test_client();
let all = client
.workflow_states::<WorkflowState>()
.first(50)
.send()
.await
.unwrap();
if all.nodes.len() < 2 {
return;
}
let page1 = client
.workflow_states::<WorkflowState>()
.first(1)
.send()
.await
.unwrap();
assert_eq!(page1.nodes.len(), 1);
let cursor = page1
.page_info
.end_cursor
.as_ref()
.expect("first page should have endCursor");
let page2 = client
.workflow_states::<WorkflowState>()
.first(1)
.after(cursor)
.send()
.await
.unwrap();
assert_eq!(page2.nodes.len(), 1);
assert_ne!(
page1.nodes[0].id, page2.nodes[0].id,
"after(cursor) should return a different item than page 1"
);
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn include_archived_does_not_error() {
let client = test_client();
let _ = client
.teams::<Team>()
.first(1)
.include_archived(true)
.send()
.await
.unwrap();
let _ = client
.teams::<Team>()
.first(1)
.include_archived(false)
.send()
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn search_issues_term_filters_results() {
use lineark_sdk::generated::inputs::IssueCreateInput;
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let unique = format!("[builder-test-{}]", uuid::Uuid::new_v4());
let input = IssueCreateInput {
title: Some(unique.clone()).into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let entity = retry_create(|| {
let input = input.clone();
async { client.issue_create::<Issue>(input).await }
})
.await;
let issue_id = entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
let search_unique = unique.clone();
let result = retry_search(
|| {
client
.search_issues::<IssueSearchResult>(&search_unique)
.first(5)
.send()
},
|conn| {
conn.nodes.iter().any(|n| {
n.title
.as_deref()
.is_some_and(|t| t.contains(&search_unique))
})
},
)
.await;
assert!(
result.is_some(),
"search_issues(term) should find the created issue"
);
let not_found = client
.search_issues::<IssueSearchResult>("xyzzy_nonexistent_99999")
.first(5)
.send()
.await
.expect("nonsense search should not be rate-limited");
let false_match = not_found
.nodes
.iter()
.any(|n| n.title.as_deref().is_some_and(|t| t.contains(&unique)));
assert!(
!false_match,
"search with different term should not find our issue"
);
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn search_issues_team_id_filters_by_team() {
use lineark_sdk::generated::inputs::IssueCreateInput;
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let unique = format!("[team-filter-{}]", uuid::Uuid::new_v4());
let input = IssueCreateInput {
title: Some(unique.clone()).into(),
team_id: team_id.clone(),
priority: Some(4).into(),
..Default::default()
};
let entity = retry_create(|| {
let input = input.clone();
async { client.issue_create::<Issue>(input).await }
})
.await;
let issue_id = entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
let search_unique = unique.clone();
let search_team_id = team_id.clone();
let result = retry_search(
|| {
client
.search_issues::<IssueSearchResult>(&search_unique)
.first(5)
.team_id(&search_team_id)
.send()
},
|conn| {
conn.nodes.iter().any(|n| {
n.title
.as_deref()
.is_some_and(|t| t.contains(&search_unique))
})
},
)
.await;
assert!(
result.is_some(),
"search with correct team_id should find the issue"
);
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let with_wrong_team = client
.search_issues::<IssueSearchResult>(&unique)
.first(5)
.team_id("00000000-0000-0000-0000-000000000000")
.send()
.await
.expect("wrong-team search should not be rate-limited");
let false_match = with_wrong_team
.nodes
.iter()
.any(|n| n.title.as_deref().is_some_and(|t| t.contains(&unique)));
assert!(
!false_match,
"search with wrong team_id should not find the issue"
);
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn users_include_disabled_accepted() {
let client = test_client();
let _ = client
.users::<User>()
.include_disabled(true)
.first(5)
.send()
.await
.unwrap();
let _ = client
.users::<User>()
.include_disabled(false)
.first(5)
.send()
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn no_params_returns_defaults() {
let client = test_client();
let conn = client.teams::<Team>().send().await.unwrap();
assert!(
!conn.nodes.is_empty(),
"teams() with no params should return results"
);
let conn = client.issues::<Issue>().send().await.unwrap();
for issue in &conn.nodes {
assert!(issue.id.is_some());
}
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_create_and_delete() {
use lineark_sdk::generated::inputs::IssueCreateInput;
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let title = format!(
"[test] SDK issue_create_and_delete {}",
&uuid::Uuid::new_v4().to_string()[..8]
);
let input = IssueCreateInput {
title: Some(title).into(),
team_id,
description: Some("Automated test — will be deleted immediately.".to_string()).into(),
priority: Some(4).into(), ..Default::default()
};
let entity = retry_create(|| {
let input = input.clone();
async { client.issue_create::<Issue>(input).await }
})
.await;
let issue_id = entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
assert!(!issue_id.is_empty());
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_update() {
use lineark_sdk::generated::inputs::{IssueCreateInput, IssueUpdateInput};
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let suffix = &uuid::Uuid::new_v4().to_string()[..8];
let input = IssueCreateInput {
title: Some(format!("[test] SDK issue_update {suffix}")).into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let entity = retry_create(|| {
let input = input.clone();
async { client.issue_create::<Issue>(input).await }
})
.await;
let issue_id = entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
let update_input = IssueUpdateInput {
title: Some(format!("[test] SDK issue_update — updated {suffix}")).into(),
priority: Some(3).into(), ..Default::default()
};
let updated_entity = client
.issue_update::<Issue>(update_input, issue_id.clone())
.await
.unwrap();
assert!(updated_entity.id.is_some());
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_archive_and_unarchive() {
use lineark_sdk::generated::inputs::IssueCreateInput;
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let input = IssueCreateInput {
title: Some(format!(
"[test] SDK issue_archive_and_unarchive {}",
&uuid::Uuid::new_v4().to_string()[..8]
))
.into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let entity = retry_create(|| {
let input = input.clone();
async { client.issue_create::<Issue>(input).await }
})
.await;
let issue_id = entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
client
.issue_archive::<Issue>(None, issue_id.clone())
.await
.unwrap();
client
.issue_unarchive::<Issue>(issue_id.clone())
.await
.unwrap();
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn comment_create() {
use lineark_sdk::generated::inputs::{CommentCreateInput, IssueCreateInput};
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let issue_input = IssueCreateInput {
title: Some(format!(
"[test] SDK comment_create {}",
&uuid::Uuid::new_v4().to_string()[..8]
))
.into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let issue_entity = retry_create(|| {
let issue_input = issue_input.clone();
async { client.issue_create::<Issue>(issue_input).await }
})
.await;
let issue_id = issue_entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
let comment_input = CommentCreateInput {
body: Some("Automated test comment from lineark SDK.".to_string()).into(),
issue_id: Some(issue_id.clone()).into(),
..Default::default()
};
let comment_entity = retry_create(|| {
let comment_input = comment_input.clone();
async { client.comment_create::<Comment>(comment_input).await }
})
.await;
assert!(comment_entity.id.is_some());
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn documents_returns_connection() {
let client = test_client();
let conn = client
.documents::<Document>()
.first(10)
.send()
.await
.unwrap();
let _ = conn.page_info;
for doc in &conn.nodes {
assert!(doc.id.is_some());
}
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn document_create_update_and_delete() {
use lineark_sdk::generated::inputs::{DocumentCreateInput, DocumentUpdateInput};
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let suffix = &uuid::Uuid::new_v4().to_string()[..8];
let title = format!("[test] SDK document_create_update_and_delete {suffix}");
let input = DocumentCreateInput {
title: title.clone(),
content: Some("Automated test document content.".to_string()).into(),
team_id: Some(team_id).into(),
..Default::default()
};
let doc_entity = retry_create(|| {
let input = input.clone();
async { client.document_create::<Document>(input).await }
})
.await;
let doc_id = doc_entity.id.clone().unwrap();
let _doc_guard = DocumentGuard {
token: test_token(),
id: doc_id.clone(),
};
assert!(!doc_id.is_empty());
let fetched = client.document::<Document>(doc_id.clone()).await.unwrap();
assert_eq!(fetched.id, Some(doc_id.clone()));
assert_eq!(fetched.title, Some(title));
let update_input = DocumentUpdateInput {
title: Some(format!("[test] SDK document — updated {suffix}")).into(),
content: Some("Updated content.".to_string()).into(),
..Default::default()
};
client
.document_update::<Document>(update_input, doc_id.clone())
.await
.unwrap();
client.document_delete::<Document>(doc_id).await.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_relations_returns_connection() {
let client = test_client();
let conn = client
.issue_relations::<IssueRelation>()
.first(10)
.send()
.await
.unwrap();
let _ = conn.page_info;
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_relation_create_between_two_issues() {
use lineark_sdk::generated::enums::IssueRelationType;
use lineark_sdk::generated::inputs::{IssueCreateInput, IssueRelationCreateInput};
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let suffix = &uuid::Uuid::new_v4().to_string()[..8];
let input_a = IssueCreateInput {
title: Some(format!("[test] relation issue A {suffix}")).into(),
team_id: team_id.clone(),
priority: Some(4).into(),
..Default::default()
};
let entity_a = retry_create(|| {
let input_a = input_a.clone();
async { client.issue_create::<Issue>(input_a).await }
})
.await;
let issue_a_id = entity_a.id.clone().unwrap();
let _issue_a_guard = IssueGuard {
token: test_token(),
id: issue_a_id.clone(),
};
let input_b = IssueCreateInput {
title: Some(format!("[test] relation issue B {suffix}")).into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let entity_b = retry_create(|| {
let input_b = input_b.clone();
async { client.issue_create::<Issue>(input_b).await }
})
.await;
let issue_b_id = entity_b.id.clone().unwrap();
let _issue_b_guard = IssueGuard {
token: test_token(),
id: issue_b_id.clone(),
};
let relation_input = IssueRelationCreateInput {
id: lineark_sdk::MaybeUndefined::Undefined,
issue_id: issue_a_id.clone(),
related_issue_id: issue_b_id.clone(),
r#type: IssueRelationType::Blocks,
};
let relation_entity = retry_create(|| {
let relation_input = relation_input.clone();
async {
client
.issue_relation_create::<IssueRelation>(None, relation_input)
.await
}
})
.await;
assert!(relation_entity.id.is_some(), "relation should have an id");
let relation_id = relation_entity.id.clone().unwrap();
let fetched = client
.issue_relation::<IssueRelation>(relation_id)
.await
.unwrap();
assert!(fetched.id.is_some());
client
.issue_delete::<Issue>(Some(true), issue_a_id)
.await
.unwrap();
client
.issue_delete::<Issue>(Some(true), issue_b_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn file_upload_returns_signed_url() {
let client = test_client();
let entity = client
.file_upload(
None,
None,
100,
"text/plain".to_string(),
"test.txt".to_string(),
)
.await
.unwrap();
let upload_file = entity.get("uploadFile").expect("should have uploadFile");
assert!(
upload_file.get("uploadUrl").is_some(),
"should have uploadUrl"
);
assert!(
upload_file.get("assetUrl").is_some(),
"should have assetUrl"
);
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn upload_file_end_to_end() {
let client = test_client();
let content = b"lineark SDK test upload content".to_vec();
let result = client
.upload_file("test-upload.txt", "text/plain", content, false)
.await
.unwrap();
assert!(
!result.asset_url.is_empty(),
"asset_url should be non-empty"
);
assert!(
result.asset_url.starts_with("https://"),
"asset_url should be an HTTPS URL, got: {}",
result.asset_url
);
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn upload_then_download_round_trip() {
let client = test_client();
let content = b"SDK round-trip test content 12345".to_vec();
let upload_result = client
.upload_file("round-trip.txt", "text/plain", content.clone(), false)
.await
.unwrap();
assert!(!upload_result.asset_url.is_empty());
let download_result = client.download_url(&upload_result.asset_url).await.unwrap();
assert_eq!(
download_result.bytes, content,
"downloaded bytes should match uploaded content"
);
assert!(
download_result
.content_type
.as_deref()
.is_some_and(|ct| ct.contains("text/plain")),
"content type should be text/plain, got: {:?}",
download_result.content_type
);
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_batch_update_changes_priority() {
use lineark_sdk::generated::inputs::{IssueCreateInput, IssueUpdateInput};
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let input_a = IssueCreateInput {
title: Some(format!(
"[test] SDK batch_update A {}",
&uuid::Uuid::new_v4().to_string()[..8]
))
.into(),
team_id: team_id.clone(),
priority: Some(4).into(),
..Default::default()
};
let entity_a = retry_create(|| {
let input_a = input_a.clone();
async { client.issue_create::<Issue>(input_a).await }
})
.await;
let id_a = entity_a.id.clone().unwrap();
let _guard_a = IssueGuard {
token: test_token(),
id: id_a.clone(),
};
let input_b = IssueCreateInput {
title: Some(format!(
"[test] SDK batch_update B {}",
&uuid::Uuid::new_v4().to_string()[..8]
))
.into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let entity_b = retry_create(|| {
let input_b = input_b.clone();
async { client.issue_create::<Issue>(input_b).await }
})
.await;
let id_b = entity_b.id.clone().unwrap();
let _guard_b = IssueGuard {
token: test_token(),
id: id_b.clone(),
};
let update_input = IssueUpdateInput {
priority: Some(2).into(),
..Default::default()
};
let result = client
.issue_batch_update::<Issue>(update_input, vec![id_a, id_b])
.await
.unwrap();
assert_eq!(result.len(), 2, "batch update should return 2 issues");
for issue in &result {
assert!(issue.id.is_some());
}
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn team_create_update_and_delete() {
use lineark_sdk::generated::inputs::{TeamCreateInput, TeamUpdateInput};
let client = test_client();
let unique = format!("[test] sdk-team {}", &uuid::Uuid::new_v4().to_string()[..8]);
let input = TeamCreateInput {
name: unique.clone(),
..Default::default()
};
let team = retry_create(|| {
let input = input.clone();
async { client.team_create::<Team>(None, input).await }
})
.await;
let team_id = team.id.clone().unwrap();
let _team_guard = TeamGuard {
token: test_token(),
id: team_id.clone(),
};
assert!(!team_id.is_empty());
assert_eq!(team.name, Some(unique));
let update_input = TeamUpdateInput {
description: Some("Updated by SDK test.".to_string()).into(),
..Default::default()
};
let updated = client
.team_update::<Team>(None, update_input, team_id.clone())
.await
.unwrap();
assert!(updated.id.is_some());
let fetched = client.team::<Team>(team_id.clone()).await.unwrap();
assert_eq!(
fetched.description,
Some("Updated by SDK test.".to_string())
);
client.team_delete(team_id).await.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn team_membership_create_and_delete() {
use lineark_sdk::generated::inputs::{TeamCreateInput, TeamMembershipCreateInput};
let client = test_client();
let unique = format!(
"[test] sdk-member {}",
&uuid::Uuid::new_v4().to_string()[..8]
);
let input = TeamCreateInput {
name: unique,
..Default::default()
};
let team = retry_create(|| {
let input = input.clone();
async { client.team_create::<Team>(None, input).await }
})
.await;
let team_id = team.id.clone().unwrap();
let _team_guard = TeamGuard {
token: test_token(),
id: team_id.clone(),
};
let viewer = client.whoami::<User>().await.unwrap();
let my_id = viewer.id.clone().unwrap();
let all_users = client.users::<User>().last(250).send().await.unwrap();
let other_user = all_users
.nodes
.iter()
.find(|u| u.id.as_deref() != Some(&my_id))
.expect("workspace must have at least two users to run this test");
let other_user_id = other_user.id.clone().unwrap();
let membership_input = TeamMembershipCreateInput {
team_id: team_id.clone(),
user_id: other_user_id,
..Default::default()
};
let membership = retry_create(|| {
let membership_input = membership_input.clone();
async {
client
.team_membership_create::<TeamMembership>(membership_input)
.await
}
})
.await;
let membership_id = membership.id.clone().unwrap();
assert!(!membership_id.is_empty());
client
.team_membership_delete(None, membership_id)
.await
.unwrap();
client.team_delete(team_id).await.unwrap();
}
#[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();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let issue_input = IssueCreateInput {
title: Some(format!(
"[test] SDK comment_update_changes_body {}",
&uuid::Uuid::new_v4().to_string()[..8]
))
.into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let issue_entity = retry_create(|| {
let issue_input = issue_input.clone();
async { client.issue_create::<Issue>(issue_input).await }
})
.await;
let issue_id = issue_entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
let comment_input = CommentCreateInput {
body: Some("Original body".to_string()).into(),
issue_id: Some(issue_id.clone()).into(),
..Default::default()
};
let comment_entity = retry_create(|| {
let comment_input = comment_input.clone();
async { client.comment_create::<Comment>(comment_input).await }
})
.await;
let comment_id = comment_entity.id.clone().unwrap();
assert_eq!(comment_entity.body.as_deref(), Some("Original body"));
let update_input = CommentUpdateInput {
body: Some("Updated body".to_string()).into(),
..Default::default()
};
let updated = client
.comment_update::<Comment>(None, update_input, comment_id)
.await
.unwrap();
assert_eq!(updated.body.as_deref(), Some("Updated body"));
client
.issue_delete::<Issue>(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();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let issue_input = IssueCreateInput {
title: Some(format!(
"[test] SDK comment_resolve_and_unresolve {}",
&uuid::Uuid::new_v4().to_string()[..8]
))
.into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let issue_entity = retry_create(|| {
let issue_input = issue_input.clone();
async { client.issue_create::<Issue>(issue_input).await }
})
.await;
let issue_id = issue_entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
let comment_input = CommentCreateInput {
body: Some("Thread to resolve".to_string()).into(),
issue_id: Some(issue_id.clone()).into(),
..Default::default()
};
let comment_entity = retry_create(|| {
let comment_input = comment_input.clone();
async { client.comment_create::<Comment>(comment_input).await }
})
.await;
let comment_id = comment_entity.id.clone().unwrap();
assert!(
comment_entity.resolved_at.is_none(),
"new comment should not be resolved"
);
let resolved = client
.comment_resolve::<Comment>(None, comment_id.clone())
.await
.unwrap();
assert!(
resolved.resolved_at.is_some(),
"comment should have resolvedAt after resolve"
);
let unresolved = client
.comment_unresolve::<Comment>(comment_id)
.await
.unwrap();
assert!(
unresolved.resolved_at.is_none(),
"comment should not have resolvedAt after unresolve"
);
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_vcs_branch_search_found() {
use lineark_sdk::generated::inputs::IssueCreateInput;
let client = test_client();
let team = create_test_team(&client).await;
let team_id = team.id.clone();
let uid = &uuid::Uuid::new_v4().to_string()[..8];
let input = IssueCreateInput {
title: Some(format!("[test] SDK branch search {uid}")).into(),
team_id,
priority: Some(4).into(),
..Default::default()
};
let entity = retry_create(|| {
let input = input.clone();
async { client.issue_create::<Issue>(input).await }
})
.await;
let issue_id = entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};
let branch_name = entity
.branch_name
.clone()
.expect("newly created issue should have a branchName");
let result = client
.issue_vcs_branch_search::<Issue>(branch_name)
.await
.unwrap();
assert!(result.is_some(), "should find issue by branch name");
let found = result.unwrap();
assert_eq!(found.id, Some(issue_id));
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_vcs_branch_search_not_found() {
let client = test_client();
let result = client
.issue_vcs_branch_search::<Issue>("nonexistent-branch-xyz-999".to_string())
.await
.unwrap();
assert!(
result.is_none(),
"should return None for nonexistent branch"
);
}
#[test_with::runtime_ignore_if(no_online_test_token)]
async fn invalid_token_returns_auth_error() {
let client = Client::from_token("lin_api_invalid_token_12345").unwrap();
let result = client.whoami::<User>().await;
assert!(result.is_err(), "invalid token should produce an error");
}
}