test: add full integration test for tuono new CLI command (#411)

Co-authored-by: Valerio Ageno <valerioageno@yahoo.it>
This commit is contained in:
Robert Lawson
2025-02-23 11:06:06 +01:00
committed by GitHub
parent 0a4d1e2995
commit ca4a7b3466
8 changed files with 211 additions and 29 deletions
+2
View File
@@ -37,6 +37,8 @@ spinners = "4.1.1"
console = "0.15.10"
[dev-dependencies]
wiremock = "0.6.2"
tempfile = "3.14.0"
assert_cmd = "2.0.16"
serial_test = "0.10.0"
+41 -23
View File
@@ -7,13 +7,6 @@ use std::fs::{self, create_dir, File, OpenOptions};
use std::io::{self, prelude::*};
use std::path::{Path, PathBuf};
const GITHUB_TUONO_TAGS_URL: &str = "https://api.github.com/repos/tuono-labs/tuono/git/ref/tags/";
const GITHUB_TUONO_TAG_COMMIT_TREES_URL: &str =
"https://api.github.com/repos/tuono-labs/tuono/git/trees/";
const GITHUB_RAW_CONTENT_URL: &str = "https://raw.githubusercontent.com/tuono-labs/tuono";
#[derive(Deserialize, Debug)]
enum GithubFileType {
#[serde(rename = "blob")]
@@ -69,6 +62,12 @@ pub fn create_new_project(
) {
let folder = folder_name.unwrap_or(".".to_string());
let github_api_base_url =
env::var("__INTERNAL_TUONO_TEST").unwrap_or("https://api.github.com".to_string());
let github_raw_base_url = env::var("__INTERNAL_TUONO_TEST")
.unwrap_or("https://raw.githubusercontent.com".to_string());
// In case of missing select the tuono example
let template = template.unwrap_or("tuono-app".to_string());
let client = blocking::Client::builder()
@@ -79,7 +78,8 @@ pub fn create_new_project(
// This string does not include the "v" version prefix
let cli_version: &str = crate_version!();
let tree_url: String = generate_tree_url(select_head, &client, cli_version);
let tree_url: String =
generate_tree_url(select_head, &client, cli_version, &github_api_base_url);
let res_tree = client
.get(tree_url)
@@ -126,7 +126,8 @@ pub fn create_new_project(
} in new_project_files.iter()
{
if let GithubFileType::Blob = element_type {
let url = generate_raw_content_url(select_head, cli_version, path);
let url =
generate_raw_content_url(select_head, cli_version, path, &github_raw_base_url);
let file_content = client
.get(url)
@@ -144,7 +145,7 @@ pub fn create_new_project(
let file_path = folder_path.join(&path);
if let Err(err) = create_file(file_path, file_content) {
exit_with_error(&format!("Failed to create file: {}", err));
exit_with_error(&format!("Failed to create file: {err}"));
}
}
}
@@ -154,22 +155,34 @@ pub fn create_new_project(
outro(folder);
}
fn generate_raw_content_url(select_head: Option<bool>, cli_version: &str, path: &String) -> String {
fn generate_raw_content_url(
select_head: Option<bool>,
cli_version: &str,
path: &String,
url: &str,
) -> String {
let tag = if select_head.unwrap_or(false) {
"main"
"/main"
} else {
&format!("v{cli_version}")
&format!("/tuono-labs/tuono/v{cli_version}")
};
format!("{}/{}/{}", GITHUB_RAW_CONTENT_URL, tag, path)
format!("{url}{tag}/{path}")
}
fn generate_tree_url(select_head: Option<bool>, client: &Client, cli_version: &str) -> String {
fn generate_tree_url(
select_head: Option<bool>,
client: &Client,
cli_version: &str,
url: &str,
) -> String {
if select_head.unwrap_or(false) {
format!("{}main?recursive=1", GITHUB_TUONO_TAG_COMMIT_TREES_URL)
format!("{url}/repos/tuono-labs/tuono/git/trees/main?recursive=1")
} else {
// This string does not include the "v" version prefix
let res_tag = client
.get(format!("{}v{}", GITHUB_TUONO_TAGS_URL, cli_version))
.get(format!(
"{url}/repos/tuono-labs/tuono/git/ref/tags/v{cli_version}"
))
.send()
.unwrap_or_else(|_| {
exit_with_error("Failed to call the tag github API for v{cli_version}")
@@ -178,8 +191,8 @@ fn generate_tree_url(select_head: Option<bool>, client: &Client, cli_version: &s
.unwrap_or_else(|_| exit_with_error("Failed to parse the tag response"));
format!(
"{}{}?recursive=1",
GITHUB_TUONO_TAG_COMMIT_TREES_URL, res_tag.object.sha
"{url}/repos/tuono-labs/tuono/git/trees/{}?recursive=1",
res_tag.object.sha
)
}
}
@@ -197,7 +210,10 @@ fn create_directories(
let path = PathBuf::from(&path.replace(&format!("examples/{template}/"), ""));
let dir_path = folder_path.join(&path);
create_dir(&dir_path).unwrap();
if let Err(e) = create_dir(&dir_path) {
eprintln!("Failed to create directory {}: {}", dir_path.display(), e);
std::process::exit(1);
}
}
}
Ok(())
@@ -267,12 +283,13 @@ mod tests {
fn generate_valid_content_url_from_head() {
let expected = format!(
"{}/{}/{}",
GITHUB_RAW_CONTENT_URL, "main", "examples/tuono-app"
"http://localhost:3000", "main", "examples/tuono-app"
);
let generated = generate_raw_content_url(
Some(true),
crate_version!(),
&String::from("examples/tuono-app"),
"http://localhost:3000",
);
assert_eq!(expected, generated)
}
@@ -281,14 +298,15 @@ mod tests {
fn generate_valid_content_url_from_cli_version() {
let expected = format!(
"{}/{}/{}",
GITHUB_RAW_CONTENT_URL,
&format!("v{}", crate_version!()),
"http://localhost:3000",
&format!("tuono-labs/tuono/v{}", crate_version!()),
"examples/tuono-app"
);
let generated = generate_raw_content_url(
Some(false),
crate_version!(),
&String::from("examples/tuono-app"),
"http://localhost:3000",
);
assert_eq!(expected, generated)
}
-2
View File
@@ -127,9 +127,7 @@ pub fn bundle_axum_source(mode: Mode) -> io::Result<App> {
let base_path = std::env::current_dir().unwrap();
let app = App::new();
let bundled_file = generate_axum_source(&app, mode);
create_main_file(&base_path, &bundled_file);
Ok(app)
+3 -4
View File
@@ -1,8 +1,9 @@
mod utils;
use assert_cmd::Command;
use serial_test::serial;
use std::fs;
use utils::TempTuonoProject;
mod utils;
use utils::temp_tuono_project::TempTuonoProject;
const POST_API_FILE: &str = r"#[tuono_lib::api(POST)]";
const GET_API_FILE: &str = r"#[tuono_lib::api(GET)]";
@@ -58,8 +59,6 @@ fn it_successfully_create_an_api_route() {
let temp_main_rs_content =
fs::read_to_string(&temp_main_rs_path).expect("Failed to read '.tuono/main.rs' content.");
dbg!(&temp_main_rs_content);
assert!(temp_main_rs_content.contains(r#"#[path="../src/routes/api/health_check.rs"]"#));
assert!(temp_main_rs_content.contains("mod api_health_check;"));
+61
View File
@@ -0,0 +1,61 @@
use assert_cmd::Command;
use clap::crate_version;
use serial_test::serial;
use std::fs;
mod utils;
use utils::mock_github_endpoint::GitHubServerMock;
use utils::temp_tuono_project::TempTuonoProject;
#[tokio::test]
#[serial]
async fn it_creates_a_new_project_and_replace_the_versions_in_the_manifest() {
let GitHubServerMock { env_vars, .. } = GitHubServerMock::new().await;
let temp_project = TempTuonoProject::new();
let project_folder = "my-project";
let mut cmd = Command::cargo_bin("tuono").unwrap();
cmd.arg("new")
.arg(project_folder)
.envs(env_vars)
.assert()
.success();
let main_rs_path = temp_project
.path()
.join(project_folder)
.join("src")
.join("main.rs");
let cargo_toml_path = temp_project.path().join(project_folder).join("Cargo.toml");
let package_json_path = temp_project
.path()
.join(project_folder)
.join("package.json");
let main_rs_content = fs::read_to_string(main_rs_path).unwrap();
let cargo_toml_content = fs::read_to_string(cargo_toml_path).unwrap();
let package_json_content = fs::read_to_string(package_json_path).unwrap();
assert_eq!(
main_rs_content,
"fn main() { println!(\"Hello, world!\"); }"
);
let version = crate_version!();
let expected_cargo_toml_content =
format!("[package] name = \"tuono-tutorial\" [dependencies] tuono_lib = \"{version}\"");
assert_eq!(cargo_toml_content, expected_cargo_toml_content);
let expected_package_json_content =
format!("{{\"name\": \"tuono-app\", \"dependencies\": {{ \"tuono\": \"{version}\" }}}}");
assert_eq!(package_json_content, expected_package_json_content);
}
@@ -0,0 +1,101 @@
use clap::crate_version;
use std::env;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[allow(dead_code)]
pub struct GitHubServerMock {
pub server: MockServer,
pub env_vars: Vec<(String, String)>,
}
#[allow(dead_code)]
impl GitHubServerMock {
pub async fn new() -> Self {
let server = MockServer::start().await;
let tuono_version = crate_version!();
let sha = "1234567890abcdef";
let sha_response_template = ResponseTemplate::new(200).set_body_raw(
format!("{{ \"object\": {{ \"sha\": \"{sha}\" }} }}"),
"application/json",
);
Mock::given(method("GET"))
.and(path(&format!(
"repos/tuono-labs/tuono/git/ref/tags/v{}",
tuono_version
)))
.respond_with(sha_response_template)
.mount(&server)
.await;
let tree_response_template = ResponseTemplate::new(200).set_body_raw(
r#"{
"tree": [
{
"path": "examples/tuono-app/src",
"type": "tree"
},
{
"path": "examples/tuono-app/src/main.rs",
"type": "blob"
},
{
"path": "examples/tuono-app/Cargo.toml",
"type": "blob"
},
{
"path": "examples/tuono-app/package.json",
"type": "blob"
}
]
}"#,
"application/json",
);
Mock::given(method("GET"))
.and(path(&format!("repos/tuono-labs/tuono/git/trees/{}", sha)))
.respond_with(tree_response_template)
.mount(&server)
.await;
let file_response_template =
|content: &str| ResponseTemplate::new(200).set_body_raw(content, "text/plain");
Mock::given(method("GET"))
.and(path(format!(
"tuono-labs/tuono/v{tuono_version}/examples/tuono-app/src/main.rs"
)))
.respond_with(file_response_template(
"fn main() { println!(\"Hello, world!\"); }",
))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path(format!(
"tuono-labs/tuono/v{tuono_version}/examples/tuono-app/Cargo.toml"
)))
.respond_with(file_response_template(
"[package] name = \"tuono-tutorial\" [dependencies] tuono_lib = { path = \"../../crates/tuono_lib/\" }"
))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path(format!(
"tuono-labs/tuono/v{tuono_version}/examples/tuono-app/package.json"
)))
.respond_with(file_response_template(
r#"{"name": "tuono-app", "dependencies": { "tuono": "link:../../packages/tuono" }}"#,
))
.mount(&server)
.await;
let env_vars = vec![("__INTERNAL_TUONO_TEST".to_string(), server.uri())];
GitHubServerMock { server, env_vars }
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod mock_github_endpoint;
pub mod temp_tuono_project;
@@ -11,6 +11,7 @@ pub struct TempTuonoProject {
temp_dir: TempDir,
}
#[allow(dead_code)]
impl TempTuonoProject {
pub fn new() -> Self {
let project = TempTuonoProject::new_with_no_config();