fabro/lib/components/fabro-github/tests/integration.rs
2026-07-23 17:59:34 -04:00

228 lines
6 KiB
Rust

use fabro_github::{
GitHubAppCredentials, GitHubContext, GitHubCredentials, 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::{GitHubAppOptions, GitHubAppState, TwinGitHub};
use fabro_types::settings::run::MergeStrategy;
const TEST_RSA_KEY: &str = include_str!("../src/testdata/rsa_private.pem");
fn github_credentials() -> GitHubCredentials {
GitHubCredentials::App(GitHubAppCredentials {
app_id: "42".to_string(),
private_key_pem: TEST_RSA_KEY.to_string(),
slug: Some("test-app".to_string()),
})
}
fn standard_app_state() -> GitHubAppState {
let mut state = GitHubAppState::new();
state.register_app(GitHubAppOptions {
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 ctx = &GitHubContext::new(&creds, &twin.base_url);
let created = create_pull_request(
ctx,
"acme",
"widgets",
"main",
"feature",
"Add widgets",
"PR body",
false,
)
.await
.unwrap();
let pr = get_pull_request(ctx, "acme", "widgets", created.number)
.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 ctx = &GitHubContext::new(&creds, &twin.base_url);
let created = create_pull_request(
ctx, "acme", "widgets", "main", "feature", "Merge me", "PR body", false,
)
.await
.unwrap();
merge_pull_request(
ctx,
"acme",
"widgets",
created.number,
MergeStrategy::Squash,
)
.await
.unwrap();
let pr = get_pull_request(ctx, "acme", "widgets", created.number)
.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 ctx = &GitHubContext::new(&creds, &twin.base_url);
let created = create_pull_request(
ctx, "acme", "widgets", "main", "feature", "Close me", "PR body", false,
)
.await
.unwrap();
close_pull_request(ctx, "acme", "widgets", created.number)
.await
.unwrap();
let pr = get_pull_request(ctx, "acme", "widgets", created.number)
.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 ctx = &GitHubContext::new(&creds, &twin.base_url);
let created = create_pull_request(
ctx,
"acme",
"widgets",
"main",
"feature",
"Auto merge me",
"PR body",
false,
)
.await
.unwrap();
enable_auto_merge(
ctx,
"acme",
"widgets",
&created.node_id,
MergeStrategy::Squash,
)
.await
.unwrap();
let GitHubCredentials::App(app_creds) = &creds else {
panic!("expected app credentials");
};
let jwt = sign_app_jwt(&app_creds.app_id, &app_creds.private_key_pem).unwrap();
let client = fabro_test::test_http_client();
let token =
create_installation_access_token_for_pr(&client, &jwt, "acme", "widgets", &twin.base_url)
.await
.unwrap();
let detail: serde_json::Value = fabro_test::test_http_client()
.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(
&GitHubContext::new(&creds, &twin.base_url),
"https://github.com/acme/widgets.git",
)
.await
.unwrap();
assert!(url.raw_string().starts_with("https://x-access-token:ghs_"));
assert!(url.raw_string().contains("github.com/acme/widgets.git"));
assert!(
url.redacted_string()
.starts_with("https://x-access-token:****@")
);
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(
&GitHubContext::new(&creds, &twin.base_url),
"https://gitlab.com/foo/bar",
)
.await
.unwrap_err();
assert!(error.to_string().contains("Not a GitHub HTTPS URL"));
twin.shutdown().await;
}