use axum::body::Body;
use axum::http::{Request, StatusCode};
use axum::Router;
use laterite_admin::{resource, router, AdminConfig, Contributions, Permission};
use laterite_auth::{AuthConfig, AuthService, NewOperator, RequestContext};
use laterite_core::{t, CatalogStore, Db};
use std::sync::Arc;
use tower::ServiceExt;
const COOKIE: &str = "laterite_session";
async fn test_db() -> (Db, laterite_core::testing::TestGuard) {
laterite_core::testing::connect_test(&laterite_admin::builtin_migrations()).await
}
async fn superuser(db: &Db) -> String {
let svc = AuthService::new(db.clone(), AuthConfig::default());
svc.create_superuser(NewOperator {
username: "root",
email: "root@acme.test",
first_name: "Root",
last_name: None,
password: "rootpw12345",
timezone: None,
})
.await
.unwrap();
svc.authenticate("root", "rootpw12345", &RequestContext::default())
.await
.unwrap()
.token
}
fn app(db: Db) -> Router {
router(
AuthService::new(db.clone(), AuthConfig::default()),
db,
Contributions {
resources: vec![resource!("tests/descriptors/posts.yaml")],
permissions: vec![Permission::new(
"acme.manage_posts",
t!("Manage posts"),
t!("Acme"),
)],
..Default::default()
},
AdminConfig::default(),
Arc::new(CatalogStore::default()),
)
}
async fn get(db: Db, path: &str, token: &str) -> (StatusCode, String) {
let resp = app(db)
.oneshot(
Request::builder()
.uri(path)
.header("cookie", format!("{COOKIE}={token}"))
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let status = resp.status();
let body = String::from_utf8(
axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap()
.to_vec(),
)
.unwrap();
(status, body)
}
#[tokio::test]
async fn a_resource_from_a_file_serves_its_list() {
let (db, _guard) = test_db().await;
let token = superuser(&db).await;
let (status, html) = get(db, "/admin/posts", &token).await;
assert_eq!(status, StatusCode::OK);
assert!(html.contains("Posts"), "the title");
assert!(html.contains("Title"), "a column labelled by the file");
assert!(
html.contains("Created at"),
"and one labelled from its name"
);
assert!(html.contains("No posts yet."), "the empty message");
assert!(
html.contains("placeholder='Search posts'"),
"the search prompt"
);
assert!(html.contains("Rows per page"), "the offered page sizes");
assert!(html.contains("lat-col--right"), "the column alignment");
assert!(html.contains("style=\"width:12%\""), "and its width");
}
#[tokio::test]
async fn the_form_the_file_describes_is_mounted() {
let (db, _guard) = test_db().await;
let token = superuser(&db).await;
let (status, html) = get(db, "/admin/posts/new", &token).await;
assert_eq!(status, StatusCode::OK);
assert!(html.contains(r#"name="code""#));
assert!(html.contains(r#"name="name""#));
assert!(html.contains("Title"), "a field labelled by the file");
}
#[tokio::test]
async fn both_screens_read_the_same_entity() {
let file = resource!("tests/descriptors/posts.yaml");
assert_eq!(file.list.entity, "backend_roles");
assert_eq!(file.form.as_ref().unwrap().entity, "backend_roles");
assert_eq!(file.form.as_ref().unwrap().base_path, "/posts");
assert_eq!(file.list.edit_base.as_deref(), Some("/posts"));
}