Skip to content

Commit

Permalink
schedule: Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
daenney committed Dec 11, 2020
1 parent f45f053 commit e72276a
Show file tree
Hide file tree
Showing 2 changed files with 321 additions and 1 deletion.
4 changes: 3 additions & 1 deletion schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/anaskhan96/soup"
)

const scheduleURL = "https://gamesdonequick.com/schedule"

// Schedule represents the events occurring at a GDQ
type Schedule struct {
Events []*Event
Expand Down Expand Up @@ -176,7 +178,7 @@ func GetSchedule(id Edition, client *http.Client) (*Schedule, error) {
client = newHTTPClient()
}

resp, err := soup.GetWithClient(fmt.Sprintf("https://gamesdonequick.com/schedule/%d", id), client)
resp, err := soup.GetWithClient(fmt.Sprintf("%s/%d", scheduleURL, id), client)
if err != nil {
return nil, fmt.Errorf("failed to fetch schedule: %w", err)
}
Expand Down
318 changes: 318 additions & 0 deletions schedule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
package gdq

import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"testing"
"time"
)

var testEvents = []*Event{
{
Title: "Game 1",
Runners: []string{"amazing"},
Hosts: []string{"wonderful"},
Start: time.Now().Add(-10 * time.Minute).UTC(),
},
{
Title: "Game 2",
Runners: []string{"amazing"},
},
{
Title: "Game 3",
Hosts: []string{"wonderful"},
},
{
Title: "Game 4",
Runners: []string{"amazing"},
Hosts: []string{"awesome"},
Start: time.Now().Add(10 * time.Minute).UTC(),
},
{
Title: "Game 5",
Runners: []string{"fantastic"},
Hosts: []string{"wonderful"},
Start: time.Now().Add(30 * time.Minute).UTC(),
},
}

func assertEqual(t *testing.T, a interface{}, b interface{}) {
t.Helper()
if a == b {
return
}
t.Errorf("Received '%v' (type %v), expected '%v' (type %v)", a, reflect.TypeOf(a), b, reflect.TypeOf(b))
}

func assertNotNil(t *testing.T, a interface{}) {
t.Helper()
if a != nil {
return
}
t.Errorf("Received '%v' (type %v), nil", a, reflect.TypeOf(a))
}

func TestNewScheduleFrom(t *testing.T) {
t.Run("no events", func(t *testing.T) {
events := []*Event{}
s := NewScheduleFrom(events)
assertEqual(t, len(s.Events), 0)
assertEqual(t, len(s.byRunner), 0)
assertEqual(t, len(s.byHost), 0)
})
t.Run("single empty event", func(t *testing.T) {
events := []*Event{{}}
s := NewScheduleFrom(events)
assertEqual(t, len(s.Events), 1)
assertEqual(t, len(s.byRunner), 0)
assertEqual(t, len(s.byHost), 0)
})
t.Run("single event", func(t *testing.T) {
events := []*Event{
{
Runners: []string{"amazing"},
Hosts: []string{"wonderful"},
},
}
s := NewScheduleFrom(events)
assertEqual(t, len(s.Events), 1)
assertEqual(t, len(s.byRunner), 1)
assertEqual(t, len(s.byHost), 1)
assertEqual(t, len(s.byRunner["amazing"]), 1)
assertEqual(t, len(s.byHost["wonderful"]), 1)
})
t.Run("multiple events", func(t *testing.T) {
s := NewScheduleFrom(testEvents)
assertEqual(t, len(s.Events), 5)
assertEqual(t, len(s.byRunner), 2)
assertEqual(t, len(s.byHost), 2)
assertEqual(t, len(s.byRunner["amazing"]), 3)
assertEqual(t, len(s.byHost["wonderful"]), 3)
assertEqual(t, len(s.byRunner["fantastic"]), 1)
assertEqual(t, len(s.byHost["awesome"]), 1)
})
}

func TestForEntity(t *testing.T) {
s := NewScheduleFrom(testEvents)
t.Run("runner: empty", func(t *testing.T) {
assertEqual(t, len(s.forEntity("runner", " ").Events), 0)
})
t.Run("host: empty", func(t *testing.T) {
assertEqual(t, len(s.forEntity("host", " ").Events), 0)
})
t.Run("unknown", func(t *testing.T) {
assertEqual(t, len(s.forEntity("runner", "zz").Events), 0)
})
t.Run("exact match", func(t *testing.T) {
assertEqual(t, len(s.forEntity("runner", "amazing").Events), 3)
})
t.Run("exact match with extra spacing", func(t *testing.T) {
assertEqual(t, len(s.forEntity("runner", " amazing ").Events), 3)
})
t.Run("patial match single runner", func(t *testing.T) {
assertEqual(t, len(s.forEntity("runner", "maz").Events), 3)
})
t.Run("patial match multiple runners", func(t *testing.T) {
assertEqual(t, len(s.forEntity("runner", "a").Events), 4)
})
t.Run("unknown kind", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("unknown kind must result in a panic")
}
}()
s.forEntity("derp", "a")
})

t.Run("ForRunner", func(t *testing.T) {
assertEqual(t, len(s.ForRunner("a").Events), 4)
})
t.Run("ForHost", func(t *testing.T) {
assertEqual(t, len(s.ForHost("a").Events), 1)
})
}

func TestNextEvent(t *testing.T) {
t.Run("with events in the future", func(t *testing.T) {
s := NewScheduleFrom(testEvents)
e := s.NextEvent()
assertEqual(t, e.Title, "Game 4")
})
t.Run("with only events in the past", func(t *testing.T) {
s := NewScheduleFrom([]*Event{
{
Title: "Game 1",
Runners: []string{"amazing"},
Hosts: []string{"wonderful"},
Start: time.Now().Add(-10 * time.Minute).UTC(),
},
})
e := s.NextEvent()
var empty *Event
assertEqual(t, e, empty)
})
}

func TestForTitle(t *testing.T) {
t.Run("empty title", func(t *testing.T) {
s := NewScheduleFrom(testEvents)
assertEqual(t, len(s.ForTitle("").Events), 0)
})
t.Run("unknown title", func(t *testing.T) {
s := NewScheduleFrom(testEvents)
assertEqual(t, len(s.ForTitle("x").Events), 0)
})
t.Run("matching title", func(t *testing.T) {
s := NewScheduleFrom(testEvents)
assertEqual(t, len(s.ForTitle("4 ").Events), 1)
})
t.Run("matching multiple titles", func(t *testing.T) {
s := NewScheduleFrom(testEvents)
assertEqual(t, len(s.ForTitle(" ga ").Events), 5)
})
}

type roundTripFunc func(req *http.Request) *http.Response

func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}

func newTestClient(fn roundTripFunc) *http.Client {
return &http.Client{
Transport: roundTripFunc(fn),
}
}

func newRespWithBody(body string) *http.Response {
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
Header: http.Header{
"content-type": []string{"text/html; charset=UTF-8"},
},
}
}

func TestGetSchedule(t *testing.T) {
t.Run("URL construction", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
assertEqual(t, req.URL.String(), fmt.Sprintf("%s/0", scheduleURL))
return newRespWithBody(``)
})
_, _ = GetSchedule(Latest, client)
})
t.Run("empty response", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
return newRespWithBody(``)
})

_, err := GetSchedule(Latest, client)
assertNotNil(t, err)
if !errors.Is(err, io.EOF) {
t.Errorf("Got %v, expected %v", err, io.EOF)
}
})
t.Run("missing runtable", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
return newRespWithBody(`<html></html>`)
})

_, err := GetSchedule(Latest, client)
assertNotNil(t, err)
if !errors.Is(err, ErrMissingSchedule) {
t.Errorf("Got %v, expected %v", err, ErrMissingSchedule)
}
})
t.Run("missing runtable body", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
return newRespWithBody(`<html><table id="runTable"></table></html>`)
})

_, err := GetSchedule(Latest, client)
assertNotNil(t, err)
if !errors.Is(err, ErrMissingSchedule) {
t.Errorf("Got %v, expected %v", err, ErrMissingSchedule)
}
})
t.Run("empty runtable", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
return newRespWithBody(`<html><table id="runTable"><tbody></tbody></table></html>`)
})

_, err := GetSchedule(Latest, client)
assertNotNil(t, err)
if !errors.Is(err, ErrMissingSchedule) {
t.Errorf("Got %v, expected %v", err, ErrMissingSchedule)
}
})
t.Run("uneven runs", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
return newRespWithBody(`<html><table id="runTable"><tbody><tr></tr><tr></tr><tr></tr></tbody></table></html>`)
})

_, err := GetSchedule(Latest, client)
assertNotNil(t, err)
if !errors.Is(err, ErrInvalidSchedule) {
t.Errorf("Got %v, expected %v", err, ErrInvalidSchedule)
}
})
t.Run("runtable, empty rows", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
return newRespWithBody(`<html><table id="runTable"><tbody><tr></tr><tr></tr></tbody></table></html>`)
})

_, err := GetSchedule(Latest, client)
assertNotNil(t, err)
if !errors.Is(err, ErrUnexpectedData) {
t.Errorf("Got %v, expected %v", err, ErrUnexpectedData)
}
})
t.Run("runtable, multiple events", func(t *testing.T) {
client := newTestClient(func(req *http.Request) *http.Response {
return newRespWithBody(`<html><table id="runTable"><tbody><tr>
<td>2020-12-01T16:00:00Z</td>
<td>First Event</td>
<td>First Runner</td>
<td> <i class="fa fa-clock-o text-gdq-red" aria-hidden="true"></i> 0:10:00 </td>
</tr>
<tr>
<td> <i class="fa fa-clock-o" aria-hidden="true"></i> 0:20:00 </td>
<td>Any% &mdash; </td>
<td><i class="fa fa-microphone"></i> First commentator</td>
</tr>
<tr>
<td>2020-12-01T17:00:00Z</td>
<td>Second&#039;s Game</td>
<td>Second Runner</td>
<td> <i class="fa fa-clock-o text-gdq-red" aria-hidden="true"></i> 0:17:00 </td>
</tr>
<tr>
<td> <i class="fa fa-clock-o" aria-hidden="true"></i> 0:46:00 </td>
<td>Any% Glitch &mdash; PC</td>
<td><i class="fa fa-microphone"></i> Second commentator</td>
</tr>
<tr>
<td>2020-12-01T18:00:00Z</td>
<td>Third Game</td>
<td>Third Runner, Fourth Runner</td>
<td> <i class="fa fa-clock-o text-gdq-red" aria-hidden="true"></i> 0:25:00 </td>
</tr>
<tr>
<td> <i class="fa fa-clock-o" aria-hidden="true"></i> 0:93:00 </td>
<td>BBQ &mdash; GBA</td>
<td><i class="fa fa-microphone"></i> Third commentator, fourthcommentator</td>
</tr></tbody></table></html>`)
})

s, err := GetSchedule(Latest, client)
assertEqual(t, err, nil)
assertEqual(t, len(s.Events), 3)
})
}

0 comments on commit e72276a

Please sign in to comment.