Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.

refactor: Accounts API method optimisations #17

Merged
merged 3 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 18 additions & 24 deletions api/accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@ import (
)

func GetAccountUid(accessToken string) (string, error) {
accountsJSON, err := getAccountsJSON(accessToken)

if err != nil {
return "", err
}

accounts, err := decodeToAccounts(accountsJSON)
accounts, err := getAccounts(accessToken)

if err != nil {
return "", err
Expand All @@ -28,14 +22,14 @@ func GetAccountUid(accessToken string) (string, error) {
return getFirstAccountUid(accounts)
}

func getAccountsJSON(accessToken string) (string, error) {
log.Debug("Querying the accounts API to get a list of all accounts.")
func getAccounts(accessToken string) ([]account, error) {
log.Debug("Getting a list of all accounts from the Accounts API endpoint.")
// We use the more verbose NewRequest so we can add headers/query parameters.
request, err := http.NewRequest("GET", api.BaseUrl+"/accounts", nil)

if err != nil {
log.WithError(err).Error("Failed to create a HTTP request.")
return "", err
return []account{}, err
}

// Adding the headers used for authentication.
Expand All @@ -49,27 +43,27 @@ func getAccountsJSON(accessToken string) (string, error) {

if err != nil {
log.WithError(err).Error("Failed to perform the HTTP request.")
return "", err
return []account{}, err
}

body, err := io.ReadAll(response.Body)
accountsJSON, err := io.ReadAll(response.Body)

if err != nil {
log.WithError(err).Error("Failed to read the HTTP response's body.")
return "", err
return []account{}, err
}

if response.StatusCode != 200 {
err = errors.New("the HTTP status code was '" + response.Status + "' not 200")
log.WithFields(log.Fields{
"err": err,
"body": string(body),
}).Error("Failed to successfully query the Accounts API.")
return "", err
"body": string(accountsJSON),
}).Error("Failed to get from the Accounts API endpoint.")
return []account{}, err
}

log.Debug("Successfully quiered the accounts API.")
return string(body), nil
log.Debug("Successfully got a list of all accounts from the Accounts API endpoint.")
return decodeToAccounts(accountsJSON)
}

type accountsAPIResponse struct {
Expand All @@ -85,29 +79,29 @@ type account struct {
Name string `json:"name"`
}

func decodeToAccounts(accountsJSON string) ([]account, error) {
log.Debug("Decoding the JSON response from the accounts API.")
decoder := json.NewDecoder(bytes.NewReader([]byte(accountsJSON)))
func decodeToAccounts(accountsJSON []byte) ([]account, error) {
log.Debug("Decoding the JSON response from the Accounts API endpoint.")
decoder := json.NewDecoder(bytes.NewReader(accountsJSON))
decoder.DisallowUnknownFields()

var accountsAPIResponse accountsAPIResponse
err := decoder.Decode(&accountsAPIResponse)

if err != nil {
log.WithError(err).Error("Failed to parse the APIs response from JSON.")
log.WithError(err).Error("Failed to parse the JSON.")
return []account{}, err
}

accounts := accountsAPIResponse.Accounts
log.Debugf("Decoded %#v accounts from the JSON response from the accounts API.", len(accounts))
log.Debugf("Decoded %#v accounts from the JSON response from the Accounts API endpoint.", len(accounts))
return accounts, nil
}

func getFirstAccountUid(accounts []account) (string, error) {
// See Technical Decisions in the README.md.
if len(accounts) == 0 {
err := errors.New("accounts: accounts array is empty")
log.WithError(err).Error("The accounts APIs response had no accounts.")
log.WithError(err).Error("The Accounts APIs endpoint returned no accounts.")
return "", err
}

Expand Down
28 changes: 14 additions & 14 deletions api/accounts/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (

func TestDecodeToAccountsWithNoAccounts(t *testing.T) {
// Given
response := `{"accounts":[]}`
accountsJSON := `{"accounts":[]}`
expectedNumberOfAccounts := 0

// When
returnedAccounts, err := decodeToAccounts(response)
returnedAccounts, err := decodeToAccounts([]byte(accountsJSON))

// Then
assert.NoError(t, err)
Expand All @@ -21,11 +21,11 @@ func TestDecodeToAccountsWithNoAccounts(t *testing.T) {

func TestDecodeToAccountsWithSingleAccount(t *testing.T) {
// Given
response := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
accountsJSON := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
expectedNumberOfAccounts := 1

// When
returnedAccounts, err := decodeToAccounts(response)
returnedAccounts, err := decodeToAccounts([]byte(accountsJSON))

// Then
assert.NoError(t, err)
Expand All @@ -34,11 +34,11 @@ func TestDecodeToAccountsWithSingleAccount(t *testing.T) {

func TestDecodeToAccountsWithMultipleAccounts(t *testing.T) {
// Given
response := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"},{"accountUid":"account-2","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
accountsJSON := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"},{"accountUid":"account-2","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
expectedNumberOfAccounts := 2

// When
returnedAccounts, err := decodeToAccounts(response)
returnedAccounts, err := decodeToAccounts([]byte(accountsJSON))

// Then
assert.NoError(t, err)
Expand All @@ -47,10 +47,10 @@ func TestDecodeToAccountsWithMultipleAccounts(t *testing.T) {

func TestDecodeToAccountsErrorsWithUnexpectedFormat(t *testing.T) {
// Given
response := `{"error":"invalid_token","error_description":"Could not validate provided access token"}`
accountsJSON := `{"error":"invalid_token","error_description":"Could not validate provided access token"}`

// When
_, err := decodeToAccounts(response)
_, err := decodeToAccounts([]byte(accountsJSON))

// Then
assert.Error(t, err)
Expand All @@ -59,8 +59,8 @@ func TestDecodeToAccountsErrorsWithUnexpectedFormat(t *testing.T) {

func TestGetFirstAccountUidWithNoAccounts(t *testing.T) {
// Given
response := `{"accounts":[]}`
accounts, err := decodeToAccounts(response)
accountsJSON := `{"accounts":[]}`
accounts, err := decodeToAccounts([]byte(accountsJSON))
assert.NoError(t, err)
expectedErrorMessage := "accounts: accounts array is empty"

Expand All @@ -74,8 +74,8 @@ func TestGetFirstAccountUidWithNoAccounts(t *testing.T) {

func TestGetFirstAccountUidWithSingleAccount(t *testing.T) {
// Given
response := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
accounts, err := decodeToAccounts(response)
accountsJSON := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
accounts, err := decodeToAccounts([]byte(accountsJSON))
assert.NoError(t, err)
expectedAccountUid := "account-1"

Expand All @@ -89,8 +89,8 @@ func TestGetFirstAccountUidWithSingleAccount(t *testing.T) {

func TestGetFirstAccountUidWithMultipleAccounts(t *testing.T) {
// Given
response := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"},{"accountUid":"account-2","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
accounts, err := decodeToAccounts(response)
accountsJSON := `{"accounts":[{"accountUid":"account-1","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"},{"accountUid":"account-2","accountType":"PRIMARY","defaultCategory":"category","currency":"GBP","createdAt":"2022-10-11T16:58:48.398Z","name":"Personal"}]}`
accounts, err := decodeToAccounts([]byte(accountsJSON))
assert.NoError(t, err)
expectedAccountUid := "account-1"

Expand Down
42 changes: 18 additions & 24 deletions api/savings/goals/savings_goals.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@ import (
)

func GetSavingsGoalsUid(accessToken string, accountUid string) (string, error) {
savingsGoalsJSON, err := getSavingsGoalsJSON(accessToken, accountUid)

if err != nil {
return "", err
}

savingsGoals, err := decodeToSavingsGoals(savingsGoalsJSON)
savingsGoals, err := getSavingsGoals(accessToken, accountUid)

if err != nil {
return "", err
Expand All @@ -28,14 +22,14 @@ func GetSavingsGoalsUid(accessToken string, accountUid string) (string, error) {
return getFirstSavingsGoalUid(savingsGoals), nil
}

func getSavingsGoalsJSON(accessToken string, accountUid string) (string, error) {
log.Debug("Querying the savings goals API to get a list of all savings goals.")
func getSavingsGoals(accessToken string, accountUid string) ([]savingsGoal, error) {
log.Debug("Getting a list of all savings goals from the Savings Goals API endpoint.")
// We use the more verbose NewRequest so we can add headers/query parameters.
request, err := http.NewRequest("GET", api.BaseUrl+"/account/"+accountUid+"/savings-goals", nil)

if err != nil {
log.WithError(err).Error("Failed to create a HTTP request.")
return "", err
return []savingsGoal{}, err
}

// Adding the headers used for authentication.
Expand All @@ -49,27 +43,27 @@ func getSavingsGoalsJSON(accessToken string, accountUid string) (string, error)

if err != nil {
log.WithError(err).Error("Failed to perform the HTTP request.")
return "", err
return []savingsGoal{}, err
}

body, err := io.ReadAll(response.Body)
savingsGoalsJSON, err := io.ReadAll(response.Body)

if err != nil {
log.WithError(err).Error("Failed to read the HTTP response's body.")
return "", err
return []savingsGoal{}, err
}

if response.StatusCode != 200 {
err = errors.New("the HTTP status code was '" + response.Status + "' not 200")
log.WithFields(log.Fields{
"err": err,
"body": string(body),
}).Error("Failed to successfully query the Accounts API.")
return "", err
"body": string(savingsGoalsJSON),
}).Error("Failed to get from the Savings Goals API endpoint.")
return []savingsGoal{}, err
}

log.Debug("Successfully quiered the savings goals API.")
return string(body), nil
log.Debug("Successfully got a list of all savings goals from the Savings Goals API endpoint.")
return decodeToSavingsGoals(savingsGoalsJSON)
}

type savingsGoalsAPIResponse struct {
Expand All @@ -89,27 +83,27 @@ type amount struct {
Value int `json:"minorUnits"`
}

func decodeToSavingsGoals(savingsGoalsJSON string) ([]savingsGoal, error) {
log.Debug("Decoding the JSON response from the savings goals API.")
decoder := json.NewDecoder(bytes.NewReader([]byte(savingsGoalsJSON)))
func decodeToSavingsGoals(savingsGoalsJSON []byte) ([]savingsGoal, error) {
log.Debug("Decoding the JSON response from the Savings Goals API endpoint.")
decoder := json.NewDecoder(bytes.NewReader(savingsGoalsJSON))
decoder.DisallowUnknownFields()

var savingsGoalsAPIResponse savingsGoalsAPIResponse
err := decoder.Decode(&savingsGoalsAPIResponse)

if err != nil {
log.WithError(err).Error("Failed to parse the APIs response from JSON.")
log.WithError(err).Error("Failed to parse the JSON.")
return []savingsGoal{}, err
}

savingsGoals := savingsGoalsAPIResponse.SavingsGoals
log.Debugf("Decoded %#v savings goals from the JSON response from the savings goals API.", len(savingsGoals))
log.Debugf("Decoded %#v savings goals from the JSON response from the Savings Goals API endpoint.", len(savingsGoals))
return savingsGoals, nil
}

func getFirstSavingsGoalUid(savingsGoals []savingsGoal) string {
if len(savingsGoals) == 0 {
log.Warn("The savings goals APIs response had no savings goals.")
log.Warn("The Savings Goals APIs endpoint returned no savings goals.")
return ""
}

Expand Down
24 changes: 12 additions & 12 deletions api/savings/goals/savings_goals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (

func TestDecodeToSavingsGoalsWithNoSavingsGoals(t *testing.T) {
// Given
response := `{"savingsGoalList":[]}`
savingsGoalsJSON := `{"savingsGoalList":[]}`
expectedNumberOfSavingsGoals := 0

// When
returnedSavingsGoals, err := decodeToSavingsGoals(response)
returnedSavingsGoals, err := decodeToSavingsGoals([]byte(savingsGoalsJSON))

// Then
assert.NoError(t, err)
Expand All @@ -21,11 +21,11 @@ func TestDecodeToSavingsGoalsWithNoSavingsGoals(t *testing.T) {

func TestDecodeToSavingsGoalsWithOneSavingsGoalNoTarget(t *testing.T) {
// Given
response := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Paris","totalSaved":{"currency":"GBP","minorUnits":0}}]}`
savingsGoalsJSON := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Paris","totalSaved":{"currency":"GBP","minorUnits":0}}]}`
expectedNumberOfSavingsGoals := 1

// When
returnedSavingsGoals, err := decodeToSavingsGoals(response)
returnedSavingsGoals, err := decodeToSavingsGoals([]byte(savingsGoalsJSON))

// Then
assert.NoError(t, err)
Expand All @@ -34,11 +34,11 @@ func TestDecodeToSavingsGoalsWithOneSavingsGoalNoTarget(t *testing.T) {

func TestDecodeToSavingsGoalsWithOneSavingsGoalWithTarget(t *testing.T) {
// Given
response := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Paris","target":{"currency":"GBP","minorUnits":123456},"totalSaved":{"currency":"GBP","minorUnits":123456},"savedPercentage":100}]}`
savingsGoalsJSON := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Paris","target":{"currency":"GBP","minorUnits":123456},"totalSaved":{"currency":"GBP","minorUnits":123456},"savedPercentage":100}]}`
expectedNumberOfSavingsGoals := 1

// When
returnedSavingsGoals, err := decodeToSavingsGoals(response)
returnedSavingsGoals, err := decodeToSavingsGoals([]byte(savingsGoalsJSON))

// Then
assert.NoError(t, err)
Expand All @@ -47,11 +47,11 @@ func TestDecodeToSavingsGoalsWithOneSavingsGoalWithTarget(t *testing.T) {

func TestDecodeToSavingsGoalsWithMultipleSavingsGoals(t *testing.T) {
// Given
response := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Dublin","target":{"currency":"GBP","minorUnits":50000},"totalSaved":{"currency":"GBP","minorUnits":0},"savedPercentage":0},{"savingsGoalUid":"savings-goal-2","name":"Trip to London","totalSaved":{"currency":"GBP","minorUnits":0}},{"savingsGoalUid":"savings-goal-3","name":"Trip to Paris","totalSaved":{"currency":"GBP","minorUnits":20}}]}`
savingsGoalsJSON := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Dublin","target":{"currency":"GBP","minorUnits":50000},"totalSaved":{"currency":"GBP","minorUnits":0},"savedPercentage":0},{"savingsGoalUid":"savings-goal-2","name":"Trip to London","totalSaved":{"currency":"GBP","minorUnits":0}},{"savingsGoalUid":"savings-goal-3","name":"Trip to Paris","totalSaved":{"currency":"GBP","minorUnits":20}}]}`
expectedNumberOfSavingsGoals := 3

// When
returnedSavingsGoals, err := decodeToSavingsGoals(response)
returnedSavingsGoals, err := decodeToSavingsGoals([]byte(savingsGoalsJSON))

// Then
assert.NoError(t, err)
Expand All @@ -60,8 +60,8 @@ func TestDecodeToSavingsGoalsWithMultipleSavingsGoals(t *testing.T) {

func TestGetFirstAccountUidWithNoSavingsGoals(t *testing.T) {
// Given
response := `{"savingsGoalList":[]}`
savingsGoals, err := decodeToSavingsGoals(response)
savingsGoalsJSON := `{"savingsGoalList":[]}`
savingsGoals, err := decodeToSavingsGoals([]byte(savingsGoalsJSON))
assert.NoError(t, err)
expectedSavingsGoalUid := ""

Expand All @@ -74,8 +74,8 @@ func TestGetFirstAccountUidWithNoSavingsGoals(t *testing.T) {

func TestGetFirstAccountUidWithMultipleSavingsGoals(t *testing.T) {
// Given
response := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Dublin","target":{"currency":"GBP","minorUnits":50000},"totalSaved":{"currency":"GBP","minorUnits":0},"savedPercentage":0},{"savingsGoalUid":"savings-goal-2","name":"Trip to London","totalSaved":{"currency":"GBP","minorUnits":0}},{"savingsGoalUid":"savings-goal-3","name":"Trip to Paris","totalSaved":{"currency":"GBP","minorUnits":20}}]}`
savingsGoals, err := decodeToSavingsGoals(response)
savingsGoalsJSON := `{"savingsGoalList":[{"savingsGoalUid":"savings-goal-1","name":"Trip to Dublin","target":{"currency":"GBP","minorUnits":50000},"totalSaved":{"currency":"GBP","minorUnits":0},"savedPercentage":0},{"savingsGoalUid":"savings-goal-2","name":"Trip to London","totalSaved":{"currency":"GBP","minorUnits":0}},{"savingsGoalUid":"savings-goal-3","name":"Trip to Paris","totalSaved":{"currency":"GBP","minorUnits":20}}]}`
savingsGoals, err := decodeToSavingsGoals([]byte(savingsGoalsJSON))
assert.NoError(t, err)
expectedSavingsGoalUid := "savings-goal-1"

Expand Down
Loading