use assert_cmd::Command;
use random_string::generate;
use std::env::{self, VarError};
use std::error::Error;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::time::Duration;
use tembo::cli::context::{
get_current_context, tembo_context_file_path, tembo_credentials_file_path, Environment,
CONTEXT_EXAMPLE_TEXT, CREDENTIALS_EXAMPLE_TEXT,
};
use tembo_api_client::apis::configuration::Configuration;
use tembo_api_client::apis::instance_api::get_all;
use tembo_api_client::models::{Instance, State};
const CARGO_BIN: &str = "tembo";
#[tokio::test]
async fn minimal_cloud() -> Result<(), Box<dyn Error>> {
let root_dir = env!("CARGO_MANIFEST_DIR");
let test_dir = PathBuf::from(root_dir).join("examples").join("minimal");
env::set_current_dir(&test_dir)?;
replace_file(tembo_context_file_path(), CONTEXT_EXAMPLE_TEXT)?;
replace_file(tembo_credentials_file_path(), CREDENTIALS_EXAMPLE_TEXT)?;
let mut cmd = Command::cargo_bin(CARGO_BIN)?;
cmd.arg("init");
cmd.assert().success();
let charset = "abcdefghijklmnopqrstuvwxyz";
let instance_name = format!("e2e-cli-{}", generate(10, charset));
setup_env(&instance_name)?;
let mut cmd = Command::cargo_bin(CARGO_BIN)?;
cmd.arg("context");
cmd.arg("set");
cmd.arg("--name");
cmd.arg("prod");
cmd.assert().success();
let mut cmd = Command::cargo_bin(CARGO_BIN).unwrap();
cmd.arg("apply");
let output = cmd.output()?;
assert!(output.status.success(), "`tembo apply` did not succeed");
if output
.stdout
.windows(b"Error creating instance".len())
.any(|window| window == b"Error creating instance")
{
println!("Output: {}", String::from_utf8_lossy(&output.stdout));
panic!("Error: Instance creation failed");
}
let env = get_current_context()?;
let profile = env.clone().selected_profile.unwrap();
let config = Configuration {
base_path: profile.get_tembo_host(),
bearer_access_token: Some(profile.tembo_access_token),
..Default::default()
};
for attempt in 1..=5 {
let maybe_instance = get_instance(&instance_name, &config, &env).await?;
if let Some(instance) = maybe_instance {
println!("Instance is {:?}", instance.state);
if instance.state == State::Up {
break;
}
if attempt == 5 {
assert_eq!(instance.state, State::Up, "Instance isn't Up")
}
} else if attempt == 5 {
panic!("Failed to create instance");
}
tokio::time::sleep(Duration::from_secs(30)).await;
}
let mut cmd = Command::cargo_bin(CARGO_BIN)?;
cmd.arg("delete");
let _ = cmd.ok();
tokio::time::sleep(Duration::from_secs(10)).await;
let maybe_instance = get_instance(&instance_name, &config, &env).await?;
if let Some(instance) = maybe_instance {
assert_eq!(instance.state, State::Deleting, "Instance isn't Deleting")
}
replace_vars_in_file(
"tembo.toml".to_string(),
&format!("instance_name = \"{instance_name}\""),
"instance_name = \"minimal\"",
)?;
Ok(())
}
fn get_var(name: &str, default: &str) -> Result<String, Box<dyn Error>> {
match env::var(name) {
Ok(str) => Ok(str),
Err(e) => match e {
VarError::NotUnicode(_) => Err(format!("Invalid environment variable {name}").into()),
VarError::NotPresent => match default {
"" => Err(format!("Missing environment variable {name}").into()),
s => Ok(s.to_string()),
},
},
}
}
fn setup_env(instance_name: &String) -> Result<(), Box<dyn Error>> {
replace_vars_in_file(
tembo_context_file_path(),
"ORG_ID",
&get_var("ORG_ID", "org_2YW4TYIMI1LeOqJTXIyvkHOHCUo")?,
)?;
replace_vars_in_file(
tembo_credentials_file_path(),
"ACCESS_TOKEN",
&get_var("ACCESS_TOKEN", "")?,
)?;
replace_vars_in_file(
tembo_credentials_file_path(),
"https://api.tembo.io",
&get_var("TEMBO_HOST", "https://api.cdb-dev.com")?,
)?;
replace_vars_in_file(
tembo_credentials_file_path(),
"https://api.data-1.use1.tembo.io",
&get_var("TEMBO_DATA_HOST", "https://api.data-1.use1.cdb-dev.com")?,
)?;
replace_vars_in_file(
"tembo.toml".to_string(),
"instance_name = \"minimal\"",
&format!("instance_name = \"{instance_name}\""),
)?;
replace_vars_in_file(
"tembo.toml".to_string(),
"[minimal]",
&format!("[{instance_name}]"),
)?;
Ok(())
}
fn replace_vars_in_file(
file_path: String,
word_from: &str,
word_to: &str,
) -> Result<(), Box<dyn Error>> {
let mut src = File::open(&file_path)?;
let mut data = String::new();
src.read_to_string(&mut data)?;
drop(src);
let new_data = data.replace(word_from, word_to);
let mut dst = File::create(&file_path)?;
dst.write_all(new_data.as_bytes())?;
drop(dst);
Ok(())
}
fn replace_file(file_path: String, word_to: &str) -> Result<(), Box<dyn Error>> {
let mut dst = File::create(&file_path)?;
dst.write_all(word_to.as_bytes())?;
drop(dst);
Ok(())
}
pub async fn get_instance(
instance_name: &str,
config: &Configuration,
env: &Environment,
) -> Result<Option<Instance>, anyhow::Error> {
let v = get_all(config, env.org_id.clone().unwrap().as_str()).await;
println!("OrgID: {}", env.org_id.clone().unwrap().as_str());
match v {
Ok(result) => {
let maybe_instance = result
.iter()
.find(|instance| instance.instance_name == instance_name);
if let Some(instance) = maybe_instance {
return Ok(Some(instance.clone()));
}
}
Err(error) => eprintln!("Error getting instance: {}", error),
};
Ok(None)
}