diff --git a/test/twin/github/src/auth.rs b/test/twin/github/src/auth.rs index a8e4c8370..f7652356d 100644 --- a/test/twin/github/src/auth.rs +++ b/test/twin/github/src/auth.rs @@ -116,59 +116,22 @@ pub fn ensure_permission( mod tests { use super::*; use crate::state::derive_public_key_pem; - - fn test_rsa_key() -> String { - use std::process::Command; - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() - } - - fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String { - use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; - use serde::Serialize; - - #[derive(Serialize)] - struct Claims { - iss: String, - iat: i64, - exp: i64, - } - - let now = chrono::Utc::now().timestamp(); - let claims = Claims { - iss: app_id.to_string(), - iat: now - 60, - exp: now + 600, - }; - let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap(); - encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap() - } + use crate::test_support::{sign_test_jwt, test_rsa_private_key, test_rsa_public_key}; #[test] fn verify_valid_jwt() { - let private_pem = test_rsa_key(); - let public_pem = derive_public_key_pem(&private_pem); - let jwt = sign_test_jwt("12345", &private_pem); - let result = verify_app_jwt(&jwt, &public_pem); + let private_pem = test_rsa_private_key(); + let jwt = sign_test_jwt("12345", private_pem); + let result = verify_app_jwt(&jwt, test_rsa_public_key()); assert!(result.is_ok()); assert_eq!(result.unwrap(), "12345"); } #[test] fn reject_invalid_jwt() { - let private_pem = test_rsa_key(); - let public_pem = derive_public_key_pem(&private_pem); - let result = verify_app_jwt("invalid.jwt.token", &public_pem); + let derived_public_pem = derive_public_key_pem(test_rsa_private_key()); + assert_eq!(derived_public_pem, test_rsa_public_key()); + let result = verify_app_jwt("invalid.jwt.token", test_rsa_public_key()); assert!(result.is_err()); } } diff --git a/test/twin/github/src/fixtures.rs b/test/twin/github/src/fixtures.rs index 312bc376c..bd60a81a4 100644 --- a/test/twin/github/src/fixtures.rs +++ b/test/twin/github/src/fixtures.rs @@ -372,21 +372,8 @@ fn next_pr_number(pull_requests: &HashMap<(String, String), Vec>) - } #[cfg(test)] -fn test_rsa_key() -> String { - use std::process::Command; - - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() +fn test_rsa_key() -> &'static str { + crate::test_support::test_rsa_private_key() } #[cfg(test)] @@ -548,7 +535,7 @@ impl FixtureState { slug: "fixture-app".to_string(), owner_login: "acme".to_string(), public: true, - private_key_pem: test_rsa_key(), + private_key_pem: test_rsa_key().to_string(), webhook_secret: Some("whsec".to_string()), }], ..Self::default() diff --git a/test/twin/github/src/handlers/app.rs b/test/twin/github/src/handlers/app.rs index 95b65a170..72a2d4391 100644 --- a/test/twin/github/src/handlers/app.rs +++ b/test/twin/github/src/handlers/app.rs @@ -113,59 +113,23 @@ pub async fn patch_webhook_config( mod tests { use crate::server::TestServer; use crate::state::{AppOptions, AppState}; - - fn test_rsa_key() -> String { - use std::process::Command; - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() - } - - fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String { - use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; - use serde::Serialize; - - #[derive(Serialize)] - struct Claims { - iss: String, - iat: i64, - exp: i64, - } - - let now = chrono::Utc::now().timestamp(); - let claims = Claims { - iss: app_id.to_string(), - iat: now - 60, - exp: now + 600, - }; - let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap(); - encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap() - } + use crate::test_support::{sign_test_jwt, test_rsa_private_key}; #[tokio::test] async fn get_app_returns_app_info() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "12345".to_string(), slug: "my-app".to_string(), owner_login: "my-org".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); let server = TestServer::start(state).await; - let jwt = sign_test_jwt("12345", &pem); + let jwt = sign_test_jwt("12345", pem); let client = reqwest::Client::new(); let resp = client .get(&format!("{}/app", server.url())) @@ -185,14 +149,14 @@ mod tests { #[tokio::test] async fn get_app_rejects_invalid_jwt() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "12345".to_string(), slug: "my-app".to_string(), owner_login: "my-org".to_string(), public: true, - private_key_pem: pem, + private_key_pem: pem.to_string(), webhook_secret: None, }); let server = TestServer::start(state).await; @@ -211,14 +175,14 @@ mod tests { #[tokio::test] async fn get_apps_slug_public() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "12345".to_string(), slug: "my-app".to_string(), owner_login: "my-org".to_string(), public: true, - private_key_pem: pem, + private_key_pem: pem.to_string(), webhook_secret: None, }); let server = TestServer::start(state).await; @@ -238,14 +202,14 @@ mod tests { #[tokio::test] async fn get_apps_slug_private_returns_404() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "12345".to_string(), slug: "private-app".to_string(), owner_login: "my-org".to_string(), public: false, - private_key_pem: pem, + private_key_pem: pem.to_string(), webhook_secret: None, }); let server = TestServer::start(state).await; diff --git a/test/twin/github/src/handlers/branches.rs b/test/twin/github/src/handlers/branches.rs index d4beadab9..bd1e79356 100644 --- a/test/twin/github/src/handlers/branches.rs +++ b/test/twin/github/src/handlers/branches.rs @@ -93,43 +93,7 @@ pub async fn get_branch( mod tests { use crate::server::TestServer; use crate::state::{AppOptions, AppState}; - - fn test_rsa_key() -> String { - use std::process::Command; - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() - } - - fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String { - use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; - use serde::Serialize; - - #[derive(Serialize)] - struct Claims { - iss: String, - iat: i64, - exp: i64, - } - - let now = chrono::Utc::now().timestamp(); - let claims = Claims { - iss: app_id.to_string(), - iat: now - 60, - exp: now + 600, - }; - let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap(); - encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap() - } + use crate::test_support::{sign_test_jwt, test_rsa_private_key}; async fn get_installation_token( client: &reqwest::Client, @@ -167,14 +131,14 @@ mod tests { #[tokio::test] async fn branch_exists_returns_200() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); state.add_installation("100", "owner", vec!["repo".to_string()], false); @@ -186,7 +150,7 @@ mod tests { ); let server = TestServer::start(state).await; - let jwt = sign_test_jwt("100", &pem); + let jwt = sign_test_jwt("100", pem); let client = reqwest::Client::new(); let token = get_installation_token(&client, &jwt, "owner", "repo", server.url()).await; @@ -210,21 +174,21 @@ mod tests { #[tokio::test] async fn branch_not_found_returns_404() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); state.add_installation("100", "owner", vec!["repo".to_string()], false); state.add_repository("owner", "repo", vec!["main".to_string()], false); let server = TestServer::start(state).await; - let jwt = sign_test_jwt("100", &pem); + let jwt = sign_test_jwt("100", pem); let client = reqwest::Client::new(); let token = get_installation_token(&client, &jwt, "owner", "repo", server.url()).await; diff --git a/test/twin/github/src/handlers/graphql.rs b/test/twin/github/src/handlers/graphql.rs index f165ec05d..e98f42661 100644 --- a/test/twin/github/src/handlers/graphql.rs +++ b/test/twin/github/src/handlers/graphql.rs @@ -614,43 +614,7 @@ fn extract_unquoted_value(s: &str, key: &str) -> Option { mod tests { use crate::server::TestServer; use crate::state::{AppOptions, AppState, PullRequest}; - - fn test_rsa_key() -> String { - use std::process::Command; - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() - } - - fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String { - use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; - use serde::Serialize; - - #[derive(Serialize)] - struct Claims { - iss: String, - iat: i64, - exp: i64, - } - - let now = chrono::Utc::now().timestamp(); - let claims = Claims { - iss: app_id.to_string(), - iat: now - 60, - exp: now + 600, - }; - let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap(); - encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap() - } + use crate::test_support::{sign_test_jwt, test_rsa_private_key}; async fn get_installation_token( client: &reqwest::Client, @@ -718,10 +682,10 @@ mod tests { #[tokio::test] async fn viewer_query_returns_id() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.viewer_id = "U_testviewer".to_string(); - let (server, client, token) = setup_with_token(&mut state, &pem).await; + let (server, client, token) = setup_with_token(&mut state, pem).await; let resp = client .post(&format!("{}/graphql", server.url())) @@ -742,7 +706,7 @@ mod tests { #[tokio::test] async fn enable_auto_merge_mutation() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); // Pre-seed a PR state @@ -768,7 +732,7 @@ mod tests { updated_at: "2026-01-01T00:00:00Z".to_string(), auto_merge: None, }); - let (server, client, token) = setup_with_token(&mut state, &pem).await; + let (server, client, token) = setup_with_token(&mut state, pem).await; let query = r#"mutation { enablePullRequestAutoMerge(input: {pullRequestId: "PR_test123", mergeMethod: SQUASH}) { @@ -824,7 +788,7 @@ mod tests { #[tokio::test] async fn org_project_query_returns_node_id() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.projects.push(crate::state::Project { node_id: "PVT_org123".to_string(), @@ -862,7 +826,7 @@ mod tests { }, }], }); - let (server, client, token) = setup_with_token(&mut state, &pem).await; + let (server, client, token) = setup_with_token(&mut state, pem).await; // Query org project let query = r#" @@ -894,7 +858,7 @@ mod tests { #[tokio::test] async fn project_items_query_returns_paginated() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.projects.push(crate::state::Project { node_id: "PVT_test".to_string(), @@ -919,7 +883,7 @@ mod tests { }, }], }); - let (server, client, token) = setup_with_token(&mut state, &pem).await; + let (server, client, token) = setup_with_token(&mut state, pem).await; let query = r#" query($projectId: ID!, $cursor: String) { @@ -978,7 +942,7 @@ mod tests { #[tokio::test] async fn update_project_item_field_value() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.projects.push(crate::state::Project { node_id: "PVT_test".to_string(), @@ -1012,7 +976,7 @@ mod tests { }, }], }); - let (server, client, token) = setup_with_token(&mut state, &pem).await; + let (server, client, token) = setup_with_token(&mut state, pem).await; let query = r#" mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { diff --git a/test/twin/github/src/handlers/installations.rs b/test/twin/github/src/handlers/installations.rs index 5e3f88d2f..9b61b2a04 100644 --- a/test/twin/github/src/handlers/installations.rs +++ b/test/twin/github/src/handlers/installations.rs @@ -178,61 +178,25 @@ pub async fn create_access_token( mod tests { use crate::server::TestServer; use crate::state::{AppOptions, AppState}; - - fn test_rsa_key() -> String { - use std::process::Command; - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() - } - - fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String { - use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; - use serde::Serialize; - - #[derive(Serialize)] - struct Claims { - iss: String, - iat: i64, - exp: i64, - } - - let now = chrono::Utc::now().timestamp(); - let claims = Claims { - iss: app_id.to_string(), - iat: now - 60, - exp: now + 600, - }; - let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap(); - encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap() - } + use crate::test_support::{sign_test_jwt, test_rsa_private_key}; #[tokio::test] async fn get_installation_returns_id() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); state.add_installation("100", "owner", vec!["repo".to_string()], false); state.add_repository("owner", "repo", vec!["main".to_string()], false); let server = TestServer::start(state).await; - let jwt = sign_test_jwt("100", &pem); + let jwt = sign_test_jwt("100", pem); let client = reqwest::Client::new(); let resp = client .get(&format!("{}/repos/owner/repo/installation", server.url())) @@ -250,20 +214,20 @@ mod tests { #[tokio::test] async fn get_installation_returns_404_when_not_installed() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); // No installation added let server = TestServer::start(state).await; - let jwt = sign_test_jwt("100", &pem); + let jwt = sign_test_jwt("100", pem); let client = reqwest::Client::new(); let resp = client .get(&format!("{}/repos/owner/repo/installation", server.url())) @@ -278,20 +242,20 @@ mod tests { #[tokio::test] async fn get_installation_returns_403_when_suspended() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); state.add_installation("100", "owner", vec!["repo".to_string()], true); // suspended let server = TestServer::start(state).await; - let jwt = sign_test_jwt("100", &pem); + let jwt = sign_test_jwt("100", pem); let client = reqwest::Client::new(); let resp = client .get(&format!("{}/repos/owner/repo/installation", server.url())) @@ -306,20 +270,20 @@ mod tests { #[tokio::test] async fn create_access_token_returns_201() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); let install_id = state.add_installation("100", "owner", vec!["repo".to_string()], false); let server = TestServer::start(state).await; - let jwt = sign_test_jwt("100", &pem); + let jwt = sign_test_jwt("100", pem); let client = reqwest::Client::new(); let resp = client .post(&format!( @@ -344,20 +308,20 @@ mod tests { #[tokio::test] async fn create_access_token_returns_422_for_unauthorized_repo() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "100".to_string(), slug: "test-app".to_string(), owner_login: "owner".to_string(), public: true, - private_key_pem: pem.clone(), + private_key_pem: pem.to_string(), webhook_secret: None, }); let install_id = state.add_installation("100", "owner", vec!["repo".to_string()], false); let server = TestServer::start(state).await; - let jwt = sign_test_jwt("100", &pem); + let jwt = sign_test_jwt("100", pem); let client = reqwest::Client::new(); let resp = client .post(&format!( diff --git a/test/twin/github/src/handlers/pulls.rs b/test/twin/github/src/handlers/pulls.rs index 76f50563d..b558c75d8 100644 --- a/test/twin/github/src/handlers/pulls.rs +++ b/test/twin/github/src/handlers/pulls.rs @@ -303,43 +303,7 @@ fn pr_to_json(pr: &PullRequest) -> serde_json::Value { mod tests { use crate::server::TestServer; use crate::state::{AppOptions, AppState}; - - fn test_rsa_key() -> String { - use std::process::Command; - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() - } - - fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String { - use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; - use serde::Serialize; - - #[derive(Serialize)] - struct Claims { - iss: String, - iat: i64, - exp: i64, - } - - let now = chrono::Utc::now().timestamp(); - let claims = Claims { - iss: app_id.to_string(), - iat: now - 60, - exp: now + 600, - }; - let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap(); - encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap() - } + use crate::test_support::{sign_test_jwt, test_rsa_private_key}; async fn get_installation_token( client: &reqwest::Client, @@ -405,9 +369,9 @@ mod tests { #[tokio::test] async fn create_pr_returns_201() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); - let (server, client, token) = setup_and_get_token(&mut state, &pem).await; + let (server, client, token) = setup_and_get_token(&mut state, pem).await; let resp = client .post(&format!("{}/repos/owner/repo/pulls", server.url())) @@ -435,9 +399,9 @@ mod tests { #[tokio::test] async fn get_pr_returns_detail() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); - let (server, client, token) = setup_and_get_token(&mut state, &pem).await; + let (server, client, token) = setup_and_get_token(&mut state, pem).await; // Create a PR first let create_resp = client @@ -485,9 +449,9 @@ mod tests { #[tokio::test] async fn merge_pr_returns_200() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); - let (server, client, token) = setup_and_get_token(&mut state, &pem).await; + let (server, client, token) = setup_and_get_token(&mut state, pem).await; // Create a PR let create_resp = client @@ -530,9 +494,9 @@ mod tests { #[tokio::test] async fn close_pr_returns_200() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); - let (server, client, token) = setup_and_get_token(&mut state, &pem).await; + let (server, client, token) = setup_and_get_token(&mut state, pem).await; // Create a PR let create_resp = client @@ -562,9 +526,9 @@ mod tests { #[tokio::test] async fn merge_nonexistent_pr_returns_404() { - let pem = test_rsa_key(); + let pem = test_rsa_private_key(); let mut state = AppState::new(); - let (server, client, token) = setup_and_get_token(&mut state, &pem).await; + let (server, client, token) = setup_and_get_token(&mut state, pem).await; let resp = client .put(&format!( diff --git a/test/twin/github/src/lib.rs b/test/twin/github/src/lib.rs index 2c7d1e496..1fe44f972 100644 --- a/test/twin/github/src/lib.rs +++ b/test/twin/github/src/lib.rs @@ -10,6 +10,8 @@ pub mod fixtures; pub mod handlers; pub mod server; pub mod state; +#[cfg(test)] +pub mod test_support; pub use server::TestServer; pub use state::AppState; diff --git a/test/twin/github/src/state.rs b/test/twin/github/src/state.rs index 307716701..bcd5f98a6 100644 --- a/test/twin/github/src/state.rs +++ b/test/twin/github/src/state.rs @@ -429,6 +429,7 @@ pub fn init_git_repos(state: &mut AppState, git_root: &std::path::Path) -> Resul #[cfg(test)] mod tests { use super::*; + use crate::test_support::{test_rsa_private_key, test_rsa_public_key}; #[test] fn empty_state_has_no_apps() { @@ -436,22 +437,6 @@ mod tests { assert!(state.apps.is_empty()); } - fn test_rsa_key() -> String { - use std::process::Command; - let output = Command::new("openssl") - .args([ - "genpkey", - "-algorithm", - "RSA", - "-pkeyopt", - "rsa_keygen_bits:2048", - ]) - .output() - .expect("openssl should be available"); - assert!(output.status.success()); - String::from_utf8(output.stdout).unwrap() - } - #[test] fn repository_has_private_field() { let mut state = AppState::new(); @@ -475,19 +460,17 @@ mod tests { #[test] fn can_register_app() { - let pem = test_rsa_key(); let mut state = AppState::new(); state.register_app(AppOptions { app_id: "12345".to_string(), slug: "test-app".to_string(), owner_login: "test-owner".to_string(), public: true, - private_key_pem: pem, + private_key_pem: test_rsa_private_key().to_string(), webhook_secret: Some("secret".to_string()), }); assert_eq!(state.apps.len(), 1); assert_eq!(state.apps["12345"].config.slug, "test-app"); - // Verify public key was derived - assert!(state.apps["12345"].public_key_pem.contains("PUBLIC KEY")); + assert_eq!(state.apps["12345"].public_key_pem, test_rsa_public_key()); } } diff --git a/test/twin/github/src/test_support.rs b/test/twin/github/src/test_support.rs new file mode 100644 index 000000000..4271032ae --- /dev/null +++ b/test/twin/github/src/test_support.rs @@ -0,0 +1,31 @@ +#[cfg(test)] +pub fn test_rsa_private_key() -> &'static str { + include_str!("testdata/rsa_private.pem") +} + +#[cfg(test)] +pub fn test_rsa_public_key() -> &'static str { + include_str!("testdata/rsa_public.pem") +} + +#[cfg(test)] +pub fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String { + use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; + use serde::Serialize; + + #[derive(Serialize)] + struct Claims { + iss: String, + iat: i64, + exp: i64, + } + + let now = chrono::Utc::now().timestamp(); + let claims = Claims { + iss: app_id.to_string(), + iat: now - 60, + exp: now + 600, + }; + let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap(); + encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap() +} diff --git a/test/twin/github/src/testdata/rsa_private.pem b/test/twin/github/src/testdata/rsa_private.pem new file mode 100644 index 000000000..de80899ea --- /dev/null +++ b/test/twin/github/src/testdata/rsa_private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCty51n9z4E+fGI +7m/AC2ZS5PoLgi3ZR8XyUwy10XdbVKQvCCJ04cUgml2TCqA5dFEFsj9a+Ooyz9hE +J70g9U8iFBq53lx3Og3cvZUy1vdnwInMQhdXzmJAdkqZl4/aW7V/571yXfDPZBb5 +295Iof3FMEwWzYQMiQm7kZrshRirQBfvlvpIu0N30ZwmyC1MbH0ESBq86owLiVzf +0IBO4dOxmvSTX12Qd1v7+N3hPqMyP0F4PQJM7uLRfHYIO8NVclpS+crj4GJMp9Cj +r1BJL0GvRDZsWazzLg/PVt0sEFJMmY0HoSQCZ2ybqXiD46VXDVL/q0ioxje9bE8X +ZGEUWM1ZAgMBAAECgf8PerJhVry3lzYbrvmXH6FRLRcRsPsSYNcRAYXdASih+C6G +mNVYAAV8rwf4otCfObgEQakkkFKAN1uG3eJocS9HdacMjmC+XpmlrrMz1aYwOfRA +hvE0sUxNU/o6BeuG1HMFkI+iwon6FjAWg+LdzQyGrKdies4BhFPyNI38AvkDM+BR +0z8xOESh+GkMjRcArIxKnzlFaj+BOOza6Ju3cAZfK7Sw+YvxIu7ECB8Mf+0uEX5Y +95AtnD7v3bA/kij1Ate3po9KWr7jfBBFwlNuqYFZ28Y9nSg+p69EMmZK9YQseFwo +72t4+lM60J1MuK08khVvWHsHyZzbCquKKyoVlE0CgYEA19UUbCbwIi2KgJA+5YFF +DWx5KQXo8t6tDa2jWcCorzm/y3r6jCf7Ja6zIjyZFGtqZw+5z6pD7/T2QUc/GIHO +JXo6CDJwyrmcPYb0qmqunCApfpSdXy22Dk43sDcanLF0+cLYF13T25WiebNxswtC +6knS8Nj2gIMdLGqwhgXxalUCgYEAziPDhKbh0GVTE09JLbZXvETuzOoPIQVSHq52 +0/eTxxp5DpNhERn/GGFkXYlg9XoYye+cvf7t3vhYUdefxl1qbnLp/aaq/pH4m4Zb +sXeTH+fcyG8e+H1gCnKL7mLc733JcQ/PkT2VemWwE59nobqC8a4S3jSVLgb830J8 +Tj+z4vUCgYBlmRYt+v5a+FkUP9J7sqhQs1ktaGzJtux7XdthKoG9Sk/wdwlt5Hwi +tuP4H9JnfFDwBsUO7jVgxU0OWX1oCeKCXdDnfub9ZvYXOGn/toxPTgx6W3bhEOF8 +RnGMb8QFMru/vmp+RVqvKPb8lu3UvF5ZbzruyRLwV8fp2qcRz8ZeqQKBgAvF8SG/ +YkBDuTXuHlDVDDe9fCIJ8ClDo4gLENBUYIVLlsRlWHLgmDgtP80zzmzWt1m5LKuR +RsaoSmsKNJ7gsPZz67dH8JYvghJn3w7ItIdq96oe5pbsmjKeji1kShgHiBzD+Wfj +K4ldC2xje0i6XUYuWfeE60xAq+qYYn8Zlk2NAoGACNwVYYdj7YkjfclzggBn08qV +K8GAQQ/p6Cnsq1/2YHtugEeAtASazR8GqOzV6wy0nGi6XaH6Kw5cDZ7ACETocSX+ +wM30YkPJBg0WUsCzsLg6B1ri8gnJCUBp2IwMeSU+Av5GG09+oqEwawI5WoQO1+TD +rUZ9gbbIAXA79BD11L4= +-----END PRIVATE KEY----- diff --git a/test/twin/github/src/testdata/rsa_public.pem b/test/twin/github/src/testdata/rsa_public.pem new file mode 100644 index 000000000..63d2af3e1 --- /dev/null +++ b/test/twin/github/src/testdata/rsa_public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArcudZ/c+BPnxiO5vwAtm +UuT6C4It2UfF8lMMtdF3W1SkLwgidOHFIJpdkwqgOXRRBbI/WvjqMs/YRCe9IPVP +IhQaud5cdzoN3L2VMtb3Z8CJzEIXV85iQHZKmZeP2lu1f+e9cl3wz2QW+dveSKH9 +xTBMFs2EDIkJu5Ga7IUYq0AX75b6SLtDd9GcJsgtTGx9BEgavOqMC4lc39CATuHT +sZr0k19dkHdb+/jd4T6jMj9BeD0CTO7i0Xx2CDvDVXJaUvnK4+BiTKfQo69QSS9B +r0Q2bFms8y4Pz1bdLBBSTJmNB6EkAmdsm6l4g+OlVw1S/6tIqMY3vWxPF2RhFFjN +WQIDAQAB +-----END PUBLIC KEY-----