generated from ariel17/golang-base
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds client implementation for football API and test cases (league da…
…ta). Refs #1
- Loading branch information
Showing
4 changed files
with
1,767 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package clients | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"io" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
const BASE_API_URL = "http://api.football-data.org/v4/" | ||
|
||
type Area struct { | ||
ID int64 `json:"id"` | ||
Name string `json:"name"` | ||
Code string `json:"code"` | ||
} | ||
|
||
type League struct { | ||
Area Area `json:"area"` | ||
ID int64 `json:"id"` | ||
Name string `json:"name"` | ||
Code string `json:"code"` | ||
Type string `json:"type"` | ||
} | ||
|
||
// FootballAPIClient is the behavior contract that every implementation must | ||
// comply. It offers access to football-data.org data with handy methods. It is | ||
// NOT a full client implementation but access to required resources. | ||
type FootballAPIClient interface { | ||
|
||
// GetLeagueByCode retrieves league data by its code. | ||
GetLeagueByCode(code string) (*League, error) | ||
} | ||
|
||
// NewFootballAPIClient creates a new instance of real API client. | ||
func NewFootballAPIClient(apiKey string) FootballAPIClient { | ||
if apiKey == "" { | ||
panic("cannot work without a key") | ||
} | ||
return &realAPIClient{ | ||
baseURL: BASE_API_URL, | ||
client: &http.Client{ | ||
Timeout: time.Second, | ||
}, | ||
apiKey: apiKey, | ||
} | ||
} | ||
|
||
type realAPIClient struct { | ||
baseURL string | ||
client *http.Client | ||
apiKey string | ||
} | ||
|
||
func (r *realAPIClient) GetLeagueByCode(code string) (*League, error) { | ||
url := r.baseURL + "/competitions/" + code | ||
|
||
request, _ := http.NewRequest(http.MethodGet, url, nil) | ||
request.Header.Set("X-Auth-Token", r.apiKey) | ||
|
||
response, err := r.client.Do(request) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var body []byte | ||
defer response.Body.Close() | ||
body, err = io.ReadAll(response.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if response.StatusCode != 200 { | ||
return nil, errors.New("failed to retrieve content: " + string(body)) | ||
} | ||
|
||
league := League{} | ||
err = json.Unmarshal(body, &league) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &league, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package clients | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewFootballAPIClient(t *testing.T) { | ||
t.Run("fails without api key", func(t *testing.T) { | ||
defer func() { | ||
if r := recover(); r == nil { | ||
t.Errorf("The code did not panic") | ||
} | ||
}() | ||
NewFootballAPIClient("") | ||
}) | ||
|
||
t.Run("ok", func(t *testing.T) { | ||
NewFootballAPIClient("abc123") | ||
}) | ||
} | ||
|
||
func TestGetLeagueByCode(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
code string | ||
statusCode int | ||
isSuccess bool | ||
}{ | ||
{"ok", "PL", 200, true}, | ||
{"not found", "XXX", 404, false}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
apiContent := loadGoldenFile(t.Name()) | ||
server := newTestServer("/competitions/"+tc.code, tc.statusCode, apiContent) | ||
defer server.Close() | ||
|
||
c := &realAPIClient{ | ||
baseURL: server.URL, | ||
client: &http.Client{ | ||
Timeout: time.Second, | ||
}, | ||
apiKey: "abc123", | ||
} | ||
response, err := c.GetLeagueByCode(tc.code) | ||
assert.Equal(t, err == nil, tc.isSuccess) | ||
assert.Equal(t, response != nil, tc.isSuccess) | ||
|
||
if tc.isSuccess { | ||
assert.Equal(t, "Premier League", response.Name) | ||
assert.Equal(t, "PL", response.Code) | ||
assert.Equal(t, "England", response.Area.Name) | ||
} else { | ||
assert.True(t, strings.Contains(err.Error(), "failed to retrieve content:")) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// loadGoldenFiles uses the test name to load a JSON value as expected result. | ||
func loadGoldenFile(testName string) []byte { | ||
testName = strings.ReplaceAll(testName, " ", "_") | ||
content, err := os.ReadFile("./golden/" + testName + ".json") | ||
if err != nil { | ||
panic(err) | ||
} | ||
return content | ||
} | ||
|
||
func newTestServer(url string, statusCode int, responseBody []byte) *httptest.Server { | ||
mux := http.NewServeMux() | ||
mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(statusCode) | ||
w.Header().Set("Content-Type", "application/json") | ||
w.Write(responseBody) | ||
}) | ||
return httptest.NewServer(mux) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"message": "The resource you are looking for does not exist.", | ||
"error": 404 | ||
} |
Oops, something went wrong.