fabro/lib/crates/fabro-github/tests/integration.rs
Bryan Helmkamp c22845e548 Integrate twin-github for fabro-github tests
Add the stripped twin-github test server to the workspace, wire it through
fabro-test, and cover fabro-github's real HTTP auth and pull-request flows
with twin-backed integration tests. This also refactors the GitHub helper
entry points to take explicit base URLs so tests and callers share the same
request path.
2026-04-01 09:48:33 -04:00

235 lines
5.6 KiB
Rust

use fabro_github::{
AutoMergeMethod, GitHubAppCredentials, close_pull_request,
create_installation_access_token_for_pr, create_pull_request, enable_auto_merge,
get_pull_request, merge_pull_request, resolve_authenticated_url, sign_app_jwt,
};
use fabro_test::{GitHubAppConfig, GitHubAppState, TwinGitHub};
const TEST_RSA_KEY: &str = include_str!("../src/testdata/rsa_private.pem");
fn github_credentials() -> GitHubAppCredentials {
GitHubAppCredentials {
app_id: "42".to_string(),
private_key_pem: TEST_RSA_KEY.to_string(),
}
}
fn standard_app_state() -> GitHubAppState {
let mut state = GitHubAppState::new();
state.register_app(GitHubAppConfig {
app_id: "42".into(),
slug: "test-app".into(),
owner_login: "acme".into(),
public: true,
private_key_pem: TEST_RSA_KEY.into(),
webhook_secret: None,
});
state.add_installation("42", "acme", vec!["widgets".into()], false);
state.add_repository(
"acme",
"widgets",
vec!["main".into(), "feature".into()],
false,
);
state
}
#[fabro_macros::e2e_test(twin)]
async fn create_and_get_pull_request() {
let twin = TwinGitHub::start(standard_app_state()).await;
let creds = github_credentials();
let created = create_pull_request(
&creds,
"acme",
"widgets",
"main",
"feature",
"Add widgets",
"PR body",
false,
&twin.base_url,
)
.await
.unwrap();
let pr = get_pull_request(&creds, "acme", "widgets", created.number, &twin.base_url)
.await
.unwrap();
assert_eq!(pr.title, "Add widgets");
assert_eq!(pr.state, "open");
assert_eq!(pr.head.ref_name, "feature");
assert_eq!(pr.base.ref_name, "main");
twin.shutdown().await;
}
#[fabro_macros::e2e_test(twin)]
async fn create_merge_and_verify_state() {
let twin = TwinGitHub::start(standard_app_state()).await;
let creds = github_credentials();
let created = create_pull_request(
&creds,
"acme",
"widgets",
"main",
"feature",
"Merge me",
"PR body",
false,
&twin.base_url,
)
.await
.unwrap();
merge_pull_request(
&creds,
"acme",
"widgets",
created.number,
"squash",
&twin.base_url,
)
.await
.unwrap();
let pr = get_pull_request(&creds, "acme", "widgets", created.number, &twin.base_url)
.await
.unwrap();
assert_eq!(pr.state, "closed");
assert_eq!(pr.mergeable, Some(false));
twin.shutdown().await;
}
#[fabro_macros::e2e_test(twin)]
async fn create_close_and_verify_state() {
let twin = TwinGitHub::start(standard_app_state()).await;
let creds = github_credentials();
let created = create_pull_request(
&creds,
"acme",
"widgets",
"main",
"feature",
"Close me",
"PR body",
false,
&twin.base_url,
)
.await
.unwrap();
close_pull_request(&creds, "acme", "widgets", created.number, &twin.base_url)
.await
.unwrap();
let pr = get_pull_request(&creds, "acme", "widgets", created.number, &twin.base_url)
.await
.unwrap();
assert_eq!(pr.state, "closed");
twin.shutdown().await;
}
#[fabro_macros::e2e_test(twin)]
async fn enable_auto_merge_persists() {
let twin = TwinGitHub::start(standard_app_state()).await;
let creds = github_credentials();
let created = create_pull_request(
&creds,
"acme",
"widgets",
"main",
"feature",
"Auto merge me",
"PR body",
false,
&twin.base_url,
)
.await
.unwrap();
enable_auto_merge(
&creds,
"acme",
"widgets",
&created.node_id,
AutoMergeMethod::Squash,
&twin.base_url,
)
.await
.unwrap();
let jwt = sign_app_jwt(&creds.app_id, &creds.private_key_pem).unwrap();
let token = create_installation_access_token_for_pr(
&reqwest::Client::new(),
&jwt,
"acme",
"widgets",
&twin.base_url,
)
.await
.unwrap();
let detail: serde_json::Value = reqwest::Client::new()
.get(format!(
"{}/repos/acme/widgets/pulls/{}",
twin.base_url, created.number
))
.header("Authorization", format!("Bearer {token}"))
.header("Accept", "application/vnd.github+json")
.header("User-Agent", "fabro")
.send()
.await
.unwrap()
.json()
.await
.unwrap();
assert_eq!(
detail["auto_merge"]["merge_method"].as_str(),
Some("SQUASH")
);
twin.shutdown().await;
}
#[fabro_macros::e2e_test(twin)]
async fn resolve_authenticated_url_embeds_token() {
let twin = TwinGitHub::start(standard_app_state()).await;
let creds = github_credentials();
let url = resolve_authenticated_url(
&creds,
"https://github.com/acme/widgets.git",
&twin.base_url,
)
.await
.unwrap();
assert!(url.starts_with("https://x-access-token:ghs_"));
assert!(url.contains("github.com/acme/widgets.git"));
twin.shutdown().await;
}
#[fabro_macros::e2e_test(twin)]
async fn resolve_authenticated_url_errors_on_non_github_url() {
let twin = TwinGitHub::start(standard_app_state()).await;
let creds = github_credentials();
let error = resolve_authenticated_url(&creds, "https://gitlab.com/foo/bar", &twin.base_url)
.await
.unwrap_err();
assert!(error.contains("Not a GitHub HTTPS URL"));
twin.shutdown().await;
}