Lines
100 %
Functions
Branches
use finance::account::Account;
use finance::tag::Tag;
use server::command::{CmdResult, CommodityInfo, FinanceEntity};
use std::collections::HashMap;
use uuid::Uuid;
use super::balance::{format_rational, run_get_balance_single, run_get_balances};
use super::create::run_create_account;
use super::create::run_set_account_tag;
use super::list::{
format_commodity_info_list, format_manage_tree, format_tagged_entities, list_account_entries,
parse_account_commodities_id, parse_get_account_for_manage_id, validate_lookup_key,
};
use super::quote_string;
fn account_entity(id: Uuid) -> FinanceEntity {
FinanceEntity::Account(Account::builder().id(id).build().expect("account builder"))
}
#[test]
fn format_empty_list() {
assert_eq!(format_tagged_entities(&[]), "(:accounts ())");
fn format_single_account_no_tags() {
let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let out = format_tagged_entities(&[(account_entity(id), HashMap::new())]);
assert_eq!(
out,
"(:accounts ((:id \"550e8400-e29b-41d4-a716-446655440000\")))"
);
fn format_account_with_name_tag() {
let mut tags = HashMap::new();
tags.insert(
"name".to_string(),
FinanceEntity::Tag(Tag {
id: Uuid::nil(),
tag_name: "name".to_string(),
tag_value: "Checking".to_string(),
description: None,
}),
let out = format_tagged_entities(&[(account_entity(id), tags)]);
assert!(out.contains(":name \"Checking\""));
assert!(out.contains(":id \"550e8400-e29b-41d4-a716-446655440000\""));
fn format_escapes_quotes_in_name() {
let id = Uuid::nil();
tag_value: "He said \"hi\"".to_string(),
assert!(out.contains("\"He said \\\"hi\\\"\""));
fn quote_string_round_trip_safe() {
assert_eq!(quote_string("simple"), "\"simple\"");
assert_eq!(quote_string("a\"b"), "\"a\\\"b\"");
assert_eq!(quote_string("a\\b"), "\"a\\\\b\"");
fn validate_lookup_key_rejects_missing_arg() {
let err = validate_lookup_key("get-account", None).unwrap_err();
assert!(err.to_string().contains("missing or empty"), "got: {err}");
fn validate_lookup_key_rejects_empty_string() {
let err = validate_lookup_key("get-account", Some(String::new())).unwrap_err();
fn format_rational_integer_drops_denom() {
use num_rational::Rational64;
assert_eq!(format_rational(&Rational64::new(42, 1)), "42");
assert_eq!(format_rational(&Rational64::new(0, 1)), "0");
fn format_rational_fraction_preserves_denom() {
assert_eq!(format_rational(&Rational64::new(5000, 100)), "50");
assert_eq!(format_rational(&Rational64::new(1, 3)), "1/3");
assert_eq!(format_rational(&Rational64::new(-7, 2)), "-7/2");
#[tokio::test]
async fn run_get_balance_single_with_no_arg_emits_error() {
let err = run_get_balance_single(Uuid::nil(), None).await.unwrap_err();
async fn run_get_balance_single_with_invalid_uuid_emits_error() {
let err = run_get_balance_single(Uuid::nil(), Some("not-uuid".into()))
.await
.unwrap_err();
assert!(err.to_string().contains("invalid uuid"), "got: {err}");
async fn run_get_balances_missing_arg_emits_error() {
let err = run_get_balances(Uuid::nil(), None).await.unwrap_err();
async fn run_get_balances_invalid_uuid_emits_error() {
let err = run_get_balances(Uuid::nil(), Some("not-uuid".into()))
async fn run_create_account_empty_parent_falls_through_to_name_check() {
let err = run_create_account(Uuid::nil(), None, Some(String::new()))
assert!(err.to_string().contains(":name"), "got: {err}");
async fn run_create_account_invalid_parent_uuid_emits_error() {
let err = run_create_account(
Uuid::nil(),
Some("MyAccount".into()),
Some("not-a-uuid".into()),
)
assert!(
err.to_string().contains("invalid parent uuid"),
"got: {err}"
fn format_account_commodities_empty() {
assert_eq!(format_commodity_info_list(&[]), "(:account-commodities ())");
fn format_account_commodities_multi() {
let id1 = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let id2 = Uuid::parse_str("71ddfbdb-1f00-4403-9548-dc973b43e443").unwrap();
let items = vec![
CommodityInfo {
commodity_id: id1,
symbol: "USD".into(),
name: "US Dollar".into(),
},
commodity_id: id2,
symbol: "JPY".into(),
name: "Japanese Yen".into(),
];
let out = format_commodity_info_list(&items);
assert!(out.contains(":commodity-id \"550e8400-e29b-41d4-a716-446655440000\""));
assert!(out.contains(":symbol \"USD\""));
assert!(out.contains(":name \"US Dollar\""));
assert!(out.contains(":commodity-id \"71ddfbdb-1f00-4403-9548-dc973b43e443\""));
assert!(out.contains(":symbol \"JPY\""));
fn parse_account_commodities_rejects_missing() {
let err = parse_account_commodities_id(None).unwrap_err();
fn parse_account_commodities_rejects_invalid_uuid() {
let err = parse_account_commodities_id(Some("nope".into())).unwrap_err();
async fn run_set_account_tag_missing_id_emits_error() {
let err = run_set_account_tag(Uuid::nil(), None, Some("k".into()), Some("v".into()))
assert!(err.to_string().contains(":account-id"), "got: {err}");
async fn run_set_account_tag_invalid_uuid_emits_error() {
let err = run_set_account_tag(
Some("not-uuid".into()),
Some("k".into()),
Some("v".into()),
async fn run_set_account_tag_missing_name_emits_error() {
let id = "11111111-1111-1111-1111-111111111111";
let err = run_set_account_tag(Uuid::nil(), Some(id.into()), None, Some("v".into()))
assert!(err.to_string().contains(":tag-name"), "got: {err}");
async fn run_set_account_tag_missing_value_emits_error() {
let err = run_set_account_tag(Uuid::nil(), Some(id.into()), Some("k".into()), None)
assert!(err.to_string().contains(":tag-value"), "got: {err}");
async fn run_create_account_missing_name_emits_error() {
let err = run_create_account(Uuid::nil(), None, None)
async fn run_create_account_empty_name_emits_error() {
let err = run_create_account(Uuid::nil(), Some(String::new()), None)
fn format_manage_tree_empty() {
assert_eq!(format_manage_tree(&[]), "(:accounts-tree ())");
fn format_manage_tree_root_emits_nil_parent() {
let id = Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap();
let root = FinanceEntity::Account(Account::builder().id(id).build().expect("account builder"));
let out = format_manage_tree(&[(root, HashMap::new())]);
assert!(out.contains(":id \"11111111-1111-1111-1111-111111111111\""));
assert!(out.contains(":parent-id nil"));
fn parse_get_account_for_manage_id_rejects_missing() {
let err = parse_get_account_for_manage_id(None).unwrap_err();
fn parse_get_account_for_manage_id_rejects_invalid_uuid() {
let err = parse_get_account_for_manage_id(Some("not-uuid".into())).unwrap_err();
fn format_manage_tree_child_surfaces_parent_uuid_and_name() {
let id = Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap();
let parent = Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap();
let child = FinanceEntity::Account(
Account::builder()
.id(id)
.parent(parent)
.build()
.expect("account builder"),
tag_value: "Sub".to_string(),
let out = format_manage_tree(&[(child, tags)]);
assert!(out.contains(":parent-id \"11111111-1111-1111-1111-111111111111\""));
assert!(out.contains(":name \"Sub\""));
fn list_account_entries_extracts_type_tag() {
let account =
FinanceEntity::Account(Account::builder().id(id).build().expect("account builder"));
"type".to_string(),
tag_name: "type".to_string(),
tag_value: "asset".to_string(),
let result = Ok(Some(CmdResult::TaggedEntities {
entities: vec![(account, tags)],
pagination: None,
}));
let entries = list_account_entries("test", result).unwrap();
assert_eq!(entries.len(), 1);
let (_, _, _, type_val) = &entries[0];
assert_eq!(type_val.as_deref(), Some("asset"));