lenso-api 0.1.2

HTTP API host crate for the Lenso backend framework.
Documentation
use axum::body::{Body, to_bytes};
use axum::http::{Request, StatusCode};
use lenso_api::build_router;
use platform_core::{AppConfig, AppContext, LoggingEventPublisher};
use serde_json::json;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tower::ServiceExt;

#[tokio::test]
async fn serves_console_extension_registry_from_configured_directory() {
    let root = unique_console_root("extensions");
    tokio::fs::create_dir_all(&root)
        .await
        .expect("extension dir should be created");
    tokio::fs::write(
        root.join("registry.json"),
        serde_json::to_vec(&json!({
            "version": 1,
            "bundles": [
                {
                    "packageName": "@vendor/crm-console",
                    "exportName": "crmConsoleModule",
                    "entry": "/console/extensions/crm/entry.js",
                    "hostApi": "1"
                }
            ]
        }))
        .expect("registry should serialize"),
    )
    .await
    .expect("registry should be written");

    let mut config = AppConfig::from_env();
    config.console.extensions_dir = root.to_string_lossy().into_owned();
    let app = app_with_config(config);

    let response = app
        .oneshot(
            Request::builder()
                .uri("/console/extensions/registry.json")
                .body(Body::empty())
                .expect("request should build"),
        )
        .await
        .expect("request should complete");

    assert_eq!(response.status(), StatusCode::OK);
    let bytes = to_bytes(response.into_body(), usize::MAX)
        .await
        .expect("body should read");
    let body: serde_json::Value = serde_json::from_slice(&bytes).expect("registry should be json");
    assert_eq!(body["bundles"][0]["packageName"], "@vendor/crm-console");

    let _ = tokio::fs::remove_dir_all(root).await;
}

#[tokio::test]
async fn serves_console_dist_with_index_fallback() {
    let root = unique_console_root("dist");
    tokio::fs::create_dir_all(&root)
        .await
        .expect("console dist dir should be created");
    tokio::fs::write(root.join("index.html"), "<div id=\"root\"></div>")
        .await
        .expect("console index should be written");

    let mut config = AppConfig::from_env();
    config.console.dist_dir = root.to_string_lossy().into_owned();
    let app = app_with_config(config);

    let response = app
        .oneshot(
            Request::builder()
                .uri("/console/runtime/stories")
                .body(Body::empty())
                .expect("request should build"),
        )
        .await
        .expect("request should complete");

    assert_eq!(response.status(), StatusCode::OK);
    let bytes = to_bytes(response.into_body(), usize::MAX)
        .await
        .expect("body should read");
    let body = String::from_utf8(bytes.to_vec()).expect("body should be utf-8");
    assert!(body.contains("root"));

    let _ = tokio::fs::remove_dir_all(root).await;
}

fn app_with_config(config: AppConfig) -> axum::Router {
    build_router(AppContext::new(
        config,
        platform_core::DbPool::connect_lazy("postgres://localhost/lenso_test")
            .expect("lazy pool should build"),
        Arc::new(LoggingEventPublisher),
    ))
}

fn unique_console_root(kind: &str) -> std::path::PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system clock should be after unix epoch")
        .as_nanos();
    std::env::temp_dir().join(format!("lenso-console-{kind}-{nanos}"))
}