281 lines
9.9 KiB
Go
281 lines
9.9 KiB
Go
// 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)
|
|
}
|
|
}
|