From 82082e127cee0c0f42549ec55bb25210acc1cfaa Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Tue, 17 Sep 2024 12:39:55 +0300 Subject: [PATCH 1/9] add `*.sqlite` to .gitignore starters (#754) --- starters/rest-api/.gitignore | 2 ++ starters/saas/.gitignore | 2 ++ 2 files changed, 4 insertions(+) diff --git a/starters/rest-api/.gitignore b/starters/rest-api/.gitignore index d320a904a..d83d21a83 100644 --- a/starters/rest-api/.gitignore +++ b/starters/rest-api/.gitignore @@ -15,3 +15,5 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +*.sqlite \ No newline at end of file diff --git a/starters/saas/.gitignore b/starters/saas/.gitignore index d320a904a..d83d21a83 100644 --- a/starters/saas/.gitignore +++ b/starters/saas/.gitignore @@ -15,3 +15,5 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +*.sqlite \ No newline at end of file From 5913568c76b249ad93336727c891ac8d08bcc6b0 Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Tue, 17 Sep 2024 15:22:54 +0300 Subject: [PATCH 2/9] run redis connection only when worker is `BackgroundQueue` --- src/boot.rs | 13 +++++++------ src/config.rs | 2 +- src/doctor.rs | 13 +++++++++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/boot.rs b/src/boot.rs index 040accc69..fcf95da75 100644 --- a/src/boot.rs +++ b/src/boot.rs @@ -396,11 +396,12 @@ fn create_mailer(config: &config::Mailer) -> Result> { // TODO: Refactor to eliminate unwrapping and instead return an appropriate // error type. pub async fn connect_redis(config: &Config) -> Option> { - if let Some(redis) = &config.queue { - let manager = RedisConnectionManager::new(redis.uri.clone()).unwrap(); - let redis = Pool::builder().build(manager).await.unwrap(); - Some(redis) - } else { - None + if config.workers.mode == config::WorkerMode::BackgroundQueue { + if let Some(redis) = &config.queue { + let manager = RedisConnectionManager::new(redis.uri.clone()).unwrap(); + let redis = Pool::builder().build(manager).await.unwrap(); + return Some(redis); + } } + None } diff --git a/src/config.rs b/src/config.rs index 53b0feeac..8f5bf4ee2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -354,7 +354,7 @@ pub struct Workers { } /// Worker mode configuration -#[derive(Clone, Default, Serialize, Deserialize, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq, Eq)] pub enum WorkerMode { /// Workers operate asynchronously in the background, processing queued /// tasks. **Requires a Redis connection**. diff --git a/src/doctor.rs b/src/doctor.rs index 758bd57dc..6422d5cdf 100644 --- a/src/doctor.rs +++ b/src/doctor.rs @@ -2,7 +2,7 @@ use std::{collections::BTreeMap, process::Command}; use crate::{ boot, - config::{Config, Database}, + config::{self, Config, Database}, db, redis, Error, Result, }; @@ -89,11 +89,16 @@ impl std::fmt::Display for Check { /// Runs checks for all configured resources. pub async fn run_all(config: &Config) -> BTreeMap { - BTreeMap::from([ + let mut checks = BTreeMap::from([ (Resource::SeaOrmCLI, check_seaorm_cli()), (Resource::Database, check_db(&config.database).await), - (Resource::Redis, check_redis(config).await), - ]) + ]); + + if config.workers.mode == config::WorkerMode::BackgroundQueue { + checks.insert(Resource::Redis, check_redis(config).await); + } + + checks } /// Checks the database connection. From afbe8a9e166ea0f866d962c9be829050bb804e3c Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Tue, 17 Sep 2024 15:27:38 +0300 Subject: [PATCH 3/9] Update CHANGELOG.md --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ced13f7ed..c693a0ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,14 @@ ## Unreleased - -## v0.8.1 -* fix: introduce secondary binary for compile-and-run on Windows. [https://github.com/loco-rs/loco/pull/727](https://github.com/loco-rs/loco/pull/727) * Add fallback behavior. [https://github.com/loco-rs/loco/pull/732](https://github.com/loco-rs/loco/pull/732) * Add Scheduler Feature for Running Cron Jobs. [https://github.com/loco-rs/loco/pull/735](https://github.com/loco-rs/loco/pull/735) * Add `--html`, `--htmx` and `--api` flags to scaffold CLI command. [https://github.com/loco-rs/loco/pull/749](https://github.com/loco-rs/loco/pull/749) * Add base template for scaffold generation. [https://github.com/loco-rs/loco/pull/752](https://github.com/loco-rs/loco/pull/752) +* Connect Redis only when the worker is BackgroundQueue. [https://github.com/loco-rs/loco/pull/755](https://github.com/loco-rs/loco/pull/755) +## v0.8.1 +* fix: introduce secondary binary for compile-and-run on Windows. [https://github.com/loco-rs/loco/pull/727](https://github.com/loco-rs/loco/pull/727) ## v0.8.0 From 214136e9a60828ca56a443e11aa3b8f768f821dc Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Tue, 17 Sep 2024 15:33:50 +0300 Subject: [PATCH 4/9] allow get connect_timeout value fron env var --- starters/rest-api/config/development.yaml | 2 +- starters/rest-api/config/test.yaml | 2 +- starters/saas/config/development.yaml | 2 +- starters/saas/config/test.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/starters/rest-api/config/development.yaml b/starters/rest-api/config/development.yaml index e2205f106..8d40f0fe6 100644 --- a/starters/rest-api/config/development.yaml +++ b/starters/rest-api/config/development.yaml @@ -113,7 +113,7 @@ database: # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. - connect_timeout: 500 + connect_timeout: {{ get_env(name="DB_CONNECT_TIMEOUT", default="500") }} # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. diff --git a/starters/rest-api/config/test.yaml b/starters/rest-api/config/test.yaml index be9449a02..29ab51e67 100644 --- a/starters/rest-api/config/test.yaml +++ b/starters/rest-api/config/test.yaml @@ -94,7 +94,7 @@ database: # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. - connect_timeout: 500 + connect_timeout: {{ get_env(name="DB_CONNECT_TIMEOUT", default="500") }} # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. diff --git a/starters/saas/config/development.yaml b/starters/saas/config/development.yaml index 019ab3d63..790655fb7 100644 --- a/starters/saas/config/development.yaml +++ b/starters/saas/config/development.yaml @@ -171,7 +171,7 @@ database: # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. - connect_timeout: 500 + connect_timeout: {{ get_env(name="DB_CONNECT_TIMEOUT", default="500") }} # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. diff --git a/starters/saas/config/test.yaml b/starters/saas/config/test.yaml index b742cec23..7fd03ad0e 100644 --- a/starters/saas/config/test.yaml +++ b/starters/saas/config/test.yaml @@ -100,7 +100,7 @@ database: # When enabled, the sql query will be logged. enable_logging: false # Set the timeout duration when acquiring a connection. - connect_timeout: 500 + connect_timeout: {{ get_env(name="DB_CONNECT_TIMEOUT", default="500") }} # Set the idle duration before closing a connection. idle_timeout: 500 # Minimum number of connections for a pool. From 77704bdada2d649c8dd57bd962e9d5136f8ddd23 Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Wed, 18 Sep 2024 14:38:14 +0300 Subject: [PATCH 5/9] app_routes tests --- src/controller/app_routes.rs | 107 ++++++++++++++++++ ...troller__app_routes__tests__[[slash]].snap | 5 + ...__app_routes__tests__[[slash]_health].snap | 5 + ...er__app_routes__tests__[[slash]_ping].snap | 5 + ...routes__tests__[[slash]api[slash]bar].snap | 5 + ...routes__tests__[[slash]api[slash]foo].snap | 5 + ...es__tests__[[slash]api[slash]loco-rs].snap | 5 + ...outes__tests__[[slash]api[slash]loco].snap | 5 + ...app_routes__tests__[[slash]multiple1].snap | 5 + ...app_routes__tests__[[slash]multiple2].snap | 5 + ...app_routes__tests__[[slash]multiple3].snap | 5 + ...slash]normalizer[slash]foo[slash]bar].snap | 5 + ...slash]normalizer[slash]loco[slash]rs].snap | 5 + ...[slash]normalizer[slash]multiple-end].snap | 5 + ...lash]normalizer[slash]multiple-start].snap | 5 + ...s__[[slash]normalizer[slash]no-slash].snap | 5 + ...pp_routes__tests__[[slash]normalizer].snap | 5 + src/prelude.rs | 2 +- 18 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_health].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_ping].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]bar].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]foo].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco-rs].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple1].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple2].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple3].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]foo[slash]bar].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]loco[slash]rs].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-end].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-start].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]no-slash].snap create mode 100644 src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer].snap diff --git a/src/controller/app_routes.rs b/src/controller/app_routes.rs index 1d21940aa..2c2e7535c 100644 --- a/src/controller/app_routes.rs +++ b/src/controller/app_routes.rs @@ -521,3 +521,110 @@ fn handle_panic(err: Box) -> axum::response: errors::Error::InternalServerError.into_response() } + +#[cfg(test)] +mod tests { + + use super::*; + use crate::prelude::*; + use crate::tests_cfg; + use insta::assert_debug_snapshot; + use rstest::rstest; + use tower::ServiceExt; + + async fn action() -> Result { + format::json("Hello, World!") + } + + #[test] + fn can_load_app_route_from_default() { + for route in AppRoutes::with_default_routes().collect() { + assert_debug_snapshot!( + format!("[{}]", route.uri.replace('/', "[slash]")), + format!("{:?} {}", route.actions, route.uri) + ); + } + } + + #[test] + fn can_load_empty_app_routes() { + assert_eq!(AppRoutes::empty().collect().len(), 0); + } + + #[test] + fn can_load_routes() { + let router_without_prefix = Routes::new().add("/", get(action)); + let normalizer = Routes::new() + .prefix("/normalizer") + .add("no-slash", get(action)) + .add("/", post(action)) + .add("//loco///rs//", delete(action)) + .add("//////multiple-start", head(action)) + .add("multiple-end/////", trace(action)); + + let app_router = AppRoutes::empty() + .add_route(router_without_prefix) + .add_route(normalizer) + .add_routes(vec![ + Routes::new().add("multiple1", put(action)), + Routes::new().add("multiple2", options(action)), + Routes::new().add("multiple3", patch(action)), + ]); + + for route in app_router.collect() { + assert_debug_snapshot!( + format!("[{}]", route.uri.replace('/', "[slash]")), + format!("{:?} {}", route.actions, route.uri) + ); + } + } + + #[test] + fn can_load_routes_with_root_prefix() { + let router_without_prefix = Routes::new() + .add("/loco", get(action)) + .add("loco-rs", get(action)); + + let app_router = AppRoutes::empty() + .prefix("api") + .add_route(router_without_prefix); + + for route in app_router.collect() { + assert_debug_snapshot!( + format!("[{}]", route.uri.replace('/', "[slash]")), + format!("{:?} {}", route.actions, route.uri) + ); + } + } + #[rstest] + #[case(axum::http::Method::GET, get(action))] + #[case(axum::http::Method::POST, post(action))] + #[case(axum::http::Method::DELETE, delete(action))] + #[case(axum::http::Method::HEAD, head(action))] + #[case(axum::http::Method::OPTIONS, options(action))] + #[case(axum::http::Method::PATCH, patch(action))] + #[case(axum::http::Method::POST, post(action))] + #[case(axum::http::Method::PUT, put(action))] + #[case(axum::http::Method::TRACE, trace(action))] + #[tokio::test] + async fn can_xx( + #[case] http_method: axum::http::Method, + #[case] method: axum::routing::MethodRouter, + ) { + let router_without_prefix = Routes::new().add("/loco", method); + + let app_router = AppRoutes::empty().add_route(router_without_prefix); + + let ctx = tests_cfg::app::get_app_context().await; + let router = app_router.to_router(ctx, axum::Router::new()).unwrap(); + + let req = axum::http::Request::builder() + .uri("/loco") + .method(http_method) + .body(axum::body::Body::empty()) + .unwrap(); + + let response = router.oneshot(req).await.unwrap(); + assert!(response.status().is_success()); + } +} diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]].snap new file mode 100644 index 000000000..c87c43b6d --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_health].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_health].snap new file mode 100644 index 000000000..5b9aafaee --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_health].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /_health" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_ping].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_ping].snap new file mode 100644 index 000000000..2e6f05aa0 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]_ping].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /_ping" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]bar].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]bar].snap new file mode 100644 index 000000000..b65e4d39d --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]bar].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /api/bar" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]foo].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]foo].snap new file mode 100644 index 000000000..2f826466d --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]foo].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /api/foo" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco-rs].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco-rs].snap new file mode 100644 index 000000000..e1e39891c --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco-rs].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /api/loco-rs" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco].snap new file mode 100644 index 000000000..4cfc803c5 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]api[slash]loco].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /api/loco" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple1].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple1].snap new file mode 100644 index 000000000..155bd6f2b --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple1].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[PUT] /multiple1" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple2].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple2].snap new file mode 100644 index 000000000..63e06dbd0 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple2].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[OPTIONS] /multiple2" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple3].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple3].snap new file mode 100644 index 000000000..f64a40853 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]multiple3].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[PATCH] /multiple3" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]foo[slash]bar].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]foo[slash]bar].snap new file mode 100644 index 000000000..d6ee9be92 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]foo[slash]bar].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[DELETE] /normalizer/foo/bar" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]loco[slash]rs].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]loco[slash]rs].snap new file mode 100644 index 000000000..1e5f17a66 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]loco[slash]rs].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[DELETE] /normalizer/loco/rs" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-end].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-end].snap new file mode 100644 index 000000000..06009cbde --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-end].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[TRACE] /normalizer/multiple-end" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-start].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-start].snap new file mode 100644 index 000000000..b685d6fcb --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-start].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[HEAD] /normalizer/multiple-start" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]no-slash].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]no-slash].snap new file mode 100644 index 000000000..c65a0d844 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]no-slash].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[GET] /normalizer/no-slash" diff --git a/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer].snap b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer].snap new file mode 100644 index 000000000..8c1f9c7c5 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__app_routes__tests__[[slash]normalizer].snap @@ -0,0 +1,5 @@ +--- +source: src/controller/app_routes.rs +expression: "format!(\"{:?} {}\", route.actions, route.uri)" +--- +"[POST] /normalizer" diff --git a/src/prelude.rs b/src/prelude.rs index 5c6e23384..ea92be2f8 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,7 +2,7 @@ pub use async_trait::async_trait; pub use axum::{ extract::{Form, Path, State}, response::{IntoResponse, Response}, - routing::{delete, get, post, put}, + routing::{delete, get, head, options, patch, post, put, trace}, }; pub use axum_extra::extract::cookie; pub use chrono::NaiveDateTime as DateTime; From fa0ee22eb73ac3d14b6881760e99cf07b993029d Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Wed, 18 Sep 2024 14:41:48 +0300 Subject: [PATCH 6/9] app_routes tests --- src/controller/app_routes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/app_routes.rs b/src/controller/app_routes.rs index 2c2e7535c..6d75d64e0 100644 --- a/src/controller/app_routes.rs +++ b/src/controller/app_routes.rs @@ -533,7 +533,7 @@ mod tests { use tower::ServiceExt; async fn action() -> Result { - format::json("Hello, World!") + format::json("loco") } #[test] From 109c928aa390ff03f04b41bb4d04bb85ecf02b98 Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Thu, 19 Sep 2024 08:40:56 +0300 Subject: [PATCH 7/9] adding format tests --- src/controller/format.rs | 235 ++++++++++++++++++ ...rmat__tests__builder_cookies_response.snap | 8 + ...format__tests__builder_empty_response.snap | 12 + ..._format__tests__builder_html_response.snap | 14 ++ ..._format__tests__builder_json_response.snap | 14 ++ ...mat__tests__builder_redirect_response.snap | 14 ++ ...mat__tests__builder_template_response.snap | 14 ++ ..._format__tests__builder_text_response.snap | 14 ++ ...ormat__tests__builder_view_response-2.snap | 14 ++ ..._format__tests__builder_view_response.snap | 14 ++ ...at__tests__empty_json_response_format.snap | 14 ++ ..._format__tests__empty_response_format.snap | 12 + ...__format__tests__html_response_format.snap | 14 ++ ...__format__tests__json_response_format.snap | 14 ++ ...ler__format__tests__redirect_response.snap | 14 ++ ...ler__format__tests__template_response.snap | 14 ++ ...__format__tests__text_response_format.snap | 14 ++ ...oller__format__tests__view_response-2.snap | 14 ++ ...troller__format__tests__view_response.snap | 14 ++ 19 files changed, 477 insertions(+) create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_cookies_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_empty_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_html_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_json_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_redirect_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_template_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_text_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response-2.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__empty_json_response_format.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__empty_response_format.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__html_response_format.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__json_response_format.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__redirect_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__template_response.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__text_response_format.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__view_response-2.snap create mode 100644 src/controller/snapshots/loco_rs__controller__format__tests__view_response.snap diff --git a/src/controller/format.rs b/src/controller/format.rs index 19c407c2d..8acb70382 100644 --- a/src/controller/format.rs +++ b/src/controller/format.rs @@ -367,3 +367,238 @@ impl Default for RenderBuilder { pub fn render() -> RenderBuilder { RenderBuilder::new() } + +#[cfg(test)] +mod tests { + + use super::*; + use crate::{controller::views::engines::TeraView, prelude::*}; + use insta::assert_debug_snapshot; + use tree_fs; + + async fn response_body_to_string(response: hyper::Response) -> String { + let bytes = axum::body::to_bytes(response.into_body(), 200) + .await + .unwrap(); + std::str::from_utf8(&bytes).unwrap().to_string() + } + + pub fn get_header_from_response( + response: &hyper::Response, + header: &str, + ) -> Option { + Some(response.headers().get(header)?.to_str().ok()?.to_string()) + } + + #[tokio::test] + async fn empty_response_format() { + let response: hyper::Response = empty().unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(response_body_to_string(response).await, String::new()); + } + + #[tokio::test] + async fn text_response_format() { + let response_content = "loco"; + let response = text(response_content).unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(response_body_to_string(response).await, response_content); + } + + #[tokio::test] + async fn json_response_format() { + let response_content = serde_json::json!({"loco": "app"}); + let response = json(&response_content).unwrap(); + + assert_debug_snapshot!(response); + assert_eq!( + response_body_to_string(response).await, + response_content.to_string() + ); + } + + #[tokio::test] + async fn empty_json_response_format() { + let response = empty_json().unwrap(); + + assert_debug_snapshot!(response); + assert_eq!( + response_body_to_string(response).await, + serde_json::json!({}).to_string() + ); + } + + #[tokio::test] + async fn html_response_format() { + let response_content: &str = "

loco

"; + let response = html(response_content).unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(response_body_to_string(response).await, response_content); + } + + #[tokio::test] + async fn redirect_response() { + let response = redirect("https://loco.rs").unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(response_body_to_string(response).await, String::new()); + } + + #[tokio::test] + async fn view_response() { + let yaml_content = r" + files: + - path: template/test.html + content: |- + - {{foo}} + "; + + let tree_res = tree_fs::from_yaml_str(yaml_content).unwrap(); + let v = TeraView::from_custom_dir(&tree_res).unwrap(); + + assert_debug_snapshot!(view(&v, "template/none.html", serde_json::json!({}))); + let response = view(&v, "template/test.html", serde_json::json!({"foo": "loco"})).unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(&response_body_to_string(response).await, "- loco"); + } + + #[tokio::test] + async fn template_response() { + let response = template("- {{foo}}", serde_json::json!({"foo": "loco"})).unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(&response_body_to_string(response).await, "- loco"); + } + + #[tokio::test] + async fn builder_set_status_code_response() { + assert_eq!(render().empty().unwrap().status(), 200); + assert_eq!(render().status(202).empty().unwrap().status(), 202); + } + + #[tokio::test] + async fn builder_set_headers_response() { + assert_eq!(render().empty().unwrap().headers().len(), 0); + let response = render() + .header("header-1", "loco") + .header("header-2", "rs") + .empty() + .unwrap(); + + assert_eq!(response.headers().len(), 2); + assert_eq!( + get_header_from_response(&response, "header-1"), + Some("loco".to_string()) + ); + assert_eq!( + get_header_from_response(&response, "header-2"), + Some("rs".to_string()) + ); + } + + #[tokio::test] + async fn builder_etag_response() { + assert_eq!(render().empty().unwrap().headers().len(), 0); + let response = render().etag("foobar").unwrap().empty().unwrap(); + + assert_eq!(response.headers().len(), 1); + assert_eq!( + get_header_from_response(&response, "etag"), + Some("foobar".to_string()) + ); + } + + #[tokio::test] + async fn builder_cookies_response() { + let response = render() + .cookies(&[ + cookie::Cookie::new("foo", "bar"), + cookie::Cookie::new("baz", "qux"), + ]) + .unwrap() + .empty() + .unwrap(); + + assert_debug_snapshot!(response.headers()); + } + + #[tokio::test] + async fn builder_text_response() { + let response = render().text("loco").unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(&response_body_to_string(response).await, "loco"); + } + + #[tokio::test] + async fn builder_empty_response() { + let response = render().empty().unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(response_body_to_string(response).await, String::new()); + } + + #[tokio::test] + async fn builder_view_response() { + let yaml_content = r" + files: + - path: template/test.html + content: |- + - {{foo}} + "; + + let tree_res = tree_fs::from_yaml_str(yaml_content).unwrap(); + let v = TeraView::from_custom_dir(&tree_res).unwrap(); + + assert_debug_snapshot!(view(&v, "template/none.html", serde_json::json!({}))); + let response = render() + .view(&v, "template/test.html", serde_json::json!({"foo": "loco"})) + .unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(&response_body_to_string(response).await, "- loco"); + } + + #[tokio::test] + async fn builder_template_response() { + let response = render() + .template("- {{foo}}", serde_json::json!({"foo": "loco"})) + .unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(&response_body_to_string(response).await, "- loco"); + } + + #[tokio::test] + async fn builder_html_response() { + let response_content = "

loco

"; + let response = render().html(response_content).unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(&response_body_to_string(response).await, response_content); + } + + #[tokio::test] + async fn builder_json_response() { + let response_content = serde_json::json!({"loco": "app"}); + let response = render().json(&response_content).unwrap(); + + assert_debug_snapshot!(response); + assert_eq!( + response_body_to_string(response).await, + response_content.to_string() + ); + } + + #[tokio::test] + async fn builder_redirect_response() { + let response = render().redirect("https://loco.rs").unwrap(); + + assert_debug_snapshot!(response); + assert_eq!(response_body_to_string(response).await, String::new()); + } +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_cookies_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_cookies_response.snap new file mode 100644 index 000000000..f04b6c4f8 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_cookies_response.snap @@ -0,0 +1,8 @@ +--- +source: src/controller/format.rs +expression: response.headers() +--- +{ + "set-cookie": "foo=bar", + "set-cookie": "baz=qux", +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_empty_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_empty_response.snap new file mode 100644 index 000000000..f0ae5da0f --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_empty_response.snap @@ -0,0 +1,12 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: {}, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_html_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_html_response.snap new file mode 100644 index 000000000..725b8a2fd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_html_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_json_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_json_response.snap new file mode 100644 index 000000000..2d44c1974 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_json_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "application/json", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_redirect_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_redirect_response.snap new file mode 100644 index 000000000..dc2a417dd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_redirect_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 303, + version: HTTP/1.1, + headers: { + "location": "https://loco.rs", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_template_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_template_response.snap new file mode 100644 index 000000000..725b8a2fd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_template_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_text_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_text_response.snap new file mode 100644 index 000000000..72bd75161 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_text_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/plain; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response-2.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response-2.snap new file mode 100644 index 000000000..725b8a2fd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response-2.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response.snap new file mode 100644 index 000000000..de00916f9 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__builder_view_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: "view(&v, \"template/none.html\", serde_json::json!({}))" +--- +Err( + Tera( + Error { + kind: TemplateNotFound( + "template/none.html", + ), + source: None, + }, + ), +) diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__empty_json_response_format.snap b/src/controller/snapshots/loco_rs__controller__format__tests__empty_json_response_format.snap new file mode 100644 index 000000000..2d44c1974 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__empty_json_response_format.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "application/json", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__empty_response_format.snap b/src/controller/snapshots/loco_rs__controller__format__tests__empty_response_format.snap new file mode 100644 index 000000000..f0ae5da0f --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__empty_response_format.snap @@ -0,0 +1,12 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: {}, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__html_response_format.snap b/src/controller/snapshots/loco_rs__controller__format__tests__html_response_format.snap new file mode 100644 index 000000000..725b8a2fd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__html_response_format.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__json_response_format.snap b/src/controller/snapshots/loco_rs__controller__format__tests__json_response_format.snap new file mode 100644 index 000000000..2d44c1974 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__json_response_format.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "application/json", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__redirect_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__redirect_response.snap new file mode 100644 index 000000000..dc2a417dd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__redirect_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 303, + version: HTTP/1.1, + headers: { + "location": "https://loco.rs", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__template_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__template_response.snap new file mode 100644 index 000000000..725b8a2fd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__template_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__text_response_format.snap b/src/controller/snapshots/loco_rs__controller__format__tests__text_response_format.snap new file mode 100644 index 000000000..72bd75161 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__text_response_format.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/plain; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__view_response-2.snap b/src/controller/snapshots/loco_rs__controller__format__tests__view_response-2.snap new file mode 100644 index 000000000..725b8a2fd --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__view_response-2.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: response +--- +Response { + status: 200, + version: HTTP/1.1, + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: Body( + UnsyncBoxBody, + ), +} diff --git a/src/controller/snapshots/loco_rs__controller__format__tests__view_response.snap b/src/controller/snapshots/loco_rs__controller__format__tests__view_response.snap new file mode 100644 index 000000000..de00916f9 --- /dev/null +++ b/src/controller/snapshots/loco_rs__controller__format__tests__view_response.snap @@ -0,0 +1,14 @@ +--- +source: src/controller/format.rs +expression: "view(&v, \"template/none.html\", serde_json::json!({}))" +--- +Err( + Tera( + Error { + kind: TemplateNotFound( + "template/none.html", + ), + source: None, + }, + ), +) From ab14e8fb68073f97c40919e6de7adad5aa3f2dd9 Mon Sep 17 00:00:00 2001 From: Dotan Nahum Date: Thu, 19 Sep 2024 10:04:55 +0300 Subject: [PATCH 8/9] small fixes --- src/cli.rs | 47 ++++++++++++++++++++++++++++++------ src/controller/fallback.html | 2 +- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 9c3a9a2c6..6ec128831 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -184,9 +184,21 @@ enum ComponentArg { /// Actions actions: Vec, - /// The kind of scaffold to generate - #[clap(short, long, value_enum, default_value_t = gen::ScaffoldKind::Api)] - kind: gen::ScaffoldKind, + /// The kind of controller actions to generate + #[clap(short, long, value_enum, group = "scaffold_kind_group")] + kind: Option, + + /// Use HTMX controller actions + #[clap(long, group = "scaffold_kind_group")] + htmx: bool, + + /// Use HTML controller actions + #[clap(long, group = "scaffold_kind_group")] + html: bool, + + /// Use API controller actions + #[clap(long, group = "scaffold_kind_group")] + api: bool, }, /// Generate a Task based on the given name Task { @@ -256,11 +268,30 @@ impl TryFrom for Component { name, actions, kind, - } => Ok(Self::Controller { - name, - actions, - kind, - }), + htmx, + html, + api, + } => { + let kind = if let Some(kind) = kind { + kind + } else if htmx { + ScaffoldKind::Htmx + } else if html { + ScaffoldKind::Html + } else if api { + ScaffoldKind::Api + } else { + return Err(crate::Error::string( + "Error: One of `kind`, `htmx`, `html`, or `api` must be specified.", + )); + }; + + Ok(Self::Controller { + name, + actions, + kind, + }) + } ComponentArg::Task { name } => Ok(Self::Task { name }), ComponentArg::Scheduler {} => Ok(Self::Scheduler {}), ComponentArg::Worker { name } => Ok(Self::Worker { name }), diff --git a/src/controller/fallback.html b/src/controller/fallback.html index 306e1bfee..610b6aee9 100644 --- a/src/controller/fallback.html +++ b/src/controller/fallback.html @@ -11,7 +11,7 @@
-
+
From bfc86b988fee30fa26932f754730b76501d5659c Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Thu, 19 Sep 2024 10:30:53 +0300 Subject: [PATCH 9/9] add --htmx to controller generate ci --- .github/workflows/ci-generators.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-generators.yml b/.github/workflows/ci-generators.yml index 1d5bba032..c543f25e4 100644 --- a/.github/workflows/ci-generators.yml +++ b/.github/workflows/ci-generators.yml @@ -69,7 +69,7 @@ jobs: DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test - name: controller - run: cargo run -- generate controller pages about && cargo build && cargo test pages + run: cargo run -- generate controller pages about --htmx && cargo build && cargo test pages working-directory: ./examples/demo env: REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}}