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
1035 lines
30 KiB
Rust
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"));
|
|
}
|