1
//! Unit tests for the commodity-domain natives. The `format_*` / `quote_string`
2
//! helpers are test-only fixtures retained from the pre-WasmGC string envelope.
3

            
4
use super::*;
5
use finance::commodity::Commodity;
6
use std::collections::HashMap;
7
use uuid::Uuid;
8

            
9
/// Renders the TaggedEntities result as the previous self-capturing string
10
/// envelope; retained for the format fixtures below.
11
3
fn format_tagged_commodities(
12
3
    entities: &[(
13
3
        FinanceEntity,
14
3
        std::collections::HashMap<String, FinanceEntity>,
15
3
    )],
16
3
) -> String {
17
3
    let mut out = String::from("(:commodities (");
18
3
    for (idx, (entity, tags)) in entities.iter().enumerate() {
19
2
        if idx > 0 {
20
            out.push(' ');
21
2
        }
22
2
        let id = match entity {
23
2
            FinanceEntity::Commodity(c) => c.id,
24
            other => {
25
                out.push_str(&format!("(:error \"unexpected entity {other:?}\")"));
26
                continue;
27
            }
28
        };
29
2
        out.push_str(&format!("(:id \"{id}\""));
30
2
        if let Some(symbol) = tag_value(tags, "symbol") {
31
1
            out.push_str(&format!(" :symbol {}", quote_string(symbol)));
32
1
        }
33
2
        if let Some(name) = tag_value(tags, "name") {
34
1
            out.push_str(&format!(" :name {}", quote_string(name)));
35
1
        }
36
2
        out.push(')');
37
    }
38
3
    out.push_str("))");
39
3
    out
40
3
}
41

            
42
2
fn quote_string(s: &str) -> String {
43
2
    let mut q = String::with_capacity(s.len() + 2);
44
2
    q.push('"');
45
12
    for ch in s.chars() {
46
12
        match ch {
47
            '"' => q.push_str("\\\""),
48
            '\\' => q.push_str("\\\\"),
49
12
            other => q.push(other),
50
        }
51
    }
52
2
    q.push('"');
53
2
    q
54
2
}
55

            
56
2
fn commodity_entity(id: Uuid) -> FinanceEntity {
57
2
    FinanceEntity::Commodity(Commodity { id })
58
2
}
59

            
60
#[test]
61
1
fn format_empty_list() {
62
1
    assert_eq!(format_tagged_commodities(&[]), "(:commodities ())");
63
1
}
64

            
65
#[test]
66
1
fn format_single_commodity_with_symbol_and_name() {
67
1
    let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
68
1
    let mut tags = HashMap::new();
69
1
    tags.insert(
70
1
        "symbol".to_string(),
71
1
        FinanceEntity::Tag(Tag {
72
1
            id: Uuid::nil(),
73
1
            tag_name: "symbol".into(),
74
1
            tag_value: "USD".into(),
75
1
            description: None,
76
1
        }),
77
    );
78
1
    tags.insert(
79
1
        "name".to_string(),
80
1
        FinanceEntity::Tag(Tag {
81
1
            id: Uuid::nil(),
82
1
            tag_name: "name".into(),
83
1
            tag_value: "US Dollar".into(),
84
1
            description: None,
85
1
        }),
86
    );
87
1
    let out = format_tagged_commodities(&[(commodity_entity(id), tags)]);
88
1
    assert!(out.contains(":id \"550e8400-e29b-41d4-a716-446655440000\""));
89
1
    assert!(out.contains(":symbol \"USD\""));
90
1
    assert!(out.contains(":name \"US Dollar\""));
91
1
}
92

            
93
#[test]
94
1
fn parse_amount_str_integer() {
95
1
    assert_eq!(parse_amount_str("100").unwrap(), (100, 1));
96
1
}
97

            
98
#[test]
99
1
fn parse_amount_str_fraction() {
100
1
    assert_eq!(parse_amount_str("153/100").unwrap(), (153, 100));
101
1
}
102

            
103
#[test]
104
1
fn parse_amount_str_negative_integer() {
105
1
    assert_eq!(parse_amount_str("-42").unwrap(), (-42, 1));
106
1
}
107

            
108
#[test]
109
1
fn parse_amount_str_negative_fraction() {
110
1
    assert_eq!(parse_amount_str("-153/100").unwrap(), (-153, 100));
111
1
}
112

            
113
#[test]
114
1
fn parse_amount_str_empty_is_error() {
115
1
    assert!(parse_amount_str("").is_err());
116
1
}
117

            
118
#[test]
119
1
fn parse_amount_str_garbage_is_error() {
120
1
    assert!(parse_amount_str("abc").is_err());
121
1
}
122

            
123
#[test]
124
1
fn parse_amount_str_zero_denom_is_error() {
125
1
    let err = parse_amount_str("1/0").unwrap_err();
126
1
    assert!(err.to_string().contains("denominator must be positive"));
127
1
}
128

            
129
#[test]
130
1
fn parse_amount_str_negative_denom_is_error() {
131
1
    let err = parse_amount_str("1/-100").unwrap_err();
132
1
    assert!(err.to_string().contains("denominator must be positive"));
133
1
}
134

            
135
#[test]
136
1
fn parse_amount_str_multiple_slashes_is_error() {
137
1
    assert!(parse_amount_str("1/2/3").is_err());
138
1
}
139

            
140
#[tokio::test]
141
1
async fn run_create_commodity_missing_symbol_emits_error() {
142
1
    let err = run_create_commodity(Uuid::nil(), None, Some("name".into()))
143
1
        .await
144
1
        .unwrap_err();
145
1
    assert!(err.to_string().contains(":symbol"));
146
1
}
147

            
148
#[tokio::test]
149
1
async fn run_create_commodity_missing_name_emits_error() {
150
1
    let err = run_create_commodity(Uuid::nil(), Some("sym".into()), None)
151
1
        .await
152
1
        .unwrap_err();
153
1
    assert!(err.to_string().contains(":name"));
154
1
}
155

            
156
#[test]
157
1
fn format_commodity_without_tags_emits_id_only() {
158
1
    let id = Uuid::nil();
159
1
    let out = format_tagged_commodities(&[(commodity_entity(id), HashMap::new())]);
160
1
    assert_eq!(
161
        out,
162
        "(:commodities ((:id \"00000000-0000-0000-0000-000000000000\")))"
163
    );
164
1
}