Coverage Report

Created: 2022-08-31 14:14

/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::<csv_ledger::ledger::ledger::TestReaderTwo>::{closure#0}
Unexecuted instantiation: <csv_ledger::ledger::Ledger>::consume_csv::<std::fs::File>::{closure#0}
Unexecuted instantiation: <csv_ledger::ledger::Ledger>::consume_csv::<csv_ledger::ledger::ledger::TestReader>::{closure#0}
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))?
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::<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
    }
<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::<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
    }
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(
amount), Some(client5
)) = (
111
7
            self.transactions.get(&transaction_id),
112
7
            self.clients.get_mut(&client_id),
113
7
        ) {
114
5
            client.available -= amount;
115
5
            client.held.insert(transaction_id, *amount);
116
5
        }
2
117
7
    }
118
119
    /// Resolves a disputed transaction - adds disputed transaction's value back to the available funds.
120
    pub fn resolve(&mut self, client_id: u16, transaction_id: u32) {
121
        // Discard any incorrect inputs
122
4
        if let Some(
client3
) = self.clients.get_mut(&client_id) &&
123
3
            let Some(
amount2
) = client.held.remove(&transaction_id)
124
2
        {
125
2
            client.available += amount;
126
2
        }
127
4
    }
128
129
    /// Peform a chargeback on a disputed transaction -
130
    pub fn chageback(&mut self, client_id: u16, transaction_id: u32) {
131
        // Discard any incorrect inputs
132
4
        if let Some(
client3
) = self.clients.get_mut(&client_id) &&
133
3
            let Some(
amount2
) = client.held.remove(&transaction_id)
134
2
        {
135
2
            client.total -= amount;
136
2
            client.locked = true;
137
2
        }
138
4
    }
139
}
140
141
impl Display for Ledger {
142
7
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143
7
        write!(
144
7
            f,
145
7
            "client, available, held, total, locked{}",
146
7
            self.clients
147
7
                .iter()
148
8
                .fold(String::new(), |acc, (key, value)| format!(
149
8
                    "{acc}\n{key}, {value}"
150
8
                ))
151
7
        )
152
7
    }
153
}
154
155
/// Validate the header of the csv file.
156
17
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
157
17
where
158
17
    T: Read,
159
17
{
160
17
    let mut buf = String::new();
161
17
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?2
; // map_err is used to provide better debug info
162
15
    parse_header(&buf).map_err(|err| 
LedgerErr::Parse(err.to_string(), 1)6
)
?6
;
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&str>>::{closure#0}
Line
Count
Source
162
4
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
Unexecuted instantiation: csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReader>::{closure#0}
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&[u8; 1usize]>>::{closure#0}
Line
Count
Source
162
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
Unexecuted instantiation: csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReaderTwo>::{closure#0}
csv_ledger::ledger::validate_header::<std::fs::File>::{closure#0}
Line
Count
Source
162
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
Unexecuted instantiation: csv_ledger::ledger::validate_header::<csv_ledger::ledger::validate_header::TestReader>::{closure#0}
163
9
    Ok(())
164
17
}
csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReader>
Line
Count
Source
156
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
157
1
where
158
1
    T: Read,
159
1
{
160
1
    let mut buf = String::new();
161
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)?; // map_err is used to provide better debug info
162
0
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
163
0
    Ok(())
164
1
}
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&str>>
Line
Count
Source
156
7
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
157
7
where
158
7
    T: Read,
159
7
{
160
7
    let mut buf = String::new();
161
7
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
162
7
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))
?4
;
163
3
    Ok(())
164
7
}
csv_ledger::ledger::validate_header::<csv_ledger::ledger::validate_header::TestReader>
Line
Count
Source
156
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
157
1
where
158
1
    T: Read,
159
1
{
160
1
    let mut buf = String::new();
161
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)?; // map_err is used to provide better debug info
162
0
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
163
0
    Ok(())
164
1
}
csv_ledger::ledger::validate_header::<std::fs::File>
Line
Count
Source
156
6
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
157
6
where
158
6
    T: Read,
159
6
{
160
6
    let mut buf = String::new();
161
6
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
162
6
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))
?1
;
163
5
    Ok(())
164
6
}
csv_ledger::ledger::validate_header::<std::io::cursor::Cursor<&[u8; 1usize]>>
Line
Count
Source
156
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
157
1
where
158
1
    T: Read,
159
1
{
160
1
    let mut buf = String::new();
161
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
162
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))?;
163
0
    Ok(())
164
1
}
csv_ledger::ledger::validate_header::<csv_ledger::ledger::ledger::TestReaderTwo>
Line
Count
Source
156
1
fn validate_header<T>(reader: &mut BufReader<T>) -> Result<(), LedgerErr>
157
1
where
158
1
    T: Read,
159
1
{
160
1
    let mut buf = String::new();
161
1
    reader.read_line(&mut buf).map_err(LedgerErr::Reading)
?0
; // map_err is used to provide better debug info
162
1
    parse_header(&buf).map_err(|err| LedgerErr::Parse(err.to_string(), 1))
?0
;
163
1
    Ok(())
164
1
}
165
166
impl ClientData {
167
14
    fn new(amount: i64) -> Self {
168
14
        ClientData {
169
14
            held: BTreeMap::new(),
170
14
            available: amount,
171
14
            total: amount,
172
14
            locked: false,
173
14
        }
174
14
    }
175
}
176
177
impl Display for ClientData {
178
8
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179
8
        write!(
180
8
            f,
181
8
            "{}, {}, {}, {}",
182
8
            dp_string(self.available),
183
8
            dp_string(self.held.values().sum()),
184
8
            dp_string(self.total),
185
8
            self.locked
186
8
        )
187
8
    }
188
}
189
190
/// Convert a i64 to a string with four decimal places (eg val / 100)
191
30
fn dp_string(amount: i64) -> String {
192
30
    format!("{}.{:04}", amount / 10000, amount % 10000)
193
30
}
194
195
#[cfg(test)]
196
mod dp_string {
197
    use super::dp_string;
198
1
    #[test]
199
1
    fn test_dp_string() {
200
1
        assert_eq!(dp_string(0), "0.0000");
201
1
        assert_eq!(dp_string(1), "0.0001");
202
1
        assert_eq!(dp_string(10), "0.0010");
203
1
        assert_eq!(dp_string(100), "0.0100");
204
1
        assert_eq!(dp_string(1000), "0.1000");
205
1
        assert_eq!(dp_string(10000), "1.0000");
206
1
    }
207
}
208
209
#[cfg(test)]
210
mod validate_header {
211
    use super::validate_header;
212
    use std::io::{BufReader, Cursor, Error, ErrorKind, Read};
213
214
    struct TestReader {}
215
216
    impl Read for TestReader {
217
1
        fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
218
1
            Err(Error::new(ErrorKind::InvalidData, "Something went wrong."))
219
1
        }
220
    }
221
222
1
    #[test]
223
1
    fn ok() {
224
1
        validate_header(&mut BufReader::new(Cursor::new("type, client, tx, amount"))).unwrap();
225
1
    }
226
227
1
    #[test]
228
1
    fn err_runthrough() {
229
1
        validate_header(&mut BufReader::new(TestReader {})).unwrap_err();
230
1
        validate_header(&mut BufReader::new(Cursor::new(""))).unwrap_err();
231
1
        validate_header(&mut BufReader::new(Cursor::new("\n"))).unwrap_err();
232
1
        validate_header(&mut BufReader::new(Cursor::new("type,"))).unwrap_err();
233
1
    }
234
}
235
236
#[cfg(test)]
237
mod client_data {
238
    use super::ClientData;
239
240
1
    #[test]
241
1
    fn debug() {
242
1
        let data = ClientData::new(10);
243
1
244
1
        assert_eq!(
245
1
            format!("{:?}", data),
246
1
            "ClientData { held: {}, available: 10, total: 10, locked: false }"
247
1
        );
248
1
    }
249
}
250
251
#[cfg(test)]
252
mod ledger {
253
    use super::{ClientData, Ledger};
254
    use std::collections::BTreeMap;
255
    use std::io::{BufReader, Cursor, Error, ErrorKind, Read};
256
257
    struct TestReader {}
258
259
    impl Read for TestReader {
260
1
        fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
261
1
            Err(Error::new(ErrorKind::InvalidData, "Something went wrong."))
262
1
        }
263
    }
264
265
    struct TestReaderTwo<'a> {
266
        inner: Cursor<&'a str>,
267
        state: bool,
268
    }
269
270
    // Fail after second read
271
    impl Read for TestReaderTwo<'_> {
272
2
        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
273
2
            if self.state {
274
1
                Err(Error::new(ErrorKind::InvalidData, "Something went wrong."))
275
            } else {
276
1
                self.state = true;
277
1
                Ok(self.inner.read(buf).unwrap())
278
            }
279
2
        }
280
    }
281
282
1
    #[test]
283
1
    fn ok_consume() {
284
1
        let mut ledger = Ledger::default();
285
1
286
1
        ledger
287
1
            .consume_csv(BufReader::new(Cursor::new(
288
1
                "type, client, tx, amount
289
1
                
290
1
                deposit, 1, 1, 20.0
291
1
                withdrawal,1,2,10.0
292
1
                dispute,1,2,
293
1
                resolve,1,2,
294
1
            
295
1
                deposit,2,3,113.1112
296
1
                dispute,2,3,
297
1
                chargeback,2,3,
298
1
                
299
1
                ",
300
1
            )))
301
1
            .unwrap();
302
1
303
1
        let result = ledger.to_string();
304
1
        let mut lines = result.lines();
305
1
306
1
        assert_eq!(
307
1
            lines.next().unwrap(),
308
1
            "client, available, held, total, locked"
309
1
        );
310
311
1
        let accounts = vec![
312
1
            "1, 10.0000, 0.0000, 10.0000, false",
313
1
            "2, 0.0000, 0.0000, 0.0000, true",
314
1
        ];
315
1
316
1
        assert!(accounts.contains(&lines.next().unwrap()));
317
1
        assert!(accounts.contains(&lines.next().unwrap()));
318
1
        assert!(lines.next().is_none())
319
1
    }
320
321
1
    #[test]
322
1
    fn err_consume_runthrough() {
323
1
        let mut ledger = Ledger::default();
324
1
325
1
        ledger
326
1
            .consume_csv(BufReader::new(Cursor::new("")))
327
1
            .unwrap_err();
328
1
329
1
        ledger
330
1
            .consume_csv(BufReader::new(Cursor::new(&[0x0])))
331
1
            .unwrap_err();
332
1
333
1
        ledger
334
1
            .consume_csv(BufReader::new(TestReader {}))
335
1
            .unwrap_err();
336
1
337
1
        ledger
338
1
            .consume_csv(BufReader::new(TestReaderTwo {
339
1
                inner: Cursor::new("type, client, tx, amount\n"),
340
1
                state: false,
341
1
            }))
342
1
            .unwrap_err();
343
1
344
1
        ledger
345
1
            .consume_csv(BufReader::new(Cursor::new("type, client, tx, amount\n123")))
346
1
            .unwrap_err();
347
1
    }
348
349
1
    #[test]
350
1
    fn insert_transaction() {
351
1
        let mut client_2 = ClientData::new(0);
352
1
        client_2.locked = true;
353
1
354
1
        let mut ledger = Ledger {
355
1
            clients: [(2_u16, client_2)].into_iter().collect(),
356
1
            transactions: BTreeMap::new(),
357
1
        };
358
1
359
1
        ledger.insert_transaction(1, 1, 1);
360
1
        ledger.insert_transaction(1, 2, 1);
361
1
362
1
        // Locked
363
1
        ledger.insert_transaction(2, 3, 1);
364
1
365
1
        let client_1 = ledger.clients.get(&1).unwrap();
366
1
        let client_2 = ledger.clients.get(&2).unwrap();
367
1
        assert_eq!(client_1.available, 2);
368
1
        assert_eq!(client_2.available, 0);
369
1
        assert_eq!(client_1.total, 2);
370
1
        assert_eq!(client_2.total, 0);
371
1
    }
372
373
1
    #[test]
374
1
    fn dispute() {
375
1
        let mut ledger = Ledger::default();
376
1
377
1
        ledger.insert_transaction(1, 1, 1);
378
1
        ledger.hold(1, 1);
379
1
        ledger.hold(2, 1);
380
1
        ledger.hold(1, 2);
381
1
382
1
        let c = ledger.clients.get(&1).unwrap();
383
1
384
1
        assert_eq!(ledger.clients.len(), 1);
385
1
        assert_eq!(c.held.get(&1).unwrap(), &1_i64);
386
1
        assert_eq!(c.available, 0_i64);
387
1
    }
388
389
1
    #[test]
390
1
    fn resolve() {
391
1
        let mut ledger = Ledger::default();
392
1
393
1
        ledger.insert_transaction(1, 1, 1);
394
1
        ledger.hold(1, 1);
395
1
        ledger.resolve(1, 1);
396
1
        ledger.resolve(2, 1);
397
1
        ledger.resolve(1, 2);
398
1
399
1
        let c = ledger.clients.get(&1).unwrap();
400
1
        assert_eq!(c.held.len(), 0);
401
1
        assert_eq!(c.available, 1_i64);
402
1
    }
403
404
1
    #[test]
405
1
    fn chargeback() {
406
1
        let mut ledger = Ledger::default();
407
1
408
1
        ledger.insert_transaction(1, 1, 1);
409
1
        ledger.hold(1, 1);
410
1
        ledger.chageback(1, 1);
411
1
        ledger.chageback(2, 1);
412
1
        ledger.chageback(1, 2);
413
1
414
1
        let c = ledger.clients.get(&1).unwrap();
415
1
        assert_eq!(c.held.len(), 0);
416
1
        assert_eq!(c.total, 0_i64);
417
1
        assert_eq!(c.locked, true);
418
1
    }
419
420
1
    #[test]
421
1
    fn debug() {
422
1
        assert_eq!(
423
1
            format!("{:?}", Ledger::default()),
424
1
            "Ledger { clients: {}, transactions: {} }"
425
1
        )
426
1
    }
427
428
1
    #[test]
429
1
    fn display() {
430
1
        let mut ledger = Ledger::default();
431
1
        ledger.insert_transaction(1, 1, 1);
432
1
        assert_eq!(
433
1
            format!("{}", ledger),
434
1
            "client, available, held, total, locked\n1, 0.0001, 0.0000, 0.0001, false"
435
1
        );
436
1
    }
437
}