Skip to content

Commit

Permalink
Merge pull request #9 from mdb/integration-tests
Browse files Browse the repository at this point in the history
add integration tests
  • Loading branch information
mdb committed Mar 24, 2023
2 parents a2a01d6 + 5409753 commit fb019b9
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ jobs:
with:
go-version-file: go.mod
- run: make
- run: make int-test
env:
MAGIC_SEAWEED_API_KEY: ${{ secrets.MAGIC_SEAWEED_API_KEY }}
3 changes: 3 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
with:
go-version-file: go.mod
- run: make
- run: make int-test
env:
MAGIC_SEAWEED_API_KEY: ${{ secrets.MAGIC_SEAWEED_API_KEY }}

release:
needs: test
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
SOURCE=./...
VERSION=0.5.0
SOURCE=./
VERSION=0.6.0

.DEFAULT_GOAL := test

test: vet test-fmt
go test -v -coverprofile=coverage.out -race $(SOURCE)
.PHONY: test

int-test: vet test-fmt
go test -v -race ./internal/integrationtest
.PHONY: int-test

vet:
go vet $(SOURCE)
.PHONY: vet
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![CI](https://github.com/mdb/seaweed/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/mdb/seaweed/actions/workflows/ci.yaml)
[![CI](https://github.com/mdb/seaweed/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/mdb/seaweed/actions/workflows/ci.yaml) [![PkgGoDev](https://pkg.go.dev/badge/github.com/mdb/seaweed)](https://pkg.go.dev/github.com/mdb/seaweed) [![Go Report Card](https://goreportcard.com/badge/github.com/mdb/seaweed)](https://goreportcard.com/report/github.com/mdb/seaweed)

# seaweed

Expand Down Expand Up @@ -28,6 +28,7 @@ Use a customized client:

```go
client := seaweed.Client{
BaseURL: "https://magicseaweed.com",
APIKey: "YOUR_KEY",
HTTPClient: &http.Client{}, // *http.Client
Logger: logrus.New(logging.INFO), // *logrus.Logger
Expand Down
69 changes: 53 additions & 16 deletions client.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
// Package seaweed provides a Magic Seaweed API client.
package seaweed

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"

"github.com/sirupsen/logrus"
)

// Clock is a clock interface used to report the current time.
// Clock is a clock interface used to report the current time such that the
// Client#Today and Client#Tomorrow methods can return the proper forecasts
// relative to the current time.
// It exists largely for testing purposes.
type Clock interface {
Now() time.Time
}
Expand All @@ -25,23 +31,40 @@ func (RealClock) Now() time.Time {

// Client represents a seaweed API client
type Client struct {
APIKey string
// BaseURL is the targeted Magic Seaweed API base URL, such as https://magicseaweed.com.
BaseURL string
// APIKey is a Magic Seaweed API key.
APIKey string
// HTTPClient is a *http.Client.
HTTPClient *http.Client
Logger *logrus.Logger
clock Clock
// Logger is a *logrus.Logger.
Logger *logrus.Logger
// clock is a seaweed.Clock used to report the current time/date such that the
// Client#Tomorrow and Client#Today methods can return the proper forecasts
// relative to the current time.
clock Clock
}

// NewClient takes an API key and returns a seaweed API client
func NewClient(APIKey string) *Client {
return &Client{
"https://magicseaweed.com",
APIKey,
&http.Client{},
logrus.New(),
RealClock{},
}
}

// Forecast fetches the full, multi-day forecast for a given spot.
// Forecast fetches the full, multi-day forecast for a given spot ID.
//
// A spot's ID appears in its URL. For example, Ocean City, NJ's spot ID is 391:
// https://magicseaweed.com/Ocean-City-NJ-Surf-Report/391/
//
// Note that the Magic Seaweed API may respond with an HTTP status code of 200
// and a response body reporting an error (see APIError). Forecast attempts to
// handle such instances by returning an error surfacing the response body error
// message.
func (c *Client) Forecast(spot string) ([]Forecast, error) {
forecasts, err := c.getForecast(spot)
if err != nil {
Expand All @@ -51,7 +74,7 @@ func (c *Client) Forecast(spot string) ([]Forecast, error) {
return forecasts, nil
}

// Today fetches the today's forecast for a given spot.
// Today fetches the today's forecast for a given spot ID.
func (c *Client) Today(spot string) ([]Forecast, error) {
today := []Forecast{}
now := c.clock.Now().UTC()
Expand All @@ -69,7 +92,7 @@ func (c *Client) Today(spot string) ([]Forecast, error) {
return today, nil
}

// Tomorrow fetches tomorrow's forecast for a given spot.
// Tomorrow fetches tomorrow's forecast for a given spot ID.
func (c *Client) Tomorrow(spot string) ([]Forecast, error) {
tomorrow := []Forecast{}
tomorrowD := c.clock.Now().UTC().AddDate(0, 0, 1)
Expand All @@ -87,7 +110,7 @@ func (c *Client) Tomorrow(spot string) ([]Forecast, error) {
return tomorrow, nil
}

// Weekend fetches the weekend's forecast for a given spot.
// Weekend fetches the weekend's forecast for a given spot ID.
func (c *Client) Weekend(spot string) ([]Forecast, error) {
weekendFs := []Forecast{}
forecasts, err := c.Forecast(spot)
Expand All @@ -105,19 +128,30 @@ func (c *Client) Weekend(spot string) ([]Forecast, error) {
}

func (c *Client) getForecast(spotID string) ([]Forecast, error) {
url := fmt.Sprintf("https://magicseaweed.com/api/%s/forecast/?spot_id=%s", c.APIKey, spotID)
url := fmt.Sprintf("%s/api/%s/forecast/?spot_id=%s", c.BaseURL, c.APIKey, spotID)
forecasts := []Forecast{}
body, err := c.get(url)
if err != nil {
return forecasts, err
}

err = json.Unmarshal(body, &forecasts)
if err != nil {
return forecasts, err
}
switch {
case strings.Contains(string(body), "error_response"):
var errResp APIError
err = json.Unmarshal(body, &errResp)
if err != nil {
return forecasts, err
}

return forecasts, nil
return forecasts, errors.New(errResp.ErrorResponse.ErrorMsg)
default:
err = json.Unmarshal(body, &forecasts)
if err != nil {
return forecasts, err
}

return forecasts, nil
}
}

func (c *Client) get(url string) ([]byte, error) {
Expand All @@ -132,13 +166,16 @@ func (c *Client) get(url string) ([]byte, error) {
return nil, err
}

sanitizedURL := strings.Replace(url, c.APIKey, "<REDACTED>", 1)
sanitizedURL = strings.Replace(sanitizedURL, c.BaseURL, "", 1)

if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("%s returned HTTP status code %d", url, resp.StatusCode)
err = fmt.Errorf("GET %s returned HTTP status code %d", sanitizedURL, resp.StatusCode)
}

l := c.Logger.WithFields(
logrus.Fields{
"url": url,
"url": sanitizedURL,
"http_status": resp.StatusCode,
"body": string(body),
})
Expand Down
57 changes: 48 additions & 9 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import (
"github.com/sirupsen/logrus"
)

var resp string
var (
resp string
errorResp string
)

func TestMain(m *testing.M) {
content, err := ioutil.ReadFile("testdata/response.json")
Expand All @@ -25,6 +28,13 @@ func TestMain(m *testing.M) {

resp = string(content)

errContent, err := ioutil.ReadFile("testdata/error.json")
if err != nil {
log.Fatal(err)
}

errorResp = string(errContent)

exitVal := m.Run()
os.Exit(exitVal)
}
Expand All @@ -51,6 +61,7 @@ func testServerAndClient(code int, body string) (*httptest.Server, *Client) {
httpClient := &http.Client{Transport: tr}

client := &Client{
server.URL,
"fakeKey",
httpClient,
logrus.New(),
Expand Down Expand Up @@ -93,11 +104,27 @@ func TestForecast(t *testing.T) {
body: resp,
code: 500,
expectForecastCount: 0,
expectError: errors.New("https://magicseaweed.com/api/fakeKey/forecast/?spot_id=123 returned HTTP status code 500"),
expectError: errors.New("GET /api/<REDACTED>/forecast/?spot_id=123 returned HTTP status code 500"),
}, {
desc: "when the response code is OK but the response body specifies an error",
body: errorResp,
code: 200,
expectForecastCount: 0,
expectError: errors.New("Unable to authenticate request: Ensure your API key is passed correctly. Refer to the API docs."),
}, {
desc: "when the response code is OK and the response body indicates an error, but with unexpected JSON",
body: "error_response{",
code: 200,
expectForecastCount: 0,
expectError: errors.New("invalid character 'e' looking for beginning of value"),
}}

for _, test := range tests {
for i := range tests {
test := tests[i]

t.Run(test.desc, func(t *testing.T) {
t.Parallel()

server, c := testServerAndClient(test.code, test.body)
defer server.Close()
forecasts, err := c.Forecast("123")
Expand Down Expand Up @@ -152,11 +179,15 @@ func TestWeekend(t *testing.T) {
body: resp,
code: 500,
expectForecastCount: 0,
expectError: errors.New("https://magicseaweed.com/api/fakeKey/forecast/?spot_id=123 returned HTTP status code 500"),
expectError: errors.New("GET /api/<REDACTED>/forecast/?spot_id=123 returned HTTP status code 500"),
}}

for _, test := range tests {
for i := range tests {
test := tests[i]

t.Run(test.desc, func(t *testing.T) {
t.Parallel()

server, c := testServerAndClient(test.code, test.body)
defer server.Close()
forecasts, err := c.Weekend("123")
Expand Down Expand Up @@ -211,11 +242,15 @@ func TestToday(t *testing.T) {
body: resp,
code: 500,
expectForecastCount: 0,
expectError: errors.New("https://magicseaweed.com/api/fakeKey/forecast/?spot_id=123 returned HTTP status code 500"),
expectError: errors.New("GET /api/<REDACTED>/forecast/?spot_id=123 returned HTTP status code 500"),
}}

for _, test := range tests {
for i := range tests {
test := tests[i]

t.Run(test.desc, func(t *testing.T) {
t.Parallel()

server, c := testServerAndClient(test.code, test.body)
defer server.Close()
forecasts, err := c.Today("123")
Expand Down Expand Up @@ -270,11 +305,15 @@ func TestTomorrow(t *testing.T) {
body: resp,
code: 500,
expectForecastCount: 0,
expectError: errors.New("https://magicseaweed.com/api/fakeKey/forecast/?spot_id=123 returned HTTP status code 500"),
expectError: errors.New("GET /api/<REDACTED>/forecast/?spot_id=123 returned HTTP status code 500"),
}}

for _, test := range tests {
for i := range tests {
test := tests[i]

t.Run(test.desc, func(t *testing.T) {
t.Parallel()

server, c := testServerAndClient(test.code, test.body)
defer server.Close()
forecasts, err := c.Tomorrow("123")
Expand Down
Loading

0 comments on commit fb019b9

Please sign in to comment.