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; }