Lines
100 %
Functions
Branches
//! Wire-shape guard: captures REAL handle_form output and verifies parse_wire + value_to_rows.
//!
//! Gated on the `db` feature.
//! DATABASE_URL=postgres://… cargo test -p tests-integration --features db
#![cfg(feature = "db")]
use cli_core::render::{WireValue, parse_wire, reparse_list, value_to_rows};
use nomiscript::{Value, list_to_vec};
use rpc::{ScriptCtx, Session};
use server::db::DB_POOL;
use sqlx::PgPool;
use supp_macro::local_db_sqlx_test;
use uuid::Uuid;
async fn setup() {}
async fn insert_test_user(pool: &PgPool, id: Uuid) -> anyhow::Result<()> {
sqlx::query!(
"INSERT INTO users (
id, user_name, email, photo, verified, user_password,
user_role, db_name, created_at
) VALUES (
$1, 'render-test-user', 'render-test@example.com', 'default.png',
FALSE, 'irrelevant', 'user', 'render-test', NOW()
)",
id
)
.execute(pool)
.await?;
Ok(())
}
#[local_db_sqlx_test]
async fn parse_wire_handles_empty_list_accounts(pool: PgPool) -> anyhow::Result<()> {
let user_id = Uuid::new_v4();
insert_test_user(&pool, user_id).await?;
let mut session = Session::new(ScriptCtx::new(user_id)).expect("Session::new");
let wire = session.handle_form("(:id 1 :form (list-accounts))").await;
assert!(wire.contains(":id 1"), "got: {wire}");
let WireValue::Value(raw) = parse_wire(&wire).expect("parse_wire must succeed");
// list-accounts encodes its result as a printed-list string; callers must
// call reparse_list for pair:* results.
let s = match &raw {
Value::String(s) => s.clone(),
other => format!("{other:?}"),
};
let v = reparse_list(&s).expect("reparse_list must succeed");
let rows = value_to_rows(&v);
assert_eq!(
rows.len(),
0,
"empty accounts list should yield 0 rows, got: {rows:?}"
);
async fn parse_wire_handles_populated_list_accounts(pool: PgPool) -> anyhow::Result<()> {
let r1 = session
.handle_form("(:id 2 :form (create-account \"acct-alpha\" \"\"))")
.await;
assert!(
r1.contains(":value \""),
"create-account should return uuid: {r1}"
let r2 = session
.handle_form("(:id 3 :form (create-account \"acct-beta\" \"\"))")
r2.contains(":value \""),
"create-account should return uuid: {r2}"
let wire = session.handle_form("(:id 4 :form (list-accounts))").await;
assert!(wire.contains(":id 4"), "got: {wire}");
rows.len() >= 2,
"expected >=2 rows for 2 accounts, got: {rows:?}"
/// Pins the `pair:Account` element shape the cli `account list` renderer
/// depends on: each element is `(:account :id … :name … :parent …)`. If the
/// native shape drifts, the cli `account_list_line` (`:name` extraction) breaks.
async fn list_accounts_element_carries_account_and_name_keys(pool: PgPool) -> anyhow::Result<()> {
session
.handle_form("(:id 30 :form (create-account \"acct-alpha\" \"\"))")
let wire = session.handle_form("(:id 31 :form (list-accounts))").await;
let WireValue::Value(raw) = parse_wire(&wire).expect("parse_wire");
let Value::String(s) = &raw else {
panic!("list-accounts value must be a string, got: {raw:?}");
s.contains(":account") && s.contains(":name") && s.contains("acct-alpha"),
"account element must carry :account/:name keys, got: {s}"
/// Pins the `pair:Commodity` element shape the cli `commodity list` renderer
/// depends on: `(:commodity :id … :symbol … :name …)`.
async fn list_commodities_element_carries_symbol_and_name_keys(pool: PgPool) -> anyhow::Result<()> {
.handle_form("(:id 32 :form (create-commodity \"USD\" \"US Dollar\"))")
let wire = session
.handle_form("(:id 33 :form (list-commodities))")
panic!("list-commodities value must be a string, got: {raw:?}");
s.contains(":commodity") && s.contains(":symbol") && s.contains(":name"),
"commodity element must carry :commodity/:symbol/:name keys, got: {s}"
assert!(s.contains("USD") && s.contains("US Dollar"), "got: {s}");
async fn parse_wire_handles_wire_error(pool: PgPool) -> anyhow::Result<()> {
let _ = pool;
.handle_form(
"(:id 5 :form (set-account-tag \
\"00000000-0000-0000-0000-000000000000\" \
\"name\" \
\"x\"))",
assert!(wire.contains(":error"), "expected error wire, got: {wire}");
let result = parse_wire(&wire);
result.is_err(),
"parse_wire must return Err for wire error, got: {result:?}"
async fn parse_wire_get_balances_cell_values(pool: PgPool) -> anyhow::Result<()> {
// Create commodity, accounts, and a transaction with splits
let rc = session
.handle_form("(:id 10 :form (create-commodity \"USD\" \"US Dollar\"))")
assert!(rc.contains(":value \""), "create-commodity: {rc}");
let commodity_id = extract_uuid_value(&rc).expect("commodity uuid");
.handle_form("(:id 11 :form (create-account \"wallet\" \"\"))")
assert!(r1.contains(":value \""), "create wallet: {r1}");
let wallet_id = extract_uuid_value(&r1).expect("wallet uuid");
.handle_form("(:id 12 :form (create-account \"income\" \"\"))")
assert!(r2.contains(":value \""), "create income: {r2}");
let income_id = extract_uuid_value(&r2).expect("income uuid");
let form = format!(
"(:id 13 :form (create-transaction \
\"(:post-date \\\"2024-01-01T00:00:00Z\\\" \
:note \\\"test deposit\\\" \
:splits ((:account-id \\\"{wallet_id}\\\" :commodity-id \\\"{commodity_id}\\\" :value 100) \
(:account-id \\\"{income_id}\\\" :commodity-id \\\"{commodity_id}\\\" :value -100)))\"))"
let r3 = session.handle_form(&form).await;
assert!(r3.contains(":value \""), "create-transaction: {r3}");
.handle_form(&format!("(:id 14 :form (get-balances \"{wallet_id}\"))"))
assert!(wire.contains(":id 14"), "got: {wire}");
other => panic!("expected string value, got: {other:?}"),
// get-balances returns `pair:string`: the outer reparse yields a LIST whose
// elements are themselves plist STRINGS, so each balance needs a SECOND
// reparse to reach its `(:commodity-id .. :symbol .. :value-num ..)` fields.
// (Unlike list-accounts `pair:Entity`, whose elements reparse to structured
// plists directly — tracked as a Phase 2 shape-consistency item.)
let outer = reparse_list(&s).expect("reparse_list on get-balances output");
let elements = list_to_vec(&outer).expect("get-balances value is a list");
!elements.is_empty(),
"get-balances with a split should yield at least one balance element, got: {outer:?}"
let Value::String(plist_str) = &elements[0] else {
panic!(
"balance element should be a plist string, got: {:?}",
elements[0]
let plist = reparse_list(plist_str).expect("reparse inner balance plist");
// A reparsed plist renders as exactly ONE row of its field values.
let rows = value_to_rows(&plist);
1,
"balance plist should be one row, got: {rows:?}"
let cells = &rows[0];
cells.iter().any(|c| c.contains("USD")),
"expected USD symbol in balance row, got: {cells:?}"
cells.iter().any(|c| c == "100"),
"expected value-num 100 in balance row, got: {cells:?}"
fn extract_uuid_value(wire: &str) -> Option<String> {
let needle = ":value \"";
let start = wire.find(needle)? + needle.len();
let end = wire[start..].find('"')? + start;
Some(wire[start..end].to_string())