tablo-core 0.10.0

The core toolkit types for Tablo, a server-rendered admin toolkit on Topcoat and Toasty.
Documentation
use super::*;
use crate::{ResourceDef, lens, panel::test_support::Subscriber, test_support::memory_db};

#[tokio::test]
async fn find_by_key_loads_one_row_scoped_and_404s_malformed() {
    struct SubscriberResource;
    impl Resource for SubscriberResource {
        type Model = Subscriber;
        type Form = crate::NoForm<Self::Model>;

        fn declare() -> ResourceDef<Self> {
            ResourceDef::new().table(crate::table::Table::new(crate::TextColumn::new(lens!(
                Subscriber.email
            ))))
        }
    }

    let mut db = memory_db(toasty::models!(Subscriber)).await;
    let a = toasty::create!(Subscriber { email: "a@b.c" })
        .exec(&mut db)
        .await
        .unwrap();
    toasty::create!(Subscriber { email: "z@b.c" })
        .exec(&mut db)
        .await
        .unwrap();
    let cx = crate::test_support::panel_cx::<SubscriberResource>(&db);
    let mut ex = crate::db::db(&cx).unwrap();

    // Existing id → exactly that row (typed PK filter, not a full scan).
    let got = find_by_key(
        &cx,
        &crate::resource::require_mounted::<SubscriberResource>(&cx).unwrap(),
        &a.id.to_string(),
        &mut ex,
    )
    .await
    .unwrap();
    assert_eq!(got.id, a.id);

    // Well-formed but unknown id → 404.
    let missing = uuid::Uuid::new_v4().to_string();
    assert!(
        find_by_key(
            &cx,
            &crate::resource::require_mounted::<SubscriberResource>(&cx).unwrap(),
            &missing,
            &mut ex
        )
        .await
        .is_err(),
        "unknown id must not resolve"
    );

    // Malformed id (not a Uuid) → 404, not a query error.
    assert!(
        find_by_key(
            &cx,
            &crate::resource::require_mounted::<SubscriberResource>(&cx).unwrap(),
            "not-a-uuid",
            &mut ex
        )
        .await
        .is_err(),
        "malformed id must not resolve"
    );
}

/// The record pages split on the query they load: the edit page, delete and
/// the write re-loads read the record's own columns through `find_by_key`,
/// while the detail page loads the relations its columns declare, which here
/// include `parent`. The edit load must not pay for the detail page's
/// relations.
#[tokio::test]
async fn record_loads_skip_the_detail_pages_includes() {
    #[derive(Debug, toasty::Model, Clone)]
    struct Parent {
        #[key]
        #[auto]
        id: uuid::Uuid,
        name: String,
    }

    #[derive(Debug, toasty::Model, crate::ModelColumns, Clone)]
    struct Child {
        #[key]
        #[auto]
        id: uuid::Uuid,
        #[index]
        parent_id: uuid::Uuid,
        #[belongs_to(key = parent_id, references = id)]
        parent: toasty::Deferred<Parent>,
    }

    struct ChildResource;
    impl Resource for ChildResource {
        type Model = Child;
        type Form = crate::NoForm<Self::Model>;

        fn declare() -> ResourceDef<Self> {
            ResourceDef::new()
                .table(crate::table::Table::new(crate::ComputedColumn::new(
                    "Id",
                    |c: &Child| c.id.to_string(),
                )))
                .detail(crate::Detail::new(
                    crate::ComputedColumn::new("Parent", |c: &Child| c.parent.get().name.clone())
                        .include_unscoped(Child::fields().parent()),
                ))
        }
    }

    let mut db = memory_db(toasty::models!(Parent, Child)).await;
    let parent = toasty::create!(Parent {
        name: "Ada".to_string(),
    })
    .exec(&mut db)
    .await
    .unwrap();
    let child = toasty::create!(Child {
        parent_id: parent.id,
    })
    .exec(&mut db)
    .await
    .unwrap();
    let cx = crate::test_support::panel_cx::<ChildResource>(&db);
    let mut ex = crate::db::db(&cx).unwrap();
    let id = child.id.to_string();

    let resource = crate::resource::require_mounted::<ChildResource>(&cx).unwrap();
    let record = find_by_key(&cx, &resource, &id, &mut ex).await.unwrap();
    assert!(
        record.parent.is_unloaded(),
        "the edit and write loads read only the record's own columns"
    );
    let detail = find_detail(&cx, &resource, &id, &mut ex).await.unwrap();
    assert!(
        !detail.parent.is_unloaded(),
        "the detail load carries its columns' includes"
    );
}