// At Digits, we care deeply about Test-Driven Development. If the build is "green", // we ship it. We do not have a QA team, and we do not have an Ops team. Everyone is // responsible for their code end-to-end, which means we're highly motivated to make it // as stable and as secure as possible. We do this with automated tests. // // If you run `go test` on the command line, you'll see that we have some problems here. // Only one test in the project passes! // // This is where you come in. // // We need you to go through the tests and help us fix them. Only spend 3 hours on this, // and don't worry about fixing all of them. We've designed it so there is more work here // than it is possible for anyone to finish in 3 hours, so don't worry about it. The tests // build on each other, so they will be generally easier if you go in order, but feel free // to jump around if you get stuck on one of them. // // Let's be clear: you are NOT being evaluated on how many tests you fix. We want to see // thoughtful, easy-to-read, well-performing, secure, and well-documented code. At Digits, // it's quality over quantity every day of the week. // // One last tip for you: You won't need to change any code in this file. Start by familiarizing // yourself with the rest of the codebase, because that's where all your work will be. // // Best of luck, and we really appreciate all your help getting these tests to pass! Remember to // stop after 3 hours wherever you are -- we know how tempting it is to keep going :-) // // -- Team Digits package analyzer_test import ( "fmt" "testing" analyzer "github.com/digits/WorkSample-Go" ) // This work sample revolves around parsing and analyzing credit card transactions, // which is something pretty core to what we work on at Digits! // // This is the Analyzer instance that the tests in this file will run against. func newAnalyzer(t *testing.T) *analyzer.Analyzer { txs, err := analyzer.TransactionsFromFile("testdata/transactions.json") if err != nil { t.Fatal(err) } anz := analyzer.New() anz.Add(txs) return anz } // The SampleTransactionData contains 188 distinct transactions. // // Let's validate that the analyzer sees all of them. // // - Note: This test should already be working correctly. No need to do anything here. func TestAnalyzer_NewFromFile(t *testing.T) { anz := newAnalyzer(t) if n := anz.TransactionCount(); n != 188 { t.Errorf("expected 188 transactions, got %d", n) } } // Alright, your work starts here! // // The SampleTransactionData contains $48,501.17 worth of transactions, but something // here isn't working. Let's make sure `Transactions.Sum()` is implemented correctly. func TestAnalyzer_TransctionsSum(t *testing.T) { anz := newAnalyzer(t) if amount := anz.TransactionsAmount(); amount.Rounded(2) != 48_501.17 { t.Errorf("expected 48,501.17 in transactions, got %f", amount) } } // Next, let's find the largest transaction. Hmm, this one doesn't seem to be working either. func TestAnalyzer_LargestTransaction(t *testing.T) { anz := newAnalyzer(t) trn, err := anz.LargestTransaction() if err != nil { t.Errorf("expected nil got %v", err) } if trn.Amount != 3_831.2 { t.Errorf("expected largest transaction amount of $3,831.2, got %f", trn.Amount) } } // Our analysis won't be accurate unless we make sure to de-duplicate transactions. // // Revamp the way we instantiate our Analyzer so that duplicate Transactions are discarded. // What data structure can you employ to make this easy? // // - Note: Once you get this working, double check that the previous tests still pass! func TestAnalyzer_DuplicatesExcluded(t *testing.T) { anz := newAnalyzer(t) txs, err := analyzer.TransactionsFromFile("testdata/transactions.json") if err != nil { t.Fatal(err) } // Re-adding the existing transactions should be a no-op. anz.Add(txs) if n := anz.TransactionCount(); n != 188 { t.Errorf("expected 188 transactions, got %d", n) } } // Now that we know our data has no duplicates, we can get on to some real analysis. // // Let's group the transactions by category to see how often we shopped at Hardware Stores, // and how much we spent on Electrical Contractors. func TestAnalyzer_GroupByCategory(t *testing.T) { anz := newAnalyzer(t) categories := anz.ByCategory() if trns := categories["HARDWARE STORES"]; len(trns) != 12 { t.Errorf("expected category 'HARDWARE STORES' to have 12 transactions, got %d", len(trns)) } if trns := categories["ELECTRICAL CONTRACTORS"]; len(trns) != 1 { t.Errorf("expected category 'ELECTRICAL CONTRACTORS' to have 1 transactions, got %d", len(trns)) } if trns := categories["ELECTRICAL CONTRACTORS"]; trns.Sum() != 89.0 { t.Errorf("expected category 'ELECTRICAL CONTRACTORS' to have a total amount of $89.00, got %f", trns.Sum()) } } // Let's take a break from Transactions, and start preparing our printed reports. // // To do this, we're going to need to be able to attractively print dollar amounts. // // Hop over to `amount_test.go` and get those tests straightened out before continuing. // // With currencies looking sharp, it's time we're able to print whole trasnactions. // // Take a look at `transaction_test.go` and make sure we're properly formatting transactions! // Finally, we're ready for our first report! We'll define "big spenders" as people who, // in aggregate across their transactions, have spent more than 2 standard deviations above // the mean spender. // // - Note: No hints this time. You know what to do! :) func TestAnalyzer_BigSpendersReport(t *testing.T) { anz := newAnalyzer(t) const sampleReport = ` Digits Big Spenders Report ------------------------------------------------------ Name Amount ------------------------------------------------------ R. Bowers $5,877.00 T. Munday $4,556.60 W. Edwards $3,248.79 P. Wood $2,888.36 ------------------------------------------------------` report := anz.BigSpenderReport() if report != sampleReport { t.Errorf("expected big spender report of:\n%s\n\nGot:\n%s", sampleReport, report) } } // Playing with 188 hard-coded transactions is fun but not very interesting. // Now, it's time to load transactions from the network, ~1000 at a time! // // - Warning: You will need an active network connection to complete the next few challenges. func TestAnalyzer_TransactionsFromURLs(t *testing.T) { const transactionsURL = "https://assets.digits.com/uploads/hiring/swift-work-sample/transactions-aa.json" txs, err := analyzer.TransactionsFromURLs(transactionsURL) if err != nil { t.Errorf("failed adding transactions from URL (%s): %s", transactionsURL, err) } anz := newAnalyzer(t) if n := anz.Add(txs); n != 980 { t.Errorf("expected 980 new transactions, got %d", n) } t.Error("WARNING: error in unittests: assumes mismatched descriptions are the same transaction: contact@blapointe.com") if n := anz.TransactionCount(); n != 1168 { t.Errorf("expected 1168 transactions, got %d", n) } const sampleReport = ` Digits Big Spenders Report ------------------------------------------------------ Name Amount ------------------------------------------------------ J. Heusel $17,832.64 G. Hines $14,787.33 T. Munday $12,967.93 M. Al-Harake $7,454.14 R. Bowers $6,368.09 P. Lloyd $5,986.06 M. Tornakian $5,395.37 C. Miller $5,025.00 B. Ritthaler $4,897.54 J. Potts $4,800.00 J. Teel $4,685.82 K. Baum $4,533.40 K. Boyd $4,488.84 A. Hunt $4,424.64 A. Kindred $4,329.10 V. Yarbrough-Tessman $4,077.30 G. Deaver $3,835.00 ------------------------------------------------------` report := anz.BigSpenderReport() if report != sampleReport { t.Errorf("expected big spender report of:\n%s\n\nGot:\n%s", sampleReport, report) } } // Now that you have one file working, it's time to load Transactions from 10 at once! // // For this challenge, dive into channels, goroutines, and sync.WaitGroup to perform the // requests in parallel. func TestAnalyzer_TransactionsFromURLsConcurrent(t *testing.T) { urls := []string{"aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai", "aj"} for i, suffix := range urls { urls[i] = fmt.Sprintf("https://assets.digits.com/uploads/hiring/swift-work-sample/transactions-%s.json", suffix) } txs, err := analyzer.TransactionsFromURLs(urls...) if err != nil { t.Errorf("failed adding transactions from URL (%v): %s", urls, err) } anz := newAnalyzer(t) if n := anz.Add(txs); n != 9661 { t.Errorf("expected 9661 transactions, got %d", n) } if n := anz.TransactionCount(); n != 9849 { t.Errorf("expected 9849 transactions, got %d", n) } const sampleReport = ` Digits Big Spenders Report ------------------------------------------------------ Name Amount ------------------------------------------------------ M. Tornakian $149,097.79 G. Hines $139,769.73 J. Heusel $108,557.26 T. Munday $66,767.24 R. Bowers $38,639.01 S. Fitzpatrick $32,320.23 V. Yarbrough-Tessman $29,655.08 A. Steanson $28,500.78 A. Kindred $24,623.06 M. Clark $24,132.51 J. Teel $23,729.02 S. Earls $22,238.02 A. Ropers $22,033.28 D. Turner $21,205.21 D. Connelly $20,458.27 D. Whitefield $20,102.42 S. Clawson $19,945.81 M. Al-Harake $19,510.29 ------------------------------------------------------` report := anz.BigSpenderReport() if report != sampleReport { t.Errorf("expected big spender report of:\n%s\n\nGot:\n%s", sampleReport, report) } }