mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 12:52:47 -07:00
test: integration test to tuono_lib (#425)
This commit is contained in:
@@ -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
|
||||
}
|
||||
"##;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
pub mod utils;
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod health_check;
|
||||
pub mod route;
|
||||
pub mod utils;
|
||||
@@ -0,0 +1,6 @@
|
||||
use tuono_lib::{Props, Request, Response};
|
||||
|
||||
#[tuono_lib::handler]
|
||||
async fn route(_: Request) -> Response {
|
||||
Response::Props(Props::new("{}"))
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user