Skip to content

Commit

Permalink
feat: added unit tests and github action (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
PaytonWebber authored Nov 27, 2024
1 parent 38694f4 commit ca67706
Show file tree
Hide file tree
Showing 13 changed files with 1,112 additions and 20 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Go

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
3 changes: 2 additions & 1 deletion cmd/bot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ func handleLeaderboardError(leaderboard *aoc.Leaderboard, err error) *aoc.Leader
}

func initTracker(cfg *config.Config, storedLeaderboard *aoc.Leaderboard) *leaderboard.Tracker {
tracker := leaderboard.NewTracker(cfg, storedLeaderboard)
client := aoc.NewClient(cfg.SessionCookie)
tracker := leaderboard.NewTracker(cfg, storedLeaderboard, client)
if tracker == nil {
log.Fatal("tracker is nil")
}
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ go 1.20

require (
github.com/bwmarrin/discordgo v0.27.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
Expand All @@ -12,3 +20,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 changes: 13 additions & 7 deletions internal/aoc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,48 @@ import (

type Client struct {
SessionCookie string
HTTPClient *http.Client
}

// NewClient creates a new AOC client with the provided session cookie.
func NewClient(sessionCookie string) *Client {
return &Client{
SessionCookie: sessionCookie,
HTTPClient: http.DefaultClient,
}
}

// For testing purposes, SetHTTPClient allows you to set the HTTP client used by the client.
func (c *Client) SetHTTPClient(client *http.Client) {
c.HTTPClient = client
}

func (c *Client) GetLeaderboard(leaderboardID string) (*Leaderboard, error) {
url := fmt.Sprintf("https://adventofcode.com/2024/leaderboard/private/view/%s.json", leaderboardID)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error reading request: %w", err)
return nil, fmt.Errorf("error creating request: %w", err)
}

req.Header.Set("User-Agent", "github.com/PaytonWebber/aoc-discord-bot by [email protected]")

req.Header.Set("cookie", fmt.Sprintf("session=%s", c.SessionCookie))

client := &http.Client{}
resp, err := client.Do(req)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
return nil, fmt.Errorf("error making HTTP request: %w", err)
}

defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading body: %w", err)
return nil, fmt.Errorf("error reading response body: %w", err)
}

var leaderboard Leaderboard
err = json.Unmarshal(body, &leaderboard)
if err != nil {
return nil, fmt.Errorf("error unmarshalling body: %w", err)
return nil, fmt.Errorf("error unmarshalling response body: %w", err)
}

return &leaderboard, nil
Expand Down
174 changes: 174 additions & 0 deletions internal/aoc/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package aoc

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

const mockLeaderboardJSON = `{
"event": "2024",
"owner_id": 12345,
"members": {
"67890": {
"id": 67890,
"last_star_ts": 1672444800,
"global_score": 100,
"local_score": 200,
"name": "Test User",
"completion_day_level": {
"1": {
"1": {
"get_star_ts": 1672444800,
"star_index": 1
},
"2": {
"get_star_ts": 1672444900,
"star_index": 2
}
}
},
"stars": 2
}
}
}`

func TestGetLeaderboardSuccess(t *testing.T) {
// Create a mock server to return a valid JSON response
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Validate the request
if r.Method != http.MethodGet {
t.Errorf("Expected GET request, got %s", r.Method)
}
expectedPath := "/2024/leaderboard/private/view/test-leaderboard.json"
if r.URL.Path != expectedPath {
t.Errorf("Expected URL path %s, got %s", expectedPath, r.URL.Path)
}
// Return mock JSON
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, mockLeaderboardJSON)
}))
defer mockServer.Close()

client := NewClient("test-session-cookie")
// Inject mock HTTP client
client.SetHTTPClient(mockServer.Client())

// Override the request URL using a custom transport
client.HTTPClient.Transport = rewriteURLTransport("https://adventofcode.com", mockServer.URL)

// Call the method under test
leaderboard, err := client.GetLeaderboard("test-leaderboard")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

// Verify top-level fields
if leaderboard.Event != "2024" {
t.Errorf("Expected event '2024', got '%s'", leaderboard.Event)
}
if leaderboard.OwnerID != 12345 {
t.Errorf("Expected owner ID 12345, got %d", leaderboard.OwnerID)
}

// Verify member data
member, exists := leaderboard.Members["67890"]
if !exists {
t.Fatalf("Expected member with ID '67890' in leaderboard members")
}
if member.ID != 67890 {
t.Errorf("Expected member ID 67890, got %d", member.ID)
}
if member.Name != "Test User" {
t.Errorf("Expected member name 'Test User', got '%s'", member.Name)
}
if member.Stars != 2 {
t.Errorf("Expected stars 2, got %d", member.Stars)
}
if member.LocalScore != 200 {
t.Errorf("Expected local score 200, got %d", member.LocalScore)
}

// Verify completion day levels
day1, exists := member.CompletionDayLevels["1"]
if !exists {
t.Fatalf("Expected day '1' in completion_day_level")
}
if day1.Level1 == nil || day1.Level1.GetStarTs != 1672444800 {
t.Errorf("Expected Level1 get_star_ts 1672444800, got %v", day1.Level1)
}
if day1.Level2 == nil || day1.Level2.GetStarTs != 1672444900 {
t.Errorf("Expected Level2 get_star_ts 1672444900, got %v", day1.Level2)
}
}

func TestGetLeaderboardInvalidSession(t *testing.T) {
// Create a mock server that returns unauthorized response
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
}))
defer mockServer.Close()

client := NewClient("invalid-session-cookie")
client.SetHTTPClient(mockServer.Client())

// Override the request URL
client.HTTPClient.Transport = rewriteURLTransport("https://adventofcode.com", mockServer.URL)

_, err := client.GetLeaderboard("test-leaderboard")
if err == nil {
t.Fatalf("Expected an error due to unauthorized access, but got none")
}
expectedError := "error unmarshalling response body"
if err.Error()[:len(expectedError)] != expectedError {
t.Errorf("Expected error starting with '%s', got '%v'", expectedError, err)
}
}

func TestGetLeaderboardInvalidJSON(t *testing.T) {
// Create a mock server that returns invalid JSON
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "invalid json")
}))
defer mockServer.Close()

client := NewClient("test-session-cookie")
client.SetHTTPClient(mockServer.Client())

// Override the request URL
client.HTTPClient.Transport = rewriteURLTransport("https://adventofcode.com", mockServer.URL)

_, err := client.GetLeaderboard("test-leaderboard")
if err == nil {
t.Fatalf("Expected an error due to invalid JSON, but got none")
}
expectedError := "error unmarshalling response body"
if err.Error()[:len(expectedError)] != expectedError {
t.Errorf("Expected error starting with '%s', got '%v'", expectedError, err)
}
}

// rewriteURLTransport modifies the request URL to point to the mock server
func rewriteURLTransport(originalBase, mockBase string) http.RoundTripper {
return &urlRewritingTransport{
originalBase: originalBase,
mockBase: mockBase,
original: http.DefaultTransport,
}
}

type urlRewritingTransport struct {
originalBase string
mockBase string
original http.RoundTripper
}

func (t *urlRewritingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Replace the original base URL with the mock server's URL
if req.URL.String()[:len(t.originalBase)] == t.originalBase {
req.URL.Scheme = "http"
req.URL.Host = t.mockBase[len("http://"):]
}
return t.original.RoundTrip(req)
}
Loading

0 comments on commit ca67706

Please sign in to comment.