// Request tests for {Controller} — one test per controller action.
// Generated by `doido generate scaffold`.
//
// The app is a binary crate, so this integration test includes the relevant
// modules via `#[path]` (the same way `src/main.rs` does) to build the router
// and reach the model entity.
#![allow(dead_code, unused_imports)]

#[path = "../app/controllers/mod.rs"]
mod controllers;
#[path = "../app/models/mod.rs"]
mod models;
#[path = "../config/routes.rs"]
mod routes;

use doido::controller::axum;
use doido::model::sea_orm::{
    ColumnTrait, ConnectOptions, ConnectionTrait, Database, EntityTrait, QueryOrder, Schema,
};
use models::{singular}::{Column, Entity};
use tower::ServiceExt;

// The full application router (this resource's routes are registered into it).
fn app() -> axum::Router {
    routes::router()
}

fn get(uri: &str) -> axum::http::Request<axum::body::Body> {
    axum::http::Request::get(uri)
        .body(axum::body::Body::empty())
        .unwrap()
}

// A form (urlencoded) request carrying a sample value for every column.
fn form(method: &str, uri: &str) -> axum::http::Request<axum::body::Body> {
    axum::http::Request::builder()
        .method(method)
        .uri(uri)
        .header("content-type", "application/x-www-form-urlencoded")
        .body(axum::body::Body::from("{form_body}"))
        .unwrap()
}

fn delete(uri: &str) -> axum::http::Request<axum::body::Body> {
    axum::http::Request::delete(uri)
        .body(axum::body::Body::empty())
        .unwrap()
}

async fn send(req: axum::http::Request<axum::body::Body>) -> axum::http::StatusCode {
    app().oneshot(req).await.unwrap().status()
}

// Serializes tests (they share the process-global pool), installs an in-memory
// database with this model's table, and the view engine. Returns the lock guard,
// which the caller holds for the duration of the test.
async fn setup() -> std::sync::MutexGuard<'static, ()> {
    let guard = doido::model::pool::test_lock();
    let _ = doido::view::init("app/views");
    if doido::model::pool::try_pool().is_none() {
        let mut opts = ConnectOptions::new("sqlite::memory:".to_owned());
        opts.max_connections(1);
        let db = Database::connect(opts).await.expect("connect in-memory db");
        let _ = doido::model::pool::set_pool(db);
    }
    let db = doido::model::pool::pool();
    let backend = db.get_database_backend();
    let mut stmt = Schema::new(backend).create_table_from_entity(Entity);
    stmt.if_not_exists();
    db.execute(&stmt).await.expect("create table");
    guard
}

// Seeds one row through the create endpoint and returns its id.
async fn seed() -> i32 {
    let db = doido::model::pool::pool();
    let _ = Entity::delete_many().exec(db).await;
    let _ = app().oneshot(form("POST", "/{plural}")).await.unwrap();
    Entity::find()
        .order_by_desc(Column::Id)
        .one(db)
        .await
        .unwrap()
        .expect("seeded row")
        .id
}

#[tokio::test]
async fn index_request() {
    let _g = setup().await;
    assert_eq!(send(get("/{plural}")).await, axum::http::StatusCode::OK);
}

#[tokio::test]
async fn new_request() {
    let _g = setup().await;
    assert_eq!(send(get("/{plural}/new")).await, axum::http::StatusCode::OK);
}

#[tokio::test]
async fn create_request() {
    let _g = setup().await;
    assert_eq!(
        send(form("POST", "/{plural}")).await,
        axum::http::StatusCode::FOUND
    );
}

#[tokio::test]
async fn show_request() {
    let _g = setup().await;
    let id = seed().await;
    assert_eq!(
        send(get(&format!("/{plural}/{id}"))).await,
        axum::http::StatusCode::OK
    );
}

#[tokio::test]
async fn edit_request() {
    let _g = setup().await;
    let id = seed().await;
    assert_eq!(
        send(get(&format!("/{plural}/{id}/edit"))).await,
        axum::http::StatusCode::OK
    );
}

#[tokio::test]
async fn update_request() {
    let _g = setup().await;
    let id = seed().await;
    assert_eq!(
        send(form("PATCH", &format!("/{plural}/{id}"))).await,
        axum::http::StatusCode::FOUND
    );
}

#[tokio::test]
async fn destroy_request() {
    let _g = setup().await;
    let id = seed().await;
    assert_eq!(
        send(delete(&format!("/{plural}/{id}"))).await,
        axum::http::StatusCode::FOUND
    );
}
