Skip to content
10 changes: 7 additions & 3 deletions coolfacts/cmd/coolfacts_client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ func NewClient(endpoint string) *client {
}

func (c *client) GetLastCreatedFact() (coolfact.Fact, error) {
allFacts, err := c.GetFacts()
filters := coolfact.Filters{
Limit: 1,
}

allFacts, err := c.GetFacts(filters)
if err != nil {
return coolfact.Fact{}, fmt.Errorf("GetLastCreatedFact: %w", err)
}
Expand All @@ -59,8 +63,8 @@ func (c *client) GetLastCreatedFact() (coolfact.Fact, error) {
return allFacts[0], nil
}

func (c *client) GetFacts() ([]coolfact.Fact, error) {
ul := c.endpoint + pathGetFacts
func (c *client) GetFacts(filters coolfact.Filters) ([]coolfact.Fact, error) {
ul := fmt.Sprintf("%s%s?limit=%d&topic=%s", c.endpoint, pathGetFacts, filters.Limit, filters.Topic)
res, err := c.httpClient.Get(ul)
if err != nil {
return nil, fmt.Errorf("client.GetLastCreatedFact to do request: %v", err)
Expand Down
21 changes: 20 additions & 1 deletion coolfacts/cmd/coolfacts_client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"os"
"regexp"
"strconv"
"strings"

"github.com/FTBpro/go-workshop/coolfacts/coolfact"
Expand Down Expand Up @@ -60,7 +61,25 @@ func processCmd(cl *client, cmd string, args []string) (string, error) {
case "":
return "", nil
case commandGetFacts:
facts, err := cl.GetFacts()
if len(args) < 1 {
return "", fmt.Errorf("must add argument for limit")
}
limit, err := strconv.Atoi(args[0])
if err != nil {
return "", fmt.Errorf("limit must be a number")
}

var topic string
if len(args) > 1 {
topic = args[1]
}

filters := coolfact.Filters{
Topic: topic,
Limit: limit,
}

facts, err := cl.GetFacts(filters)
if err != nil {
return "", err
}
Expand Down
9 changes: 8 additions & 1 deletion coolfacts/cmd/coolfacts_server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
)

type FactsService interface {
// TODO: fix signature for GetFacts
GetFacts() ([]coolfact.Fact, error)
CreateFact(fact coolfact.Fact) error
}
Expand Down Expand Up @@ -75,7 +76,7 @@ func (s *server) HandlePing(w http.ResponseWriter, _ *http.Request) {
}
}

func (s *server) HandleGetFacts(w http.ResponseWriter, _ *http.Request) {
func (s *server) HandleGetFacts(w http.ResponseWriter, r *http.Request) {
log.Println("Handling getFact ...")

facts, err := s.factsService.GetFacts()
Expand Down Expand Up @@ -145,6 +146,12 @@ func (s *server) HandleError(w http.ResponseWriter, err error) {
}
}

func (s *server) HandleBadRequest(w http.ResponseWriter, err error) {
log.Println("Handling Bad Request ...")

// TODO: implement
}

func (s *server) formatGetFactsResponse(facts []coolfact.Fact) map[string]interface{} {
formattedFacts := make([]map[string]interface{}, len(facts))
for i, coolFact := range facts {
Expand Down
219 changes: 219 additions & 0 deletions coolfacts/cmd/coolfacts_server/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package main_test

import (
"bytes"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/stretchr/testify/require"

server "github.com/FTBpro/go-workshop/coolfacts/cmd/coolfacts_server"
"github.com/FTBpro/go-workshop/coolfacts/coolfact"
)

func Test_Server_GetFacts(t *testing.T) {
facts := generateRandomFactsDesc(10)
testCases := []struct {
name string
queryParamsToSend string
expectedFilters coolfact.Filters
want []coolfact.Fact
wantErr bool
expectedHTTPStatus int
}{
{
name: "10 facts with filters",
queryParamsToSend: "?limit=10&topic=TV",
expectedFilters: coolfact.Filters{
Topic: "TV",
Limit: 10,
},
want: facts,
expectedHTTPStatus: http.StatusOK,
},
{
name: "no topic",
queryParamsToSend: "?limit=10",
expectedFilters: coolfact.Filters{
Topic: "",
Limit: 10,
},
want: facts,
expectedHTTPStatus: http.StatusOK,
},
{
name: "no limit - expect bad request",
queryParamsToSend: "",
want: nil,
wantErr: true,
expectedHTTPStatus: http.StatusBadRequest,
},
{
name: "limit is not an int - expect bad request",
queryParamsToSend: "?limit=one",
want: nil,
wantErr: true,
expectedHTTPStatus: http.StatusBadRequest,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mockService := mockFactsService{
factsToReturn: tc.want,
}

srv := server.NewServer(&mockService)
ts := httptest.NewServer(srv)

res, err := http.Get(ts.URL + "/facts" + tc.queryParamsToSend)
require.NoError(t, err)
require.Equal(t, tc.expectedHTTPStatus, res.StatusCode)

if tc.wantErr {
return
}

gotFacts, err := factsFromResponse(t, res)
require.NoError(t, err)

require.Equal(t, tc.expectedFilters, mockService.filtersGot)
expectEqualFacts(t, tc.want, gotFacts)
})
}
}

func Test_Server_CreateFacts(t *testing.T) {
facts := generateRandomFactsDesc(10)
testCases := []struct {
name string
queryParamsToSend string
factToCreate coolfact.Fact
wantErr bool
expectedHTTPStatus int
}{
{
name: "10 facts with filters",
factToCreate: facts[0],
expectedHTTPStatus: http.StatusOK,
},
{
name: "no topic",
factToCreate: facts[0],
expectedHTTPStatus: http.StatusOK,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mockService := mockFactsService{}
srv := server.NewServer(&mockService)
ts := httptest.NewServer(srv)
defer ts.Close()

payload := map[string]interface{}{
"topic": tc.factToCreate.Topic,
"description": tc.factToCreate.Description,
}

postBody, err := json.Marshal(payload)
require.NoError(t, err)

responseBody := bytes.NewBuffer(postBody)

res, err := http.Post(ts.URL+"/facts", "application/json", responseBody)
require.NoError(t, err)
require.Equal(t, tc.expectedHTTPStatus, res.StatusCode)

if tc.wantErr {
return
}
require.Equal(t, tc.factToCreate.Topic, mockService.createFactGotFact.Topic)
require.Equal(t, tc.factToCreate.Description, mockService.createFactGotFact.Description)
})
}
}

func generateRandomFactsDesc(n int) []coolfact.Fact {
var facts []coolfact.Fact
for i := 0; i < n; i++ {
fact := randomFact()
fact.CreatedAt = time.Now().Add(-(time.Duration(i) * time.Hour)).UTC()
facts = append(facts, fact)
}

return facts
}

func randomFact() coolfact.Fact {
rand.Seed(time.Now().UnixNano())
return coolfact.Fact{
Topic: fmt.Sprintf("Topic %d", rand.Intn(10000)),
Description: fmt.Sprintf("Some Description %d", rand.Intn(10000)),
}
}

type getFactsResponse struct {
Facts []struct {
Topic string `json:"topic"`
Description string `json:"description"`
CreatedAt time.Time `json:"createdAt"`
} `json:"facts"`
}

func factsFromResponse(t *testing.T, res *http.Response) ([]coolfact.Fact, error) {
var factsResponse getFactsResponse
err := json.NewDecoder(res.Body).Decode(&factsResponse)
require.NoErrorf(t, err, "factsFromResponse failed decode get facts response")

facts := make([]coolfact.Fact, len(factsResponse.Facts))
for i, fact := range factsResponse.Facts {
facts[i] = coolfact.Fact(fact)
}

return facts, nil
}

type mockFactsService struct {
filtersGot coolfact.Filters
factsToReturn []coolfact.Fact
shouldReturnError bool

createFactGotFact coolfact.Fact
}

func (m *mockFactsService) GetFacts(filters coolfact.Filters) ([]coolfact.Fact, error) {
m.filtersGot = filters

if m.shouldReturnError {
return nil, fmt.Errorf("mockFactsService asked to return an error")
}

return m.factsToReturn, nil
}

func (m *mockFactsService) CreateFact(fact coolfact.Fact) error {
m.createFactGotFact = fact

if m.shouldReturnError {
return fmt.Errorf("mockFactsService asked to return an error")
}

return nil
}

func expectEqualFacts(t *testing.T, expected, got []coolfact.Fact) {
require.Equalf(t, len(expected), len(got), "expectEqualFacts: different length")

for i, gotFact := range got {
expectedFact := expected[i]
require.Equal(t, expectedFact.Topic, gotFact.Topic)
require.Equal(t, expectedFact.Description, gotFact.Description)
require.Equal(t, expectedFact.CreatedAt, gotFact.CreatedAt)
}
}
4 changes: 4 additions & 0 deletions coolfacts/coolfact/fact.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ type Fact struct {
Description string
CreatedAt time.Time
}

// TODO: add struct Filters with:
// - Topic
// - Limit
2 changes: 2 additions & 0 deletions coolfacts/coolfact/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package coolfact
import "fmt"

type Repository interface {
//TODO: fix signature for GetFacts
GetFacts() ([]Fact, error)
CreateFact(fct Fact) error
}
Expand All @@ -17,6 +18,7 @@ func NewService(factsRepo Repository) *service {
}
}

//TODO: fix signatur and call to repo
func (s *service) GetFacts() ([]Fact, error) {
facts, err := s.factsRepo.GetFacts()
if err != nil {
Expand Down
Loading