Skip to content

Commit

Permalink
test(turborepo): Login (#3663)
Browse files Browse the repository at this point in the history
Adds a test for login. A little messy with the `cfgs`. Perhaps at some
point we can abstract out the `dialoguer` terminal interactions into a
module, then stub it more cleanly.
  • Loading branch information
NicholasLYang committed Feb 8, 2023
1 parent a6aca5f commit d8172aa
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 13 deletions.
4 changes: 2 additions & 2 deletions crates/turborepo-lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,9 @@ pub async fn run(repo_state: Option<RepoState>) -> Result<Payload> {
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)))
}
Expand Down
3 changes: 2 additions & 1 deletion crates/turborepo-lib/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ impl UserClient for APIClient {
async fn get_user(&self) -> Result<UserResponse> {
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");
Expand Down
5 changes: 3 additions & 2 deletions crates/turborepo-lib/src/commands/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -290,8 +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))
assert!(team_id == Some(TEAM_ID) || team_id == Some(USER_ID));
}

async fn start_test_server() -> Result<()> {
Expand Down
123 changes: 115 additions & 8 deletions crates/turborepo-lib/src/commands/login.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
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, warn};
use log::debug;
#[cfg(not(test))]
use log::warn;
use serde::Deserialize;
use tokio::sync::OnceCell;

Expand All @@ -13,10 +18,13 @@ 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;

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());
Expand All @@ -26,27 +34,25 @@ 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}");
let spinner = start_spinner("Waiting for your authorization...");
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(
DEFAULT_PORT,
repo_config.login_url().to_string(),
token_cell.clone(),
)
.await?;

spinner.finish_and_clear();
let token = token_cell
.get()
.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;
let ui = &base.ui;

println!(
"
Expand All @@ -67,6 +73,9 @@ pub async fn login(mut base: CommandBase) -> Result<()> {
Ok(())
}

#[cfg(test)]
fn direct_user_to_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.");
Expand All @@ -75,9 +84,17 @@ 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<OnceCell<String>>) -> Result<()> {
login_token.set(EXPECTED_TOKEN_TEST.to_string()).unwrap();
Ok(())
}

#[cfg(not(test))]
async fn new_one_shot_server(
port: u16,
login_url_base: String,
Expand All @@ -102,3 +119,93 @@ 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::{routing::get, Json, Router};
use serde::Deserialize;
use tempfile::NamedTempFile;
use tokio::sync::OnceCell;

use crate::{
client::{User, UserResponse},
commands::{login, login::EXPECTED_TOKEN_TEST, 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();

let handle = 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:3001".to_string()))
.load()
.unwrap(),
),
args: Args::default(),
};

login::login(&mut base).await.unwrap();

handle.abort();

assert_eq!(
base.user_config().unwrap().token().unwrap(),
EXPECTED_TOKEN_TEST
);
}

#[derive(Debug, Clone, Deserialize)]
struct TokenRequest {
#[cfg(not(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`
.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,
},
})
}),
);
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));

Ok(axum_server::bind(addr)
.serve(app.into_make_service())
.await?)
}
}

0 comments on commit d8172aa

Please sign in to comment.