use axum::body::Body;
use axum::http::{Request, StatusCode};
use axum::Router;
use laterite_admin::settings::{save, BrandSetting};
use laterite_admin::{router, AdminConfig};
use laterite_auth::{AuthConfig, AuthService, NewOperator, RequestContext};
use laterite_core::Db;
use tower::ServiceExt;
async fn test_db() -> (Db, laterite_core::testing::TestGuard) {
laterite_core::testing::connect_test(&laterite_admin::builtin_migrations()).await
}
fn app(db: Db, app_name: &str) -> Router {
let auth = AuthService::new(db.clone(), AuthConfig::default());
router(
auth,
db,
Vec::new(),
Vec::new(),
Vec::new(),
AdminConfig {
secure_cookie: false,
timezone: "UTC".to_string(),
app_name: app_name.to_string(),
},
)
}
async fn get(router: Router, path: &str) -> (StatusCode, String) {
let resp = router
.oneshot(
Request::builder()
.method("GET")
.uri(path)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let status = resp.status();
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
(status, String::from_utf8(bytes.to_vec()).unwrap())
}
#[tokio::test]
async fn configured_app_name_is_the_brand() {
let (db, _guard) = test_db().await;
let (status, html) = get(app(db, "Acme Blog"), "/admin/setup").await;
assert_eq!(status, StatusCode::OK);
assert!(html.contains("Acme Blog"), "shows the configured app name");
assert!(!html.contains("<h1>Laterite</h1>"), "no hardcoded brand");
}
#[tokio::test]
async fn branding_form_prefills_the_configured_name() {
let (db, _guard) = test_db().await;
let auth = AuthService::new(db.clone(), AuthConfig::default());
auth.create_superuser(NewOperator {
username: "root",
email: "root@acme.test",
first_name: "Root",
last_name: None,
password: "rootpw12345",
timezone: None,
})
.await
.unwrap();
let token = auth
.authenticate("root", "rootpw12345", &RequestContext::default())
.await
.unwrap()
.token;
let resp = app(db, "Configured Name")
.oneshot(
Request::builder()
.method("GET")
.uri("/admin/settings/laterite.brand")
.header("cookie", format!("laterite_session={token}"))
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
let html = String::from_utf8(bytes.to_vec()).unwrap();
assert!(
html.contains(r#"value="Configured Name""#),
"branding form seeds the configured app name into the field"
);
}
#[tokio::test]
async fn powered_by_laterite_attribution_is_shown() {
let (db, _guard) = test_db().await;
let (status, html) = get(app(db.clone(), "Acme"), "/admin/setup").await;
assert_eq!(status, StatusCode::OK);
assert!(
html.contains("Powered by Laterite"),
"setup shows attribution"
);
assert!(
html.contains("https://laterite.rs"),
"attribution links to the site"
);
let auth = AuthService::new(db.clone(), AuthConfig::default());
auth.create_superuser(NewOperator {
username: "root",
email: "root@acme.test",
first_name: "Root",
last_name: None,
password: "rootpw12345",
timezone: None,
})
.await
.unwrap();
let token = auth
.authenticate("root", "rootpw12345", &RequestContext::default())
.await
.unwrap()
.token;
let resp = app(db, "Acme")
.oneshot(
Request::builder()
.method("GET")
.uri("/admin")
.header("cookie", format!("laterite_session={token}"))
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
let html = String::from_utf8(bytes.to_vec()).unwrap();
assert!(
html.contains("Powered by Laterite"),
"the in-app user menu shows the attribution"
);
}
#[tokio::test]
async fn brand_setting_overrides_the_configured_name() {
let (db, _guard) = test_db().await;
save(
&db,
&BrandSetting {
app_name: "Override Co".to_string(),
},
)
.await
.unwrap();
let (status, html) = get(app(db, "Acme Blog"), "/admin/setup").await;
assert_eq!(status, StatusCode::OK);
assert!(html.contains("Override Co"), "the setting overrides config");
assert!(!html.contains("Acme Blog"), "the config name is overridden");
}