Skip to content

Commit

Permalink
Merge pull request #18 from mattevans/feat/fix-tests
Browse files Browse the repository at this point in the history
refactor(historical): rates and historical now sep services
  • Loading branch information
mattevans authored Aug 29, 2021
2 parents b177963 + 2ef24c6 commit e8415df
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 108 deletions.
11 changes: 1 addition & 10 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,2 @@
# These are supported funding model platforms

github: [mattevans]
patreon: mattevansnz
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with a single custom sponsorship URL
52 changes: 48 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ if err != nil {
**List**

```go
// List latest forex rates. This will use AUD (defined
// when intializing the client) as the base.
// List latest forex rates. This will use AUD (defined when intializing the client) as the base.
rsp, err := client.Rates.List()
if err != nil {
return err
Expand All @@ -96,14 +95,59 @@ if err != nil {
**Get**

```go
// Get latest forex rate for NZD. This will use AUD (defined
// when intializing the client) as the base.
// Get latest forex rate for NZD. This will use AUD (defined when intializing the client) as the base.
rsp, err := client.Rates.Get("NZD")
if err != nil {
return err
}
```

```
1.045545
```
---

## Historical Rates

**List**

```go
historicalDate := time.Now().AddDate(0, -5, -3)

// List historical forex rates. This will use AUD (defined when intializing the client) as the base.
rsp, err := client.Historical.List(historicalDate)
if err != nil {
return err
}
```

```json
{
"rates":{
"AED": 2.702388,
"AFN": 48.893275,
"ALL": 95.142814,
"AMD": 356.88691,
...
},
"base": "AUD"
}
```

---

**Get**

```go
historicalDate := time.Now().AddDate(0, -5, -3)

// Get historical forex rate for NZD. This will use AUD (defined when intializing the client) as the base.
rsp, err := client.Rates.Get("NZD", historicalDate)
if err != nil {
return err
}
```

```
1.045545
```
Expand Down
23 changes: 14 additions & 9 deletions cache.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dinero

import (
"fmt"
"time"

cache "github.com/patrickmn/go-cache"
Expand All @@ -24,34 +25,38 @@ func NewCacheService(
}

// Get will return our in-memory stored currency/rates.
func (s *CacheService) Get(base string) (*RateResponse, bool) {
if x, found := s.store.Get(base); found {
func (s *CacheService) Get(base string, date time.Time) (*RateResponse, bool) {
if x, found := s.store.Get(getCacheKey(base, date)); found {
return x.(*RateResponse), found
}
return nil, false
}

// Store will store our currency/rates in-memory.
func (s *CacheService) Store(rsp *RateResponse) {
func (s *CacheService) Store(rsp *RateResponse, date time.Time) {
// Set a stored timestamp.
rsp.Timestamp = time.Now().Unix()

s.store.Set(
rsp.Base,
getCacheKey(rsp.Base, date),
rsp,
cache.DefaultExpiration,
)
}

// IsExpired checks whether or not rate stored is expired.
func (s *CacheService) IsExpired(base string) bool {
if _, found := s.store.Get(base); found {
// IsExpired checks whether the rate stored is expired.
func (s *CacheService) IsExpired(base string, date time.Time) bool {
if _, found := s.store.Get(getCacheKey(base, date)); found {
return false
}
return true
}

// Expire will expire the cache for a given base currency.
func (s *CacheService) Expire(base string) {
s.store.Delete(base)
func (s *CacheService) Expire(base string, date time.Time) {
s.store.Delete(getCacheKey(base, date))
}

func getCacheKey(base string, date time.Time) string {
return fmt.Sprintf("%s_%s", base, date.Format("2006-01-02"))
}
6 changes: 3 additions & 3 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestCache(t *testing.T) {
}

// Fetch results again
response2, ok := client.Cache.Get("AUD")
response2, ok := client.Cache.Get("AUD", time.Now())
if !ok {
t.Fatalf("Expected response when fetching from cache for base currency AUD, got: %v", response2)
}
Expand All @@ -37,10 +37,10 @@ func TestCache(t *testing.T) {
Expect(first).To(MatchJSON(second))

// Expire the cache
client.Cache.Expire("AUD")
client.Cache.Expire("AUD", time.Now())

// Fetch results again (from the cache), now it's cleared.
response2, _ = client.Cache.Get("AUD")
response2, _ = client.Cache.Get("AUD", time.Now())

// Should be nothing.
Expect(response2).Should(BeNil())
Expand Down
4 changes: 2 additions & 2 deletions currencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
. "github.com/onsi/gomega"
)

// TestListCurrencies will test listing currencies from the OXR api.
func TestListCurrencies(t *testing.T) {
// TestCurrencies_List will test listing currencies from the OXR api.
func TestCurrencies_List(t *testing.T) {
// Register the test.
RegisterTestingT(t)

Expand Down
16 changes: 12 additions & 4 deletions dinero.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dinero
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
Expand All @@ -14,11 +15,16 @@ import (
)

const (
packageVersion = "0.7.1"
packageVersion = "0.8.0"
backendURL = "https://openexchangerates.org"
userAgent = "dinero/" + packageVersion
)

var (
// ErrRatesNotFound is returned if no rate can be found for a given currency code.
ErrRatesNotFound = errors.New("no rates found for code")
)

// Client holds a connection to the OXR API.
type Client struct {
// client is the HTTP client the package will use for requests.
Expand All @@ -31,9 +37,10 @@ type Client struct {
BackendURL *url.URL

// Services used for communicating with the API.
Rates *RatesService
Currencies *CurrenciesService
Cache *CacheService
Rates *RatesService
HistoricalRates *HistoricalRatesService
Currencies *CurrenciesService
Cache *CacheService
}

// NewClient creates a new Client with the appropriate connection details and
Expand All @@ -57,6 +64,7 @@ func NewClient(appID, baseCurrency string, expiry time.Duration) *Client {

// Init services.
c.Rates = NewRatesService(c, baseCurrency)
c.HistoricalRates = NewHistoricalRatesService(c, baseCurrency)
c.Currencies = NewCurrenciesService(c)
c.Cache = NewCacheService(c, store)

Expand Down
13 changes: 12 additions & 1 deletion dinero_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ package dinero

import (
"net/url"
"os"
"testing"
"time"

. "github.com/onsi/gomega"
)

func TestNewRequest(t *testing.T) {
const (
// Default currency returned by API calls that do not set a base currency.
defaultCurrency = "USD"
// "Free" OXR plans don't allow switching of base currency.
// > 403 Changing the API `base` currency is available for Developer, Enterprise and Unlimited plan clients.
setBaseNotAllowedResponsePrefix = "403"
)

var appID = os.Getenv("OPEN_EXCHANGE_APP_ID")

func Test_NewRequest(t *testing.T) {
// Register the test.
NewWithT(t)

Expand Down
115 changes: 115 additions & 0 deletions historical_rates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package dinero

import (
"errors"
"fmt"
"net/url"
"time"
)

const (
historicalAPIPath = "historical/%s.json"
)

// HistoricalRatesService handles historical rate request/responses.
type HistoricalRatesService struct {
client *Client
baseCurrency string
}

// NewHistoricalRatesService creates a new handler for this service.
func NewHistoricalRatesService(
client *Client,
baseCurrency string,
) *HistoricalRatesService {
return &HistoricalRatesService{
client: client,
baseCurrency: baseCurrency,
}
}

// HistoricalRatesResponse holds our forex rates for a given base currency
type HistoricalRatesResponse struct {
Rates map[string]float64 `json:"rates"`
Base string `json:"base"`
Timestamp int64 `json:"timestamp"`
}

// List will fetch all the latest rates for the base currency either from the store or the OXR api.
func (s *HistoricalRatesService) List(date time.Time) (*RateResponse, error) {
// If we have cached results, use them.
if results, ok := s.client.Cache.Get(s.baseCurrency, date); ok {
return results, nil
}

// No cached results, go and fetch them.
if err := s.fetch(date); err != nil {
return nil, err
}

return s.List(date)
}

// Get will fetch a single rate for a given currency either from the store or the OXR api.
func (s *HistoricalRatesService) Get(code string, date time.Time) (*float64, error) {
// No code passed, let them know!
if code == "" {
return nil, errors.New("currency code must be passed")
}

// If we have cached results, use them.
if results, ok := s.client.Cache.Get(s.baseCurrency, date); ok {
if single, ok := results.Rates[code]; ok {
return &single, nil
}
return nil, ErrRatesNotFound
}

// No cached results, go and fetch them.
if err := s.fetch(date); err != nil {
return nil, err
}

return s.Get(code, date)
}

// GetBaseCurrency will return the baseCurrency.
func (s *HistoricalRatesService) GetBaseCurrency() string {
return s.baseCurrency
}

// SetBaseCurrency will set the base currency to be used for requests.
func (s *HistoricalRatesService) SetBaseCurrency(base string) {
s.baseCurrency = base
}

func (s *HistoricalRatesService) fetch(date time.Time) error {
// Build request.
// add `base` query param if it is not empty
params := url.Values{}
if s.baseCurrency != "" {
params.Set("base", s.baseCurrency)
}
request, err := s.client.NewRequest(
"GET",
fmt.Sprintf(historicalAPIPath, date.Format("2006-01-02")),
params,
nil,
)
if err != nil {
return err
}

// Make request
var latest *RateResponse
if _, err := s.client.Do(request, &latest); err != nil {
return err
}

s.SetBaseCurrency(latest.Base)

// Store our results.
s.client.Cache.Store(latest, date)

return nil
}
Loading

0 comments on commit e8415df

Please sign in to comment.