Coverage Report

Created: 2022-09-02 18:59

/Users/oliverbrotchie/Documents/GitHub/csv-ledger/src/ledger.rs
Line
Count
Source
1
//! # Ledger
2
//!  This module contains `Ledger`, the state store used for this CLI.
3
//!
4
//! `Ledger` stores running totals of client accounts, consumes csv files and
5
//! outputs associated to account statements to string.
6
//!
7
//! **Basic example:**
8
//! ```rust
9
//! use crate::ledger::ledger
10
//!
11
//! fn main() {
12
//!     // Read in a new file
13
//!     let reader = BufReader::new(File::open("./foo.csv").unwrap());
14
//!     
15
//!     // Create a new ledger and read in the csv file line by line
16
//!     let ledger = Ledger::default();
17
//!     ledger.consume_csv(reader);
18
//!     
19
//!     // Print out the result
20
//!     println!("{}", ledger);
21
//! }
22
//! ```
23
24
use crate::{
25
    parse::{parse_header, parse_transaction, Transaction},
26
    LedgerErr,
27
};
28
use std::{
29
    collections::{BTreeMap, HashMap},
30
    fmt::{self, Display},
31
    io::{BufRead, BufReader, Read},
32
};
33
34
13
#[derive(Default, 
Debug1
)]
35
pub struct Ledger {
36
    /// The list of client accounts.
37
    pub clients: HashMap<u16, ClientData>,
38
    /// The list of transactions. Note: This is a nieve implementation of transaction storage,
39
    /// requiring all transactions to be stored in memory. Due to there being no maximum limmit to
40
    /// how old a transaction can be for a `hold` to be applied, all transactions must be addressable.
41
    pub transactions: BTreeMap<u32, i64>,
42
}
43
44
/// An individual client account.
45
1
#[derive(Debug)]
46
pub struct ClientData {
47
    held: BTreeMap<u32, i64>,
48
    available: i64,
49
    total: i64,
50
    locked: bool,
51
}
52
53
impl Ledger {
54
    #[inline]
55
    /// Consume a `BufReader` that contains a csv file of transactions.
56
    pub fn consume_csv<T>(&mut self, mut reader: BufReader<T>) -> Result<(), LedgerErr>
57
    where
58
        T: Read,
59
    {
60
12
        validate_header(&mut reader)
?4
;
61
62
18
        for (index, line) in 
reader.lines().enumerate()8
{
63
18
            let 
res17
= line.map_err(LedgerErr::Reading)
?1
; // map_err is used to provide better debug info
64
17
            if !res.trim().is_empty() {
65
13
                match parse_transaction(&res)
66
13
                    .map_err(|err| 
LedgerErr::from_parse(err, index + 2)1
)
?1
Unexecuted instantiation: <csv_ledger::ledger::Ledger>::consume_csv::<std::io::cursor::Cursor<&[u8; 1usize]>>::{closure#0}
<csv_ledger::ledger::Ledger>::consume_csv::<std::io::cursor::Cursor<&str>>::{closure#0}
Line
Count
Source
66
1
                    .map_err(|err| LedgerErr::from_parse(err, index + 2))?
Unexecuted instantiation: <csv_ledger::ledger::Ledger>::consume_csv::<csv_ledger::ledger::ledger::TestReader>::{closure#0}
Unexecuted instantiation: <csv_ledger::ledger::Ledger>::consume_csv::<csv_ledger::ledger::ledger::TestReaderTwo>::{closure#0}
Unexecuted instantiation: <csv_ledger::ledger::Ledger>::consume_csv::<std::fs::File>::{closure#0}
67
                {
68
1
                    Transaction::Withdrawal(id, tx, amount) => {
69
1
                        self.insert_transaction(id, tx, -amount) // Negative amounts for withdrawals
70
                    }
71
7
                    Transaction::Deposit(id, tx, amount) => self.insert_transaction(id, tx, amount),
72
2
                    Transaction::Dispute(id, tx) => self.hold(id, tx),
73
1
                    Transaction::Resolve(id, tx) => self.resolve(id, tx),
74
1
                    Transaction::Chargeback(id, tx) => self.chageback(id, tx),
75
                }
76
4
            }
77
        }
78
79
6
        Ok(())
80
12
    }
<csv_ledger::ledger::Ledger>::consume_csv::<csv_ledger::ledger::ledger::TestReader>
Line
Count
Source
60
1
        validate_header(&mut reader)?;
61
62
0
        for (index, line) in reader.lines().enumerate() {
63
0
            let res = line.map_err(LedgerErr::Reading)?; // map_err is used to provide better debug info
64
0
            if !res.trim().is_empty() {
65
0
                match parse_transaction(&res)
66
0
                    .map_err(|err| LedgerErr::from_parse(err, index + 2))?
67
                {
68
0
                    Transaction::Withdrawal(id, tx, amount) => {
69
0
                        self.insert_transaction(id, tx, -amount) // Negative amounts for withdrawals
70
                    }
71
0
                    Transaction::Deposit(id, tx, amount) => self.insert_transaction(id, tx, amount),
72
0
                    Transaction::Dispute(id, tx) => self.hold(id, tx),
73
0
                    Transaction::Resolve(id, tx) => self.resolve(id, tx),
74
0
                    Transaction::Chargeback(id, tx) => self.chageback(id, tx),
75
                }
76
0
            }
77
        }
78
79
0
        Ok(())
80
1
    }
<csv_ledger::ledger::Ledger>::consume_csv::<std::io::cursor::Cursor<&str>>
Line
Count
Source
60
3
        validate_header(&mut reader)
?1
;
61
62
12
        for (index, line) in 
reader.lines().enumerate()2
{
63
12
            let res = line.map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
64
12
            if !res.trim().is_empty() {
65
8
                match parse_transaction(&res)
66
8
                    .map_err(|err| LedgerErr::from_parse(err, index + 2))
?1
67
                {
68
1
                    Transaction::Withdrawal(id, tx, amount) => {
69
1
                        self.insert_transaction(id, tx, -amount) // Negative amounts for withdrawals
70
                    }
71
2
                    Transaction::Deposit(id, tx, amount) => self.insert_transaction(id, tx, amount),
72
2
                    Transaction::Dispute(id, tx) => self.hold(id, tx),
73
1
                    Transaction::Resolve(id, tx) => self.resolve(id, tx),
74
1
                    Transaction::Chargeback(id, tx) => self.chageback(id, tx),
75
                }
76
4
            }
77
        }
78
79
1
        Ok(())
80
3
    }
<csv_ledger::ledger::Ledger>::consume_csv::<csv_ledger::ledger::ledger::TestReaderTwo>
Line
Count
Source
60
1
        validate_header(&mut reader)
?0
;
61
62
1
        for (index, line) in reader.lines().enumerate() {
63
1
            let 
res0
= line.map_err(LedgerErr::Reading)?; // map_err is used to provide better debug info
64
0
            if !res.trim().is_empty() {
65
0
                match parse_transaction(&res)
66
0
                    .map_err(|err| LedgerErr::from_parse(err, index + 2))?
67
                {
68
0
                    Transaction::Withdrawal(id, tx, amount) => {
69
0
                        self.insert_transaction(id, tx, -amount) // Negative amounts for withdrawals
70
                    }
71
0
                    Transaction::Deposit(id, tx, amount) => self.insert_transaction(id, tx, amount),
72
0
                    Transaction::Dispute(id, tx) => self.hold(id, tx),
73
0
                    Transaction::Resolve(id, tx) => self.resolve(id, tx),
74
0
                    Transaction::Chargeback(id, tx) => self.chageback(id, tx),
75
                }
76
0
            }
77
        }
78
79
0
        Ok(())
80
1
    }
<csv_ledger::ledger::Ledger>::consume_csv::<std::fs::File>
Line
Count
Source
60
6
        validate_header(&mut reader)
?1
;
61
62
5
        for (index, line) in reader.lines().enumerate() {
63
5
            let res = line.map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
64
5
            if !res.trim().is_empty() {
65
5
                match parse_transaction(&res)
66
5
                    .map_err(|err| LedgerErr::from_parse(err, index + 2))
?0
67
                {
68
0
                    Transaction::Withdrawal(id, tx, amount) => {
69
0
                        self.insert_transaction(id, tx, -amount) // Negative amounts for withdrawals
70
                    }
71
5
                    Transaction::Deposit(id, tx, amount) => self.insert_transaction(id, tx, amount),
72
0
                    Transaction::Dispute(id, tx) => self.hold(id, tx),
73
0
                    Transaction::Resolve(id, tx) => self.resolve(id, tx),
74
0
                    Transaction::Chargeback(id, tx) => self.chageback(id, tx),
75
                }
76
0
            }
77
        }
78
79
5
        Ok(())
80
6
    }
<csv_ledger::ledger::Ledger>::consume_csv::<std::io::cursor::Cursor<&[u8; 1usize]>>
Line
Count
Source
60
1
        validate_header(&mut reader)?;
61
62
0
        for (index, line) in reader.lines().enumerate() {
63
0
            let res = line.map_err(LedgerErr::Reading)?; // map_err is used to provide better debug info
64
0
            if !res.trim().is_empty() {
65
0
                match parse_transaction(&res)
66
0
                    .map_err(|err| LedgerErr::from_parse(err, index + 2))?
67
                {
68
0
                    Transaction::Withdrawal(id, tx, amount) => {
69
0
                        self.insert_transaction(id, tx, -amount) // Negative amounts for withdrawals
70
                    }
71
0
                    Transaction::Deposit(id, tx, amount) => self.insert_transaction(id, tx, amount),
72
0
                    Transaction::Dispute(id, tx) => self.hold(id, tx),
73
0
                    Transaction::Resolve(id, tx) => self.resolve(id, tx),
74
0
                    Transaction::Chargeback(id, tx) => self.chageback(id, tx),
75
                }
76
0
            }
77
        }
78
79
0
        Ok(())
80
1
    }
81
82
    /// Insert a new transaction
83
    ///
84
    /// Example:
85
    /// ```rust
86
    /// const ledger = Ledger::default();
87
    ///
88
    /// // Deposit
89
    /// ledger.insert_transaction(1,1,10.0);
90
    ///
91
    /// // Withdrawal
92
    /// ledger.insert_transaction(1,2,-10.0);
93
    /// ```
94
    pub fn insert_transaction(&mut self, client_id: u16, transaction_id: u32, amount: i64) {
95
15
        if let Some(
client3
) = self.clients.get_mut(&client_id) {
96
3
            if !client.locked {
97
2
                client.total += amount;
98
2
                client.available += amount;
99
2
                self.transactions.insert(transaction_id, amount);
100
2
            }
1
101
12
        } else {
102
12
            self.clients.insert(client_id, ClientData::new(amount));
103
12
            self.transactions.insert(transaction_id, amount);
104
12
        }
105
15
    }
106
107
    /// Opens a dispute on a transaction.
108
    pub fn hold(&mut self, client_id: u16, transaction_id: u32) {
109
        // Discard any incorrect inputs
110
7
        if let Some(
client6
) = self.clients.get_mut(&client_id) {
111
6
            if let Some(
amount5
) = self.transactions.remove(&transaction_id) {
112
5
                {
113
5
                    client.available -= amount;
114
5
                    client.held.insert(transaction_id, amount);
115
5
                }
116
1
            }
117
1
        }
118
7
    }
119
120
    /// Resolves a disputed transaction - adds disputed transaction's value back to the available funds.
121
    pub fn resolve(&mut self, client_id: u16, transaction_id: u32) {
122
        // Discard any incorrect inputs
123
4
        if let Some(
client3
) = self.clients.get_mut(&client_id) {
124
3
            if let Some(
amount2
) = client.held.remove(&transaction_id) {
125
2
                client.available += amount;
126
2
            }
1
127
1
        }
128
4
    }
129
130
    /// Peform a chargeback on a disputed transaction -
131
    pub fn chageback(&mut self, client_id: u16, transaction_id: u32) {
132
        // Discard any incorrect inputs
133
4
        if let Some(
client3
) = self.clients.get_mut(&client_id) {
134
3
            if let Some(
amount2
) = client.held.remove(&transaction_id) {
135
2
                client.total -= amount;
136
2
                client.locked = true;
137
2
            }
1
138
1
        }
139
4
    }
140
}
141
142
impl Display for Ledger {
143
7
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144
7
        write!(
145
7
            f,
146
7
            "client, available, held, total, locked{}",
147
7
            self.clients
148
7
                .iter()
149
8
                .fold(String::new(), |acc, (key, value)| format!(
150
8
                    "{acc}\n{key}, {value}"
151
8
                ))
152
7
        )
153
7
    }
154
}
155
156
/// Validate the header of the csv file.
157
17
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
158
17
where
159
17
    T: Read,
160
17
{
161
17
    let mut buf = String::new();
162
17
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?2
; // map_err is used to provide better debug info
163
15
    parse_header(&buf).map_err(|err| 
LedgerErr::Parse(err.to_string(), 1)6
)
?6
;
Unexecuted instantiation: csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReader>::{closure#0}
Unexecuted instantiation: csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReaderTwo>::{closure#0}
Unexecuted instantiation: csv_ledger::ledger::validate_header::<csv_ledger::ledger::validate_header::TestReader>::{closure#0}
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&str>>::{closure#0}
Line
Count
Source
163
4
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&[u8; 1usize]>>::{closure#0}
Line
Count
Source
163
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
csv_ledger::ledger::validate_header::<std::fs::File>::{closure#0}
Line
Count
Source
163
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
164
9
    Ok(())
165
17
}
csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReader>
Line
Count
Source
157
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
158
1
where
159
1
    T: Read,
160
1
{
161
1
    let mut buf = String::new();
162
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)?; // map_err is used to provide better debug info
163
0
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
164
0
    Ok(())
165
1
}
csv_ledger::ledger::validate_header::<std::fs::File>
Line
Count
Source
157
6
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
158
6
where
159
6
    T: Read,
160
6
{
161
6
    let mut buf = String::new();
162
6
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
163
6
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))
?1
;
164
5
    Ok(())
165
6
}
csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReaderTwo>
Line
Count
Source
157
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
158
1
where
159
1
    T: Read,
160
1
{
161
1
    let mut buf = String::new();
162
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
163
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))
?0
;
164
1
    Ok(())
165
1
}
csv_ledger::ledger::validate_header::<csv_ledger::ledger::validate_header::TestReader>
Line
Count
Source
157
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
158
1
where
159
1
    T: Read,
160
1
{
161
1
    let mut buf = String::new();
162
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)?; // map_err is used to provide better debug info
163
0
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
164
0
    Ok(())
165
1
}
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&[u8; 1usize]>>
Line
Count
Source
157
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
158
1
where
159
1
    T: Read,
160
1
{
161
1
    let mut buf = String::new();
162
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
163
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
164
0
    Ok(())
165
1
}
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&str>>
Line
Count
Source
157
7
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
158
7
where
159
7
    T: Read,
160
7
{
161
7
    let mut buf = String::new();
162
7
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
163
7
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))
?4
;
164
3
    Ok(())
165
7
}
166
167
impl ClientData {
168
14
    fn new(amount: i64) -> Self {
169
14
        ClientData {
170
14
            held: BTreeMap::new(),
171
14
            available: amount,
172
14
            total: amount,
173
14
            locked: false,
174
14
        }
175
14
    }
176
}
177
178
impl Display for ClientData {
179
8
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180
8
        write!(
181
8
            f,
182
8
            "{}, {}, {}, {}",
183
8
            dp_string(self.available),
184
8
            dp_string(self.held.values().sum()),
185
8
            dp_string(self.total),
186
8
            self.locked
187
8
        )
188
8
    }
189
}
190
191
/// Convert a i64 to a string with four decimal places (eg val / 100)
192
30
fn dp_string(amount: i64) -> String {
193
30
    format!("{}.{:04}", amount / 10000, amount % 10000)
194
30
}
195
196
#[cfg(test)]
197
mod dp_string {
198
    use super::dp_string;
199
1
    #[test]
200
1
    fn test_dp_string() {
201
1
        assert_eq!(dp_string(0), "0.0000");
202
1
        assert_eq!(dp_string(1), "0.0001");
203
1
        assert_eq!(dp_string(10), "0.0010");
204
1
        assert_eq!(dp_string(100), "0.0100");
205
1
        assert_eq!(dp_string(1000), "0.1000");
206
1
        assert_eq!(dp_string(10000), "1.0000");
207
1
    }
208
}
209
210
#[cfg(test)]
211
mod validate_header {
212
    use super::validate_header;
213
    use std::io::{BufReader, Cursor, Error, ErrorKind, Read};
214
215
    struct TestReader {}
216
217
    impl Read for TestReader {
218
1
        fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
219
1
            Err(Error::new(ErrorKind::InvalidData, "Something went wrong."))
220
1
        }
221
    }
222
223
1
    #[test]
224
1
    fn ok() {
225
1
        validate_header(&mut BufReader::new(Cursor::new("type, client, tx, amount"))).unwrap();
226
1
    }
227
228
1
    #[test]
229
1
    fn err_runthrough() {
230
1
        validate_header(&mut BufReader::new(TestReader {})).unwrap_err();
231
1
        validate_header(&mut BufReader::new(Cursor::new(""))).unwrap_err();
232
1
        validate_header(&mut BufReader::new(Cursor::new("\n"))).unwrap_err();
233
1
        validate_header(&mut BufReader::new(Cursor::new("type,"))).unwrap_err();
234
1
    }
235
}
236
237
#[cfg(test)]
238
mod client_data {
239
    use super::ClientData;
240
241
1
    #[test]
242
1
    fn debug() {
243
1
        let data = ClientData::new(10);
244
1
245
1
        assert_eq!(
246
1
            format!("{:?}", data),
247
1
            "ClientData { held: {}, available: 10, total: 10, locked: false }"
248
1
        );
249
1
    }
250
}
251
252
#[cfg(test)]
253
mod ledger {
254
    use super::{ClientData, Ledger};
255
    use std::collections::BTreeMap;
256
    use std::io::{BufReader, Cursor, Error, ErrorKind, Read};
257
258
    struct TestReader {}
259
260
    impl Read for TestReader {
261
1
        fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
262
1
            Err(Error::new(ErrorKind::InvalidData, "Something went wrong."))
263
1
        }
264
    }
265
266
    struct TestReaderTwo<'a> {
267
        inner: Cursor<&'a str>,
268
        state: bool,
269
    }
270
271
    // Fail after second read
272
    impl Read for TestReaderTwo<'_> {
273
2
        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
274
2
            if self.state {
275
1
                Err(Error::new(ErrorKind::InvalidData, "Something went wrong."))
276
            } else {
277
1
                self.state = true;
278
1
                Ok(self.inner.read(buf).unwrap())
279
            }
280
2
        }
281
    }
282
283
1
    #[test]
284
1
    fn ok_consume() {
285
1
        let mut ledger = Ledger::default();
286
1
287
1
        ledger
288
1
            .consume_csv(BufReader::new(Cursor::new(
289
1
                "type, client, tx, amount
290
1
                
291
1
                deposit, 1, 1, 20.0
292
1
                withdrawal,1,2,10.0
293
1
                dispute,1,2,
294
1
                resolve,1,2,
295
1
            
296
1
                deposit,2,3,113.1112
297
1
                dispute,2,3,
298
1
                chargeback,2,3,
299
1
                
300
1
                ",
301
1
            )))
302
1
            .unwrap();
303
1
304
1
        let result = ledger.to_string();
305
1
        let mut lines = result.lines();
306
1
307
1
        assert_eq!(
308
1
            lines.next().unwrap(),
309
1
            "client, available, held, total, locked"
310
1
        );
311
312
1
        let accounts = vec![
313
1
            "1, 10.0000, 0.0000, 10.0000, false",
314
1
            "2, 0.0000, 0.0000, 0.0000, true",
315
1
        ];
316
1
317
1
        assert!(accounts.contains(&lines.next().unwrap()));
318
1
        assert!(accounts.contains(&lines.next().unwrap()));
319
1
        assert!(lines.next().is_none())
320
1
    }
321
322
1
    #[test]
323
1
    fn err_consume_runthrough() {
324
1
        let mut ledger = Ledger::default();
325
1
326
1
        ledger
327
1
            .consume_csv(BufReader::new(Cursor::new("")))
328
1
            .unwrap_err();
329
1
330
1
        ledger
331
1
            .consume_csv(BufReader::new(Cursor::new(&[0x0])))
332
1
            .unwrap_err();
333
1
334
1
        ledger
335
1
            .consume_csv(BufReader::new(TestReader {}))
336
1
            .unwrap_err();
337
1
338
1
        ledger
339
1
            .consume_csv(BufReader::new(TestReaderTwo {
340
1
                inner: Cursor::new("type, client, tx, amount\n"),
341
1
                state: false,
342
1
            }))
343
1
            .unwrap_err();
344
1
345
1
        ledger
346
1
            .consume_csv(BufReader::new(Cursor::new("type, client, tx, amount\n123")))
347
1
            .unwrap_err();
348
1
    }
349
350
1
    #[test]
351
1
    fn insert_transaction() {
352
1
        let mut client_2 = ClientData::new(0);
353
1
        client_2.locked = true;
354
1
355
1
        let mut ledger = Ledger {
356
1
            clients: [(2_u16, client_2)].into_iter().collect(),
357
1
            transactions: BTreeMap::new(),
358
1
        };
359
1
360
1
        ledger.insert_transaction(1, 1, 1);
361
1
        ledger.insert_transaction(1, 2, 1);
362
1
363
1
        // Locked
364
1
        ledger.insert_transaction(2, 3, 1);
365
1
366
1
        let client_1 = ledger.clients.get(&1).unwrap();
367
1
        let client_2 = ledger.clients.get(&2).unwrap();
368
1
        assert_eq!(client_1.available, 2);
369
1
        assert_eq!(client_2.available, 0);
370
1
        assert_eq!(client_1.total, 2);
371
1
        assert_eq!(client_2.total, 0);
372
1
    }
373
374
1
    #[test]
375
1
    fn dispute() {
376
1
        let mut ledger = Ledger::default();
377
1
378
1
        ledger.insert_transaction(1, 1, 1);
379
1
        ledger.hold(1, 1);
380
1
        ledger.hold(2, 1);
381
1
        ledger.hold(1, 2);
382
1
383
1
        let c = ledger.clients.get(&1).unwrap();
384
1
385
1
        assert_eq!(ledger.clients.len(), 1);
386
1
        assert_eq!(c.held.get(&1).unwrap(), &1_i64);
387
1
        assert_eq!(c.available, 0_i64);
388
1
    }
389
390
1
    #[test]
391
1
    fn resolve() {
392
1
        let mut ledger = Ledger::default();
393
1
394
1
        ledger.insert_transaction(1, 1, 1);
395
1
        ledger.hold(1, 1);
396
1
        ledger.resolve(1, 1);
397
1
        ledger.resolve(2, 1);
398
1
        ledger.resolve(1, 2);
399
1
400
1
        let c = ledger.clients.get(&1).unwrap();
401
1
        assert_eq!(c.held.len(), 0);
402
1
        assert_eq!(c.available, 1_i64);
403
1
    }
404
405
1
    #[test]
406
1
    fn chargeback() {
407
1
        let mut ledger = Ledger::default();
408
1
409
1
        ledger.insert_transaction(1, 1, 1);
410
1
        ledger.hold(1, 1);
411
1
        ledger.chageback(1, 1);
412
1
        ledger.chageback(2, 1);
413
1
        ledger.chageback(1, 2);
414
1
415
1
        let c = ledger.clients.get(&1).unwrap();
416
1
        assert_eq!(c.held.len(), 0);
417
1
        assert_eq!(c.total, 0_i64);
418
1
        assert_eq!(c.locked, true);
419
1
    }
420
421
1
    #[test]
422
1
    fn debug() {
423
1
        assert_eq!(
424
1
            format!("{:?}", Ledger::default()),
425
1
            "Ledger { clients: {}, transactions: {} }"
426
1
        )
427
1
    }
428
429
1
    #[test]
430
1
    fn display() {
431
1
        let mut ledger = Ledger::default();
432
1
        ledger.insert_transaction(1, 1, 1);
433
1
        assert_eq!(
434
1
            format!("{}", ledger),
435
1
            "client, available, held, total, locked\n1, 0.0001, 0.0000, 0.0001, false"
436
1
        );
437
1
    }
438
}