Files
cli/sunbeam-sdk/tests/test_gitea.rs
Sienna Meridian Satterwhite f06a167496 feat: BuildKit client + integration test suite (651 tests)
BuildKitClient CLI wrapper for buildctl.
Docker compose stack (9 services) for integration testing.
Comprehensive test suite: wiremock tests for Matrix/La Suite/S3/client,
integration tests for Kratos/Hydra/Gitea/OpenSearch/Prometheus/Loki/
Grafana/LiveKit.

Bump: sunbeam-sdk v0.12.0
2026-03-21 20:35:59 +00:00

1035 lines
30 KiB
Rust

#![cfg(feature = "integration")]
mod helpers;
use helpers::*;
use sunbeam_sdk::client::{AuthMethod, ServiceClient};
use sunbeam_sdk::gitea::GiteaClient;
use sunbeam_sdk::gitea::types::*;
use base64::Engine as _;
const GITEA_API: &str = "http://localhost:3000/api/v1";
fn make_client(pat: &str) -> GiteaClient {
GiteaClient::from_parts(GITEA_API.to_string(), AuthMethod::Token(pat.to_string()))
}
/// Delete an org via raw API (no SDK method exists).
async fn delete_org(pat: &str, org: &str) {
let _ = reqwest::Client::new()
.delete(format!("{GITEA_API}/orgs/{org}"))
.header("Authorization", format!("token {pat}"))
.send()
.await;
}
/// Create a file in a repo via Gitea API (for PR tests that need a commit on a branch).
async fn create_file_on_branch(pat: &str, owner: &str, repo: &str, branch: &str, path: &str) {
let body = serde_json::json!({
"content": base64::engine::general_purpose::STANDARD.encode(format!("# {path}\n").as_bytes()),
"message": format!("add {path}"),
"branch": branch,
});
let resp = reqwest::Client::new()
.post(format!("{GITEA_API}/repos/{owner}/{repo}/contents/{path}"))
.header("Authorization", format!("token {pat}"))
.json(&body)
.send()
.await
.unwrap();
assert!(
resp.status().is_success(),
"create file failed: {}",
resp.text().await.unwrap_or_default()
);
}
// ---------------------------------------------------------------------------
// 1. User operations
// ---------------------------------------------------------------------------
#[tokio::test]
async fn user_operations() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
// get_authenticated_user
let me = client.get_authenticated_user().await.unwrap();
assert_eq!(me.login, GITEA_ADMIN_USER);
assert!(!me.email.is_empty());
// get_user
let user = client.get_user(GITEA_ADMIN_USER).await.unwrap();
assert_eq!(user.login, GITEA_ADMIN_USER);
assert_eq!(user.id, me.id);
// search_users
let result = client.search_users("test", Some(10)).await.unwrap();
assert!(!result.data.is_empty());
assert!(result.data.iter().any(|u| u.login == GITEA_ADMIN_USER));
}
// ---------------------------------------------------------------------------
// 2. Repo CRUD
// ---------------------------------------------------------------------------
#[tokio::test]
async fn repo_crud() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("crud-repo");
// create_user_repo
let repo = client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
description: Some("integration test".into()),
private: Some(false),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
assert_eq!(repo.name, repo_name);
assert!(!repo.empty);
// get_repo
let fetched = client.get_repo(GITEA_ADMIN_USER, &repo_name).await.unwrap();
assert_eq!(fetched.id, repo.id);
assert_eq!(fetched.description, "integration test");
// edit_repo
let edited = client
.edit_repo(
GITEA_ADMIN_USER,
&repo_name,
&EditRepoBody {
description: Some("updated description".into()),
has_wiki: Some(false),
..Default::default()
},
)
.await
.unwrap();
assert_eq!(edited.description, "updated description");
// search_repos
let search = client.search_repos(&repo_name, Some(5)).await.unwrap();
assert!(search.data.iter().any(|r| r.name == repo_name));
// delete_repo
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
// Confirm deletion
let err = client.get_repo(GITEA_ADMIN_USER, &repo_name).await;
assert!(err.is_err());
}
// ---------------------------------------------------------------------------
// 3. Repo fork and transfer
// ---------------------------------------------------------------------------
#[tokio::test]
async fn repo_fork_and_transfer() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let src_name = unique_name("fork-src");
let org_name = unique_name("xfer-org");
// Create source repo
client
.create_user_repo(&CreateRepoBody {
name: src_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
// Create an org to fork/transfer into
client
.create_org(&CreateOrgBody {
username: org_name.clone(),
full_name: None,
description: None,
visibility: Some("public".into()),
})
.await
.unwrap();
// Fork into the org with a different name to avoid collision with transfer
let fork_name = format!("{src_name}-fork");
let fork = client
.fork_repo(
GITEA_ADMIN_USER,
&src_name,
&ForkRepoBody {
organization: Some(org_name.clone()),
name: Some(fork_name.clone()),
},
)
.await
.unwrap();
assert!(fork.fork);
assert_eq!(fork.name, fork_name);
// Transfer the original repo to the org
let transferred = client
.transfer_repo(
GITEA_ADMIN_USER,
&src_name,
&TransferRepoBody {
new_owner: org_name.clone(),
team_ids: None,
},
)
.await
.unwrap();
assert_eq!(
transferred.full_name,
format!("{org_name}/{src_name}")
);
// Cleanup
client.delete_repo(&org_name, &src_name).await.unwrap();
// Delete the fork (same name, same org)
// Fork already has the same name; the transfer moved the original so the fork
// may still be under the org. Best-effort cleanup.
let _ = client
.delete_repo(&org_name, &format!("{src_name}"))
.await;
delete_org(&pat, &org_name).await;
}
// ---------------------------------------------------------------------------
// 4. Org operations
// ---------------------------------------------------------------------------
#[tokio::test]
async fn org_operations() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let org_name = unique_name("test-org");
// create_org
let org = client
.create_org(&CreateOrgBody {
username: org_name.clone(),
full_name: Some("Test Org".into()),
description: Some("integration test org".into()),
visibility: Some("public".into()),
})
.await
.unwrap();
assert_eq!(org.username, org_name);
assert_eq!(org.visibility, "public");
// get_org
let fetched = client.get_org(&org_name).await.unwrap();
assert_eq!(fetched.id, org.id);
assert_eq!(fetched.description, "integration test org");
// list_user_orgs
let orgs = client.list_user_orgs(GITEA_ADMIN_USER).await.unwrap();
assert!(orgs.iter().any(|o| o.username == org_name));
// create_org_repo
let repo_name = unique_name("org-repo");
let repo = client
.create_org_repo(
&org_name,
&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
},
)
.await
.unwrap();
assert_eq!(repo.name, repo_name);
// list_org_repos
let repos = client.list_org_repos(&org_name, Some(50)).await.unwrap();
assert!(repos.iter().any(|r| r.name == repo_name));
// Cleanup
client.delete_repo(&org_name, &repo_name).await.unwrap();
delete_org(&pat, &org_name).await;
}
// ---------------------------------------------------------------------------
// 5. Branch operations
// ---------------------------------------------------------------------------
#[tokio::test]
async fn branch_operations() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("branch-repo");
// Create repo with auto_init so it has a main branch
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
default_branch: Some("main".into()),
..Default::default()
})
.await
.unwrap();
// list_branches — should have at least "main"
let branches = client
.list_branches(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
assert!(!branches.is_empty());
assert!(branches.iter().any(|b| b.name == "main"));
// create_branch
let new_branch = client
.create_branch(
GITEA_ADMIN_USER,
&repo_name,
&CreateBranchBody {
new_branch_name: "feature-a".into(),
old_branch_name: Some("main".into()),
},
)
.await
.unwrap();
assert_eq!(new_branch.name, "feature-a");
// Verify new branch shows up
let branches = client
.list_branches(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
assert!(branches.iter().any(|b| b.name == "feature-a"));
// delete_branch
client
.delete_branch(GITEA_ADMIN_USER, &repo_name, "feature-a")
.await
.unwrap();
// Confirm deletion
let branches = client
.list_branches(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
assert!(!branches.iter().any(|b| b.name == "feature-a"));
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 6. Issue CRUD
// ---------------------------------------------------------------------------
#[tokio::test]
async fn issue_crud() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("issue-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
// create_issue
let issue = client
.create_issue(
GITEA_ADMIN_USER,
&repo_name,
&CreateIssueBody {
title: "Test issue".into(),
body: Some("This is a test issue body".into()),
assignees: None,
labels: None,
milestone: None,
},
)
.await
.unwrap();
assert_eq!(issue.title, "Test issue");
assert_eq!(issue.state, "open");
assert_eq!(issue.number, 1);
// get_issue
let fetched = client
.get_issue(GITEA_ADMIN_USER, &repo_name, issue.number)
.await
.unwrap();
assert_eq!(fetched.number, issue.number);
assert_eq!(fetched.body.as_deref(), Some("This is a test issue body"));
// list_issues (open)
let open = client
.list_issues(GITEA_ADMIN_USER, &repo_name, "open", Some(10))
.await
.unwrap();
assert!(open.iter().any(|i| i.number == issue.number));
// edit_issue — close it
let closed = client
.edit_issue(
GITEA_ADMIN_USER,
&repo_name,
issue.number,
&EditIssueBody {
title: Some("Test issue (closed)".into()),
state: Some("closed".into()),
..Default::default()
},
)
.await
.unwrap();
assert_eq!(closed.state, "closed");
assert_eq!(closed.title, "Test issue (closed)");
// list_issues (closed)
let closed_list = client
.list_issues(GITEA_ADMIN_USER, &repo_name, "closed", Some(10))
.await
.unwrap();
assert!(closed_list.iter().any(|i| i.number == issue.number));
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 7. Issue comments
// ---------------------------------------------------------------------------
#[tokio::test]
async fn issue_comments() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("comment-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
let issue = client
.create_issue(
GITEA_ADMIN_USER,
&repo_name,
&CreateIssueBody {
title: "Comment test".into(),
body: None,
assignees: None,
labels: None,
milestone: None,
},
)
.await
.unwrap();
// create_issue_comment
let comment = client
.create_issue_comment(
GITEA_ADMIN_USER,
&repo_name,
issue.number,
"First comment",
)
.await
.unwrap();
assert_eq!(comment.body, "First comment");
assert!(comment.id > 0);
// Add a second comment
let comment2 = client
.create_issue_comment(
GITEA_ADMIN_USER,
&repo_name,
issue.number,
"Second comment",
)
.await
.unwrap();
assert_eq!(comment2.body, "Second comment");
// list_issue_comments
let comments = client
.list_issue_comments(GITEA_ADMIN_USER, &repo_name, issue.number)
.await
.unwrap();
assert_eq!(comments.len(), 2);
assert!(comments.iter().any(|c| c.body == "First comment"));
assert!(comments.iter().any(|c| c.body == "Second comment"));
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 8. Pull request operations
// ---------------------------------------------------------------------------
#[tokio::test]
async fn pull_request_operations() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("pr-repo");
// Create repo with auto_init
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
default_branch: Some("main".into()),
..Default::default()
})
.await
.unwrap();
// Create a feature branch
client
.create_branch(
GITEA_ADMIN_USER,
&repo_name,
&CreateBranchBody {
new_branch_name: "feature-pr".into(),
old_branch_name: Some("main".into()),
},
)
.await
.unwrap();
// Create a file on the feature branch so there is a diff for the PR
create_file_on_branch(
&pat,
GITEA_ADMIN_USER,
&repo_name,
"feature-pr",
"new-file.txt",
)
.await;
// create_pull
let pr = client
.create_pull(
GITEA_ADMIN_USER,
&repo_name,
&CreatePullBody {
title: "Test PR".into(),
head: "feature-pr".into(),
base: "main".into(),
body: Some("PR body".into()),
assignees: None,
labels: None,
milestone: None,
},
)
.await
.unwrap();
assert_eq!(pr.title, "Test PR");
assert_eq!(pr.state, "open");
assert_eq!(pr.number, 1);
// get_pull
let fetched = client
.get_pull(GITEA_ADMIN_USER, &repo_name, pr.number)
.await
.unwrap();
assert_eq!(fetched.number, pr.number);
assert_eq!(fetched.body.as_deref(), Some("PR body"));
let head_ref = fetched.head.as_ref().unwrap();
assert_eq!(head_ref.ref_name, "feature-pr");
// list_pulls
let pulls = client
.list_pulls(GITEA_ADMIN_USER, &repo_name, "open")
.await
.unwrap();
assert!(pulls.iter().any(|p| p.number == pr.number));
// merge_pull (Gitea may need a moment before the PR is mergeable)
let mut merged_ok = false;
for _ in 0..5 {
match client
.merge_pull(
GITEA_ADMIN_USER,
&repo_name,
pr.number,
&MergePullBody {
method: "merge".into(),
merge_message: Some("merge test PR".into()),
delete_branch_after_merge: Some(true),
},
)
.await
{
Ok(()) => { merged_ok = true; break; }
Err(_) => tokio::time::sleep(std::time::Duration::from_secs(1)).await,
}
}
// Verify merged (only if merge succeeded)
let merged = client
.get_pull(GITEA_ADMIN_USER, &repo_name, pr.number)
.await
.unwrap();
assert!(merged.merged);
// list_pulls — closed
let closed_pulls = client
.list_pulls(GITEA_ADMIN_USER, &repo_name, "closed")
.await
.unwrap();
assert!(closed_pulls.iter().any(|p| p.number == pr.number));
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 9. File content
// ---------------------------------------------------------------------------
#[tokio::test]
async fn file_content() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("file-repo");
// Create repo with auto_init so README.md exists
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
// get_file_content
let file = client
.get_file_content(GITEA_ADMIN_USER, &repo_name, "README.md", None)
.await
.unwrap();
assert_eq!(file.name, "README.md");
assert_eq!(file.path, "README.md");
assert_eq!(file.r#type, "file");
assert!(file.size > 0);
assert!(file.content.is_some());
assert!(!file.sha.is_empty());
// get_raw_file
let raw = client
.get_raw_file(GITEA_ADMIN_USER, &repo_name, "README.md", None)
.await
.unwrap();
assert!(!raw.is_empty());
let text = String::from_utf8_lossy(&raw);
assert!(text.contains(&repo_name));
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 10. Notifications
// ---------------------------------------------------------------------------
#[tokio::test]
async fn notifications() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
// list_notifications — should succeed, likely empty
let notifs = client.list_notifications().await.unwrap();
// No assertion on length; new test user has no notifications.
// Just verify it returns a valid vec.
let _ = notifs;
// mark_notifications_read — should succeed even with nothing to mark
client.mark_notifications_read().await.unwrap();
// Verify still returns successfully after marking
let notifs_after = client.list_notifications().await.unwrap();
assert!(notifs_after.iter().all(|n| !n.unread));
}
// ---------------------------------------------------------------------------
// 11. File content with ref parameter
// ---------------------------------------------------------------------------
#[tokio::test]
async fn file_content_with_ref() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("fileref-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
default_branch: Some("main".into()),
..Default::default()
})
.await
.unwrap();
// Create a branch and add a file on it
client
.create_branch(
GITEA_ADMIN_USER,
&repo_name,
&CreateBranchBody {
new_branch_name: "ref-branch".into(),
old_branch_name: Some("main".into()),
},
)
.await
.unwrap();
create_file_on_branch(&pat, GITEA_ADMIN_USER, &repo_name, "ref-branch", "branch-file.txt")
.await;
// get_file_content with explicit ref
let file = client
.get_file_content(GITEA_ADMIN_USER, &repo_name, "branch-file.txt", Some("ref-branch"))
.await
.unwrap();
assert_eq!(file.name, "branch-file.txt");
assert_eq!(file.r#type, "file");
// get_file_content should fail on main (file doesn't exist there)
let err = client
.get_file_content(GITEA_ADMIN_USER, &repo_name, "branch-file.txt", Some("main"))
.await;
assert!(err.is_err());
// get_raw_file with explicit ref
let raw = client
.get_raw_file(GITEA_ADMIN_USER, &repo_name, "branch-file.txt", Some("ref-branch"))
.await
.unwrap();
assert!(!raw.is_empty());
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 12. Mirror sync (on a non-mirror repo — exercises the endpoint, expects error)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn mirror_sync_non_mirror() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("mirror-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
// mirror_sync on a non-mirror repo should fail
let result = client
.mirror_sync(GITEA_ADMIN_USER, &repo_name)
.await;
assert!(result.is_err(), "mirror_sync should fail on non-mirror repo");
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 13. Transfer repo error path (transfer to nonexistent owner)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn transfer_repo_error() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("xfer-err-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
// Transfer to a nonexistent owner should fail
let result = client
.transfer_repo(
GITEA_ADMIN_USER,
&repo_name,
&TransferRepoBody {
new_owner: "nonexistent-owner-zzz".into(),
team_ids: None,
},
)
.await;
assert!(result.is_err(), "transfer to nonexistent owner should fail");
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 14. Search users with default limit
// ---------------------------------------------------------------------------
#[tokio::test]
async fn search_users_default_limit() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
// search_users with None limit (exercises default limit path)
let result = client.search_users(GITEA_ADMIN_USER, None).await.unwrap();
assert!(!result.data.is_empty());
}
// ---------------------------------------------------------------------------
// 15. Search repos with default limit
// ---------------------------------------------------------------------------
#[tokio::test]
async fn search_repos_default_limit() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let result = client.search_repos("", None).await.unwrap();
// Just verify it returns successfully
let _ = result.data;
}
// ---------------------------------------------------------------------------
// 16. List issues with default limit
// ---------------------------------------------------------------------------
#[tokio::test]
async fn list_issues_default_limit() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("defissue-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
// list_issues with None limit (exercises default limit path)
let issues = client
.list_issues(GITEA_ADMIN_USER, &repo_name, "open", None)
.await
.unwrap();
assert!(issues.is_empty());
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 17. List org repos with default limit
// ---------------------------------------------------------------------------
#[tokio::test]
async fn list_org_repos_default_limit() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let org_name = unique_name("deforg");
client
.create_org(&CreateOrgBody {
username: org_name.clone(),
full_name: None,
description: None,
visibility: Some("public".into()),
})
.await
.unwrap();
// list_org_repos with None limit
let repos = client.list_org_repos(&org_name, None).await.unwrap();
assert!(repos.is_empty());
// Cleanup
delete_org(&pat, &org_name).await;
}
// ---------------------------------------------------------------------------
// 18. Get repo error (nonexistent repo)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn get_repo_nonexistent() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let result = client
.get_repo(GITEA_ADMIN_USER, "totally-nonexistent-repo-zzz")
.await;
assert!(result.is_err());
}
// ---------------------------------------------------------------------------
// 19. Create branch without old_branch_name (default branch)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn create_branch_default_base() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("brdefault-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
default_branch: Some("main".into()),
..Default::default()
})
.await
.unwrap();
// create_branch without specifying old_branch_name
let branch = client
.create_branch(
GITEA_ADMIN_USER,
&repo_name,
&CreateBranchBody {
new_branch_name: "from-default".into(),
old_branch_name: None,
},
)
.await
.unwrap();
assert_eq!(branch.name, "from-default");
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 20. Edit issue body only
// ---------------------------------------------------------------------------
#[tokio::test]
async fn edit_issue_body() {
let pat = setup_gitea_pat().await;
let client = make_client(&pat);
let repo_name = unique_name("editbody-repo");
client
.create_user_repo(&CreateRepoBody {
name: repo_name.clone(),
auto_init: Some(true),
..Default::default()
})
.await
.unwrap();
let issue = client
.create_issue(
GITEA_ADMIN_USER,
&repo_name,
&CreateIssueBody {
title: "Body edit test".into(),
body: Some("original body".into()),
assignees: None,
labels: None,
milestone: None,
},
)
.await
.unwrap();
// Edit only the body, not the state
let edited = client
.edit_issue(
GITEA_ADMIN_USER,
&repo_name,
issue.number,
&EditIssueBody {
body: Some("updated body".into()),
..Default::default()
},
)
.await
.unwrap();
assert_eq!(edited.body.as_deref(), Some("updated body"));
assert_eq!(edited.state, "open"); // state unchanged
// Cleanup
client
.delete_repo(GITEA_ADMIN_USER, &repo_name)
.await
.unwrap();
}
// ---------------------------------------------------------------------------
// 21. with_token constructor
// ---------------------------------------------------------------------------
#[tokio::test]
async fn with_token_constructor() {
let pat = setup_gitea_pat().await;
// Use with_token to construct a client pointing at localhost
let client = GiteaClient::with_token("localhost:3000", pat);
// The base_url should be https://src.localhost:3000/api/v1 which won't work,
// but we can verify the constructor itself sets the URL format.
assert!(client.base_url().contains("src.localhost"));
}