test: integration test to tuono_lib (#425)

This commit is contained in:
Valerio Ageno
2025-01-28 19:08:18 +01:00
committed by GitHub
parent 04d6fcc1e1
commit 0ab9c297a5
12 changed files with 348 additions and 27 deletions
+3 -2
View File
@@ -35,7 +35,7 @@ pub const AXUM_ENTRY_POINT: &str = r##"
// File automatically generated
// Do not manually change it
use tuono_lib::{tokio, Mode, Server, axum::Router};
use tuono_lib::{tokio, Mode, Server, axum::Router, tuono_internal_init_v8_platform};
// AXUM_GET_ROUTE_HANDLER
const MODE: Mode = /*MODE*/;
@@ -46,6 +46,7 @@ const MODE: Mode = /*MODE*/;
#[tokio::main]
async fn main() {
tuono_internal_init_v8_platform();
println!("\n ⚡ Tuono v/*VERSION*/");
//MAIN_FILE_DEFINITION//
@@ -54,7 +55,7 @@ async fn main() {
// ROUTE_BUILDER
//MAIN_FILE_USAGE//;
Server::init(router, MODE).start().await
Server::init(router, MODE).await.start().await
}
"##;
+4
View File
@@ -42,3 +42,7 @@ http = "1.1.0"
pin-project = "1.1.7"
tower = "0.5.1"
[dev-dependencies]
fs_extra = "1.3.0"
tempfile = "3.14.0"
serial_test = "0.10.0"
+2 -1
View File
@@ -20,10 +20,11 @@ pub use mode::Mode;
pub use payload::Payload;
pub use request::Request;
pub use response::{Props, Response};
pub use server::Server;
pub use server::{tuono_internal_init_v8_platform, Server};
pub use tuono_lib_macros::{api, handler};
// Re-exports
pub use axum;
pub use axum_extra::extract::cookie;
pub use ssr_rs::Ssr;
pub use tokio;
+1 -1
View File
@@ -29,7 +29,7 @@ pub fn load_manifest() {
let file = File::open(PathBuf::from(VITE_MANIFEST_PATH)).unwrap();
let reader = BufReader::new(file);
let manifest = serde_json::from_reader(reader).unwrap();
MANIFEST.set(remap_manifest_keys(manifest)).unwrap();
let _ = MANIFEST.set(remap_manifest_keys(manifest));
}
fn remap_manifest_keys(manifest: HashMap<String, BundleInfo>) -> HashMap<String, BundleInfo> {
+26 -23
View File
@@ -15,41 +15,44 @@ use crate::{
const DEV_PUBLIC_DIR: &str = "public";
const PROD_PUBLIC_DIR: &str = "out/client";
pub fn tuono_internal_init_v8_platform() {
Ssr::create_platform();
}
#[derive(Debug)]
pub struct Server {
router: Router,
mode: Mode,
pub listener: tokio::net::TcpListener,
pub address: String,
}
impl Server {
pub fn init(router: Router, mode: Mode) -> Server {
Ssr::create_platform();
pub async fn init(router: Router, mode: Mode) -> Server {
let config = Config::get().expect("[SERVER] Failed to load config");
GLOBAL_MODE.set(mode).unwrap();
GLOBAL_CONFIG
.set(Config::get().expect("[SERVER] Failed to load config"))
.unwrap();
let _ = GLOBAL_MODE.set(mode);
let _ = GLOBAL_CONFIG.set(config.clone());
if mode == Mode::Prod {
load_manifest()
}
Server { router, mode }
let server_http_address = format!("{}:{}", config.server.host, config.server.port);
Server {
router,
mode,
address: server_http_address.clone(),
listener: tokio::net::TcpListener::bind(&server_http_address)
.await
.expect("[SERVER] Failed to bind to address"),
}
}
pub async fn start(&self) {
let config = GLOBAL_CONFIG
.get()
.expect("Failed to get the internal config");
let server_http_address = format!("{}:{}", config.server.host, config.server.port);
let listener = tokio::net::TcpListener::bind(&server_http_address)
.await
.unwrap();
let server_url = format!("http://{}", &server_http_address);
pub async fn start(self) {
if self.mode == Mode::Dev {
println!(" Ready at: {}\n", &server_url.blue().bold());
println!(" Ready at: {}\n", self.address.blue().bold());
let router = self
.router
.to_owned()
@@ -61,11 +64,11 @@ impl Server {
.fallback(get(catch_all).layer(LoggerLayer::new())),
);
axum::serve(listener, router)
axum::serve(self.listener, router)
.await
.expect("Failed to serve development server");
} else {
println!(" Production server at: {}\n", &server_url.blue().bold());
println!(" Production server at: {}\n", self.address.blue().bold());
let router = self
.router
.to_owned()
@@ -75,7 +78,7 @@ impl Server {
.fallback(get(catch_all).layer(LoggerLayer::new())),
);
axum::serve(listener, router)
axum::serve(self.listener, router)
.await
.expect("Failed to serve production server");
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
pub mod utils;
+105
View File
@@ -0,0 +1,105 @@
mod utils;
use crate::utils::utils::MockTuonoServer;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn api_endpoint_work() {
let app = MockTuonoServer::spawn().await;
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
let server_url = format!("http://{}:{}", &app.address, &app.port);
let response = client
.get(&format!("{server_url}/health_check"))
.send()
.await
.expect("Failed to execute request.");
// Assert
assert!(response.status().is_success());
assert_eq!(Some(0), response.content_length());
}
#[tokio::test]
#[serial]
async fn not_found_route() {
let app = MockTuonoServer::spawn().await;
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
let server_url = format!("http://{}:{}", &app.address, &app.port);
let response = client
.get(&format!("{server_url}/not-found"))
.send()
.await
.expect("Failed to execute request.");
// TODO: This should return a 404 status code
assert!(response.status().is_success());
assert_eq!(
response.text().await.unwrap(),
"<h1>404 Not found</h1><a href=\"/\">Return home</a>"
);
}
#[tokio::test]
#[serial]
async fn index_html_route() {
let app = MockTuonoServer::spawn().await;
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
let server_url = format!("http://{}:{}", &app.address, &app.port);
let response = client
.get(&format!("{server_url}/"))
.send()
.await
.expect("Failed to execute request.");
// TODO: This should return a 404 status code
assert!(response.status().is_success());
assert!(response
.text()
.await
.unwrap()
.starts_with("<!DOCTYPE html>"));
}
#[tokio::test]
#[serial]
async fn api_route_route() {
let app = MockTuonoServer::spawn().await;
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
let server_url = format!("http://{}:{}", &app.address, &app.port);
let response = client
.get(&format!("{server_url}/tuono/data"))
.send()
.await
.expect("Failed to execute request.");
// TODO: This should return a 404 status code
assert!(response.status().is_success());
assert_eq!(
response.text().await.unwrap(),
"{\"data\":\"{}\",\"info\":{\"redirect_destination\":null}}"
);
}
@@ -0,0 +1,7 @@
use tuono_lib::axum::http::StatusCode;
use tuono_lib::Request;
#[tuono_lib::api(GET)]
async fn health_check(_req: Request) -> StatusCode {
StatusCode::OK
}
+3
View File
@@ -0,0 +1,3 @@
pub mod health_check;
pub mod route;
pub mod utils;
+6
View File
@@ -0,0 +1,6 @@
use tuono_lib::{Props, Request, Response};
#[tuono_lib::handler]
async fn route(_: Request) -> Response {
Response::Props(Props::new("{}"))
}
+106
View File
@@ -0,0 +1,106 @@
use fs_extra::dir::create_all;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::{env, fs};
use tempfile::{tempdir, TempDir};
use tuono_lib::axum::routing::get;
use tuono_lib::{axum::Router, tuono_internal_init_v8_platform, Mode, Server};
use crate::utils::health_check::get__tuono_internal_api as health_check;
use crate::utils::route as html_route;
use crate::utils::route::tuono__internal__api as route_api;
use std::sync::Once;
static INIT: Once = Once::new();
fn init_v8() {
INIT.call_once(|| {
tuono_internal_init_v8_platform();
})
}
fn add_file_with_content<'a>(path: &'a str, content: &'a str) {
let path = PathBuf::from(path);
create_all(
path.parent().expect("File path does not have any parent"),
false,
)
.expect(
format!(
"Failed to create parent file directories: {}",
path.display()
)
.as_str(),
);
let mut file = File::create(path).expect("Failed to create the file");
file.write_all(content.as_bytes())
.expect("Failed to write into the file");
}
#[derive(Debug)]
pub struct MockTuonoServer {
pub address: String,
pub port: u16,
original_dir: PathBuf,
#[allow(dead_code)]
// Required for dropping the temp_dir when this struct drops
temp_dir: TempDir,
}
impl MockTuonoServer {
pub async fn spawn() -> Self {
init_v8();
let original_dir = env::current_dir().expect("Failed to read current_dir");
let temp_dir = tempdir().expect("Failed to create temp_dir");
let react_prod_build = fs::read_to_string("./tests/assets/fake_react_build.js")
.expect("Failed to read fake_react_build.js");
env::set_current_dir(temp_dir.path()).expect("Failed to change current dir into temp_dir");
add_file_with_content(
"./.tuono/config/config.json",
r#"{"server": {"host": "127.0.0.1", "port": 0}}"#,
);
add_file_with_content("./out/server/prod-server.js", react_prod_build.as_str());
add_file_with_content(
"./out/client/.vite/manifest.json",
r#"{"client-main.tsx": { "file": "assets/index.js", "name": "index", "src": "index.tsx", "isEntry": true,"dynamicImports": [],"css": []}}"#,
);
let router = Router::new()
.route("/", get(html_route::tuono__internal__route))
.route("/tuono/data", get(html_route::tuono__internal__api))
.route("/health_check", get(health_check))
.route("/route-api", get(route_api));
let server = Server::init(router, Mode::Prod).await;
let socket = server
.listener
.local_addr()
.expect("Failed to extract test server socket");
let _ = tokio::spawn(server.start());
MockTuonoServer {
address: socket.ip().to_string(),
port: socket.port(),
original_dir,
temp_dir,
}
}
}
impl Drop for MockTuonoServer {
fn drop(&mut self) {
// Set back the current dir in the previous state
env::set_current_dir(self.original_dir.to_owned())
.expect("Failed to restore the original directory.");
}
}