From 10e0a0e7d2bd7ebe30079d2a1bbcb636282ddbde Mon Sep 17 00:00:00 2001 From: Nicholas Yang Date: Tue, 31 Jan 2023 16:30:51 -0500 Subject: [PATCH 1/5] Testing login --- crates/turborepo-lib/src/cli.rs | 4 +- crates/turborepo-lib/src/commands/login.rs | 131 ++++++++++++++++++++- 2 files changed, 128 insertions(+), 7 deletions(-) diff --git a/crates/turborepo-lib/src/cli.rs b/crates/turborepo-lib/src/cli.rs index f8b552cef5645..32d4a03d046c4 100644 --- a/crates/turborepo-lib/src/cli.rs +++ b/crates/turborepo-lib/src/cli.rs @@ -454,9 +454,9 @@ pub async fn run(repo_state: Option) -> Result { return Ok(Payload::Go(Box::new(clap_args))); } - let base = CommandBase::new(clap_args, repo_root)?; + let mut base = CommandBase::new(clap_args, repo_root)?; - login::login(base).await?; + login::login(&mut base).await?; Ok(Payload::Rust(Ok(0))) } diff --git a/crates/turborepo-lib/src/commands/login.rs b/crates/turborepo-lib/src/commands/login.rs index d3a05c32ab4d5..6530bb490a632 100644 --- a/crates/turborepo-lib/src/commands/login.rs +++ b/crates/turborepo-lib/src/commands/login.rs @@ -2,7 +2,7 @@ use std::{net::SocketAddr, sync::Arc}; use anyhow::{anyhow, Result}; use axum::{extract::Query, response::Redirect, routing::get, Router}; -use log::{debug, warn}; +use log::debug; use serde::Deserialize; use tokio::sync::OnceCell; @@ -16,7 +16,7 @@ use crate::{ const DEFAULT_HOST_NAME: &str = "127.0.0.1"; const DEFAULT_PORT: u16 = 9789; -pub async fn login(mut base: CommandBase) -> Result<()> { +pub async fn login(base: &mut CommandBase) -> Result<()> { let repo_config = base.repo_config()?; let login_url_base = repo_config.login_url(); debug!("turbo v{}", get_version()); @@ -26,9 +26,8 @@ pub async fn login(mut base: CommandBase) -> Result<()> { let redirect_url = format!("http://{DEFAULT_HOST_NAME}:{DEFAULT_PORT}"); let login_url = format!("{login_url_base}/turborepo/token?redirect_uri={redirect_url}"); println!(">>> Opening browser to {login_url}"); + direct_user_to_url(&login_url).await; let spinner = start_spinner("Waiting for your authorization..."); - direct_user_to_url(&login_url); - let token_cell = Arc::new(OnceCell::new()); new_one_shot_server( DEFAULT_PORT, @@ -36,6 +35,7 @@ pub async fn login(mut base: CommandBase) -> Result<()> { token_cell.clone(), ) .await?; + spinner.finish_and_clear(); let token = token_cell .get() @@ -46,7 +46,7 @@ pub async fn login(mut base: CommandBase) -> Result<()> { let client = base.api_client()?.unwrap(); let user_response = client.get_user().await?; - let ui = base.ui; + let ui = &base.ui; println!( " @@ -67,6 +67,9 @@ pub async fn login(mut base: CommandBase) -> Result<()> { Ok(()) } +#[cfg(test)] +async fn direct_user_to_url(url: &str) {} +#[cfg(not(test))] fn direct_user_to_url(url: &str) { if webbrowser::open(url).is_err() { warn!("Failed to open browser. Please visit {url} in your browser."); @@ -78,6 +81,13 @@ struct LoginPayload { token: String, } +#[cfg(test)] +async fn new_one_shot_server(_: u16, _: String, login_token: Arc>) -> Result<()> { + login_token.set("test-token".to_string()).unwrap(); + Ok(()) +} + +#[cfg(not(test))] async fn new_one_shot_server( port: u16, login_url_base: String, @@ -102,3 +112,114 @@ async fn new_one_shot_server( .serve(app.into_make_service()) .await?) } + +#[cfg(test)] +mod test { + use std::{fs, net::SocketAddr}; + + use anyhow::Result; + use axum::{extract::Query, routing::get, Json, Router}; + use serde::Deserialize; + use tempfile::NamedTempFile; + use tokio::sync::OnceCell; + + use crate::{ + client::{ + CachingStatus, CachingStatusResponse, Membership, Role, Team, TeamsResponse, User, + UserResponse, + }, + commands::{login, CommandBase}, + config::{RepoConfigLoader, UserConfigLoader}, + ui::UI, + Args, + }; + + #[tokio::test] + async fn test_login() { + let user_config_file = NamedTempFile::new().unwrap(); + fs::write(user_config_file.path(), r#"{ "token": "hello" }"#).unwrap(); + let repo_config_file = NamedTempFile::new().unwrap(); + fs::write( + repo_config_file.path(), + r#"{ "apiurl": "http://localhost:3000" }"#, + ) + .unwrap(); + + tokio::spawn(start_test_server()); + let mut base = CommandBase { + repo_root: Default::default(), + ui: UI::new(false), + user_config: OnceCell::from( + UserConfigLoader::new(user_config_file.path().to_path_buf()) + .load() + .unwrap(), + ), + repo_config: OnceCell::from( + RepoConfigLoader::new(repo_config_file.path().to_path_buf()) + .with_api(Some("http://localhost:3000".to_string())) + .with_login(Some("http://localhost:3000".to_string())) + .load() + .unwrap(), + ), + args: Args::default(), + }; + + login::login(&mut base).await.unwrap(); + + assert_eq!(base.user_config().unwrap().token().unwrap(), "test-token"); + } + + #[derive(Debug, Clone, Deserialize)] + struct TokenRequest { + redirect_uri: String, + } + + const EXPECTED_TOKEN: &str = "expected_token"; + + async fn start_test_server() -> Result<()> { + let app = Router::new() + // `GET /` goes to `root` + .route( + "/v2/teams", + get(|| async move { + Json(TeamsResponse { + teams: vec![Team { + id: "vercel".to_string(), + slug: "vercel".to_string(), + name: "vercel".to_string(), + created_at: 0, + created: Default::default(), + membership: Membership::new(Role::Owner), + }], + }) + }), + ) + .route( + "/v2/user", + get(|| async move { + Json(UserResponse { + user: User { + id: "my_user_id".to_string(), + username: "my_username".to_string(), + email: "my_email".to_string(), + name: None, + created_at: 0, + }, + }) + }), + ) + .route( + "/v8/artifacts/status", + get(|| async { + Json(CachingStatusResponse { + status: CachingStatus::Enabled, + }) + }), + ); + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + + Ok(axum_server::bind(addr) + .serve(app.into_make_service()) + .await?) + } +} From 9e93e98537083e36aaffe122d981d2e4ecf3d87e Mon Sep 17 00:00:00 2001 From: Nicholas Yang Date: Wed, 1 Feb 2023 13:24:52 -0500 Subject: [PATCH 2/5] Removing warnings --- crates/turborepo-lib/src/commands/login.rs | 57 ++++++++-------------- 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/crates/turborepo-lib/src/commands/login.rs b/crates/turborepo-lib/src/commands/login.rs index 6530bb490a632..5c9565260db12 100644 --- a/crates/turborepo-lib/src/commands/login.rs +++ b/crates/turborepo-lib/src/commands/login.rs @@ -1,8 +1,11 @@ -use std::{net::SocketAddr, sync::Arc}; +#[cfg(not(test))] +use std::net::SocketAddr; +use std::sync::Arc; use anyhow::{anyhow, Result}; +#[cfg(not(test))] use axum::{extract::Query, response::Redirect, routing::get, Router}; -use log::debug; +use log::{debug, warn}; use serde::Deserialize; use tokio::sync::OnceCell; @@ -13,6 +16,9 @@ use crate::{ ui::{start_spinner, BOLD, CYAN}, }; +#[cfg(test)] +pub const EXPECTED_TOKEN_TEST: &str = "expected_token"; + const DEFAULT_HOST_NAME: &str = "127.0.0.1"; const DEFAULT_PORT: u16 = 9789; @@ -26,7 +32,7 @@ pub async fn login(base: &mut CommandBase) -> Result<()> { let redirect_url = format!("http://{DEFAULT_HOST_NAME}:{DEFAULT_PORT}"); let login_url = format!("{login_url_base}/turborepo/token?redirect_uri={redirect_url}"); println!(">>> Opening browser to {login_url}"); - direct_user_to_url(&login_url).await; + direct_user_to_url(&login_url); let spinner = start_spinner("Waiting for your authorization..."); let token_cell = Arc::new(OnceCell::new()); new_one_shot_server( @@ -68,7 +74,7 @@ pub async fn login(base: &mut CommandBase) -> Result<()> { } #[cfg(test)] -async fn direct_user_to_url(url: &str) {} +async fn direct_user_to_url(_: &str) {} #[cfg(not(test))] fn direct_user_to_url(url: &str) { if webbrowser::open(url).is_err() { @@ -78,12 +84,13 @@ fn direct_user_to_url(url: &str) { #[derive(Debug, Clone, Deserialize)] struct LoginPayload { + #[cfg(not(test))] token: String, } #[cfg(test)] async fn new_one_shot_server(_: u16, _: String, login_token: Arc>) -> Result<()> { - login_token.set("test-token".to_string()).unwrap(); + login_token.set(EXPECTED_TOKEN_TEST.to_string()).unwrap(); Ok(()) } @@ -118,17 +125,14 @@ mod test { use std::{fs, net::SocketAddr}; use anyhow::Result; - use axum::{extract::Query, routing::get, Json, Router}; + use axum::{routing::get, Json, Router}; use serde::Deserialize; use tempfile::NamedTempFile; use tokio::sync::OnceCell; use crate::{ - client::{ - CachingStatus, CachingStatusResponse, Membership, Role, Team, TeamsResponse, User, - UserResponse, - }, - commands::{login, CommandBase}, + client::{User, UserResponse}, + commands::{login, login::EXPECTED_TOKEN_TEST, CommandBase}, config::{RepoConfigLoader, UserConfigLoader}, ui::UI, Args, @@ -166,34 +170,21 @@ mod test { login::login(&mut base).await.unwrap(); - assert_eq!(base.user_config().unwrap().token().unwrap(), "test-token"); + assert_eq!( + base.user_config().unwrap().token().unwrap(), + EXPECTED_TOKEN_TEST + ); } #[derive(Debug, Clone, Deserialize)] struct TokenRequest { + #[cfg(not(test))] redirect_uri: String, } - const EXPECTED_TOKEN: &str = "expected_token"; - async fn start_test_server() -> Result<()> { let app = Router::new() // `GET /` goes to `root` - .route( - "/v2/teams", - get(|| async move { - Json(TeamsResponse { - teams: vec![Team { - id: "vercel".to_string(), - slug: "vercel".to_string(), - name: "vercel".to_string(), - created_at: 0, - created: Default::default(), - membership: Membership::new(Role::Owner), - }], - }) - }), - ) .route( "/v2/user", get(|| async move { @@ -207,14 +198,6 @@ mod test { }, }) }), - ) - .route( - "/v8/artifacts/status", - get(|| async { - Json(CachingStatusResponse { - status: CachingStatus::Enabled, - }) - }), ); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); From ba700f160936f42c89f63624a003b2793ebea1d9 Mon Sep 17 00:00:00 2001 From: Nicholas Yang Date: Mon, 6 Feb 2023 17:19:45 -0500 Subject: [PATCH 3/5] Fixed bug with running link and login tests --- crates/turborepo-lib/src/client.rs | 3 ++- crates/turborepo-lib/src/commands/link.rs | 6 ++++-- crates/turborepo-lib/src/commands/login.rs | 17 +++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/turborepo-lib/src/client.rs b/crates/turborepo-lib/src/client.rs index dc8ba6cdd3471..a378819d0c901 100644 --- a/crates/turborepo-lib/src/client.rs +++ b/crates/turborepo-lib/src/client.rs @@ -106,9 +106,10 @@ impl UserClient for APIClient { async fn get_user(&self) -> Result { let response = self .make_retryable_request(|| { + let url = self.make_url("/v2/user"); let request_builder = self .client - .get(self.make_url("/v2/user")) + .get(url) .header("User-Agent", USER_AGENT.clone()) .header("Authorization", format!("Bearer {}", self.token)) .header("Content-Type", "application/json"); diff --git a/crates/turborepo-lib/src/commands/link.rs b/crates/turborepo-lib/src/commands/link.rs index 7cbc6066615d5..317e6d3c40844 100644 --- a/crates/turborepo-lib/src/commands/link.rs +++ b/crates/turborepo-lib/src/commands/link.rs @@ -268,7 +268,7 @@ mod test { ) .unwrap(); - tokio::spawn(start_test_server()); + let handle = tokio::spawn(start_test_server()); let mut base = CommandBase { repo_root: Default::default(), ui: UI::new(false), @@ -291,7 +291,9 @@ mod test { link::link(&mut base, false).await.unwrap(); let team_id = base.repo_config().unwrap().team_id(); - assert!(team_id == Some(TEAM_ID) || team_id == Some(USER_ID)) + assert!(team_id == Some(TEAM_ID) || team_id == Some(USER_ID)); + + handle.abort(); } async fn start_test_server() -> Result<()> { diff --git a/crates/turborepo-lib/src/commands/login.rs b/crates/turborepo-lib/src/commands/login.rs index 5c9565260db12..bf12083a35ac3 100644 --- a/crates/turborepo-lib/src/commands/login.rs +++ b/crates/turborepo-lib/src/commands/login.rs @@ -5,7 +5,9 @@ use std::sync::Arc; use anyhow::{anyhow, Result}; #[cfg(not(test))] use axum::{extract::Query, response::Redirect, routing::get, Router}; -use log::{debug, warn}; +use log::debug; +#[cfg(not(test))] +use log::warn; use serde::Deserialize; use tokio::sync::OnceCell; @@ -48,10 +50,8 @@ pub async fn login(base: &mut CommandBase) -> Result<()> { .ok_or_else(|| anyhow!("Failed to get token"))?; base.user_config_mut()?.set_token(Some(token.to_string()))?; - let client = base.api_client()?.unwrap(); let user_response = client.get_user().await?; - let ui = &base.ui; println!( @@ -74,7 +74,7 @@ pub async fn login(base: &mut CommandBase) -> Result<()> { } #[cfg(test)] -async fn direct_user_to_url(_: &str) {} +fn direct_user_to_url(_: &str) {} #[cfg(not(test))] fn direct_user_to_url(url: &str) { if webbrowser::open(url).is_err() { @@ -149,7 +149,7 @@ mod test { ) .unwrap(); - tokio::spawn(start_test_server()); + let handle = tokio::spawn(start_test_server()); let mut base = CommandBase { repo_root: Default::default(), ui: UI::new(false), @@ -160,8 +160,7 @@ mod test { ), repo_config: OnceCell::from( RepoConfigLoader::new(repo_config_file.path().to_path_buf()) - .with_api(Some("http://localhost:3000".to_string())) - .with_login(Some("http://localhost:3000".to_string())) + .with_api(Some("http://localhost:3001".to_string())) .load() .unwrap(), ), @@ -174,6 +173,8 @@ mod test { base.user_config().unwrap().token().unwrap(), EXPECTED_TOKEN_TEST ); + + handle.abort(); } #[derive(Debug, Clone, Deserialize)] @@ -199,7 +200,7 @@ mod test { }) }), ); - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let addr = SocketAddr::from(([127, 0, 0, 1], 3001)); Ok(axum_server::bind(addr) .serve(app.into_make_service()) From 4b3a9474fb0545e7b2ae2298967e2fb49108426a Mon Sep 17 00:00:00 2001 From: Nicholas Yang Date: Mon, 6 Feb 2023 17:21:07 -0500 Subject: [PATCH 4/5] Added note --- crates/turborepo-lib/src/commands/login.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/turborepo-lib/src/commands/login.rs b/crates/turborepo-lib/src/commands/login.rs index bf12083a35ac3..8091099cc4346 100644 --- a/crates/turborepo-lib/src/commands/login.rs +++ b/crates/turborepo-lib/src/commands/login.rs @@ -183,6 +183,8 @@ mod test { redirect_uri: String, } + /// NOTE: Each test server should be on its own port to avoid any + /// concurrency bugs. async fn start_test_server() -> Result<()> { let app = Router::new() // `GET /` goes to `root` From 12bf41c17051edd1b866800c70eb74592e0ffc42 Mon Sep 17 00:00:00 2001 From: Nicholas Yang Date: Wed, 8 Feb 2023 12:04:38 -0500 Subject: [PATCH 5/5] PR feedback --- crates/turborepo-lib/src/commands/link.rs | 3 +-- crates/turborepo-lib/src/commands/login.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/turborepo-lib/src/commands/link.rs b/crates/turborepo-lib/src/commands/link.rs index 317e6d3c40844..f688333d1032b 100644 --- a/crates/turborepo-lib/src/commands/link.rs +++ b/crates/turborepo-lib/src/commands/link.rs @@ -290,10 +290,9 @@ mod test { link::link(&mut base, false).await.unwrap(); + handle.abort(); let team_id = base.repo_config().unwrap().team_id(); assert!(team_id == Some(TEAM_ID) || team_id == Some(USER_ID)); - - handle.abort(); } async fn start_test_server() -> Result<()> { diff --git a/crates/turborepo-lib/src/commands/login.rs b/crates/turborepo-lib/src/commands/login.rs index 8091099cc4346..a28d4a2392f3a 100644 --- a/crates/turborepo-lib/src/commands/login.rs +++ b/crates/turborepo-lib/src/commands/login.rs @@ -169,12 +169,12 @@ mod test { login::login(&mut base).await.unwrap(); + handle.abort(); + assert_eq!( base.user_config().unwrap().token().unwrap(), EXPECTED_TOKEN_TEST ); - - handle.abort(); } #[derive(Debug, Clone, Deserialize)]