Skip to content

Commit

Permalink
test(integration): First pass at creating integration tests (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
nobe4 authored Aug 13, 2024
1 parent c2269f2 commit e85e521
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 12 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/nobe4/gh-not.svg)](https://pkg.go.dev/github.com/nobe4/gh-not)
[![CI](https://github.com/nobe4/gh-not/actions/workflows/ci.yml/badge.svg)](https://github.com/nobe4/gh-not/actions/workflows/ci.yml)
[![CodeQL](https://github.com/nobe4/gh-not/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/nobe4/gh-not/actions/workflows/github-code-scanning/codeql)

> [!IMPORTANT]
> Under heavy development, expect nothing.
Expand Down
64 changes: 64 additions & 0 deletions internal/api/mock/calls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package mock

import (
"encoding/json"
"io"
"net/http"
"os"
"strings"
)

type Call struct {
Verb string
Endpoint string
Data any
Error error
Response *http.Response
}

type RawCall struct {
Verb string `json:"verb"`
Endpoint string `json:"endpoint"`
Data any `json:"data"`
Error error `json:"error"`
RawResponse RawResponse `json:"response"`
}

type RawResponse struct {
StatusCode int `json:"status_code"`
Body any `json:"body"`
}

func LoadCallsFromFile(path string) ([]Call, error) {
rawCalls := []RawCall{}

content, err := os.ReadFile(path)
if err != nil {
return nil, err
}

if err = json.Unmarshal(content, &rawCalls); err != nil {
return nil, err
}

calls := make([]Call, len(rawCalls))
for i, call := range rawCalls {
body, err := json.Marshal(call.RawResponse.Body)
if err != nil {
return nil, err
}

calls[i] = Call{
Verb: call.Verb,
Endpoint: call.Endpoint,
Data: call.Data,
Error: call.Error,
Response: &http.Response{
StatusCode: call.RawResponse.StatusCode,
Body: io.NopCloser(strings.NewReader(string(body))),
},
}
}

return calls, nil
}
28 changes: 17 additions & 11 deletions internal/api/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mock
import (
"fmt"
"io"
"log/slog"
"net/http"

"github.com/nobe4/gh-not/internal/api"
Expand All @@ -14,14 +15,6 @@ type Mock struct {
index int
}

type Call struct {
Verb string
Endpoint string
Data any
Error error
Response *http.Response
}

type MockError struct {
verb string
endpoint string
Expand All @@ -36,22 +29,35 @@ func New(c []Call) api.Requestor {
return &Mock{Calls: c}
}

func (m *Mock) Done() error {
if m.index < len(m.Calls) {
return &MockError{"", "", fmt.Sprintf("%d calls remaining", len(m.Calls)-m.index)}
}

return nil
}

func (m *Mock) call(verb, endpoint string) (Call, error) {
if m.index >= len(m.Calls) {
return Call{}, &MockError{verb, endpoint, "no more calls"}
return Call{}, &MockError{verb, endpoint, "unexpected call: no more calls"}
}

call := m.Calls[m.index]
if (call.Verb != "" && call.Verb != verb) || (call.Endpoint != "" && call.Endpoint != endpoint) {
return Call{}, &MockError{verb, endpoint, "unexpected call"}
return Call{}, &MockError{
verb,
endpoint,
fmt.Sprintf("unexpected call: mismatch, expected %s %s", call.Verb, call.Endpoint),
}
}

m.index++
slog.Debug("mock call", "verb", verb, "endpoint", endpoint, "call", call)

return call, nil
}

func (m *Mock) Request(verb, endpoint string, body io.Reader) (*http.Response, error) {
func (m *Mock) Request(verb, endpoint string, _ io.Reader) (*http.Response, error) {
call, err := m.call(verb, endpoint)
if err != nil {
return nil, err
Expand Down
1 change: 1 addition & 0 deletions internal/gh/enrichments.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (c *Client) Enrich(n *notifications.Notification) error {
return nil
}

slog.Debug("enriching", "url", n.Subject.URL)
resp, err := c.API.Request(http.MethodGet, n.Subject.URL, nil)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions internal/gh/gh.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func nextPageLink(h *http.Header) string {
}

func (c *Client) request(verb, endpoint string, body io.Reader) ([]*notifications.Notification, string, error) {
slog.Debug("request", "verb", verb, "endpoint", endpoint)
response, err := c.API.Request(verb, endpoint, body)
if err != nil {
return nil, "", err
Expand Down
51 changes: 51 additions & 0 deletions internal/notifications/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"slices"
"strings"
"time"
)

Expand Down Expand Up @@ -74,6 +75,56 @@ type User struct {
Type string `json:"type"`
}

func (n Notifications) Equal(others Notifications) bool {
if len(n) != len(others) {
return false
}

for i, n := range n {
if !n.Equal(others[i]) {
return false
}
}

return true
}

func (n Notification) Equal(other *Notification) bool {
return n.Id == other.Id &&
n.Unread == other.Unread &&
n.Reason == other.Reason &&
n.UpdatedAt.Equal(other.UpdatedAt) &&
n.URL == other.URL &&
n.Repository.Name == other.Repository.Name &&
n.Repository.FullName == other.Repository.FullName &&
n.Repository.Private == other.Repository.Private &&
n.Repository.Fork == other.Repository.Fork &&
n.Repository.Owner.Login == other.Repository.Owner.Login &&
n.Repository.Owner.Type == other.Repository.Owner.Type &&
n.Subject.Title == other.Subject.Title &&
n.Subject.URL == other.Subject.URL &&
n.Subject.Type == other.Subject.Type &&
n.Subject.State == other.Subject.State &&
n.Subject.HtmlUrl == other.Subject.HtmlUrl &&
n.Author.Login == other.Author.Login &&
n.Author.Type == other.Author.Type &&
n.Meta.Hidden == other.Meta.Hidden &&
n.Meta.Done == other.Meta.Done &&
n.Meta.RemoteExists == other.Meta.RemoteExists
}

func (n Notifications) Debug() string {
out := []string{}
for _, n := range n {
out = append(out, n.Debug())
}
return strings.Join(out, "\n")
}

func (n Notification) Debug() string {
return fmt.Sprintf("%#v", n)
}

func (n Notifications) Map() NotificationMap {
m := NotificationMap{}
for _, n := range n {
Expand Down
2 changes: 1 addition & 1 deletion script/watch-test
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

find . -name '*.go' | \
find . -not -path './.git/*' -not -path './dist/*' | \
entr -c \
bash -c '
go test -cover -coverprofile=coverage ./... &&
Expand Down
1 change: 1 addition & 0 deletions test/integration/000/cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
10 changes: 10 additions & 0 deletions test/integration/000/calls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"verb": "GET",
"endpoint": "https://api.github.com/notifications?all=true",
"response": {
"status_code": 200,
"body": []
}
}
]
Empty file.
1 change: 1 addition & 0 deletions test/integration/000/want.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
1 change: 1 addition & 0 deletions test/integration/001/cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
27 changes: 27 additions & 0 deletions test/integration/001/calls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{
"verb": "GET",
"endpoint": "https://api.github.com/notifications?all=true",
"response": {
"status_code": 200,
"body": [
{
"id": "1",
"subject": {
"url": "enrichment#1"
}
}
]
}
},
{
"verb": "GET",
"endpoint": "enrichment#1",
"response": {
"status_code": 200,
"body": {
"state": "open"
}
}
}
]
Empty file.
12 changes: 12 additions & 0 deletions test/integration/001/want.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"id": "1",
"subject": {
"url": "enrichment#1",
"state": "open"
},
"meta": {
"remote_exists": true
}
}
]
96 changes: 96 additions & 0 deletions test/integration/the_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package tests

import (
"encoding/json"
"fmt"
"os"
"testing"

apiMock "github.com/nobe4/gh-not/internal/api/mock"
configPkg "github.com/nobe4/gh-not/internal/config"
"github.com/nobe4/gh-not/internal/logger"
"github.com/nobe4/gh-not/internal/manager"
"github.com/nobe4/gh-not/internal/notifications"
)

type config struct {
Id string
// TODO: move those into config so it can be set by default as well as via
// CLI
ForceStrategy manager.ForceStrategy
RefreshStrategy manager.RefreshStrategy
}

func setup(t *testing.T, conf config) (*manager.Manager, *apiMock.Mock, notifications.Notifications) {
logger.Init(5)

configPath := fmt.Sprintf("./%s/config.yaml", conf.Id)
callsPath := fmt.Sprintf("./%s/calls.json", conf.Id)
wantPath := fmt.Sprintf("./%s/want.json", conf.Id)
cachePath := fmt.Sprintf("./%s/cache.json", conf.Id)

c, err := configPkg.New(configPath)
if err != nil {
t.Fatal(err)
}
c.Data.Cache.Path = cachePath

m := manager.New(c.Data)

// TODO: move those into config so it can be set by default as well as via
// CLI
m.ForceStrategy = conf.ForceStrategy
m.RefreshStrategy = conf.RefreshStrategy

calls, err := apiMock.LoadCallsFromFile(callsPath)
caller := &apiMock.Mock{Calls: calls}
m.SetCaller(caller)

if err := m.Load(); err != nil {
t.Fatal(err)
}
if err := m.Refresh(); err != nil {
t.Fatal(err)
}

want := notifications.Notifications{}

raw, err := os.ReadFile(wantPath)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(raw, &want); err != nil {
t.Fatal(err)
}

return m, caller, want
}

func TestIntegration(t *testing.T) {
dirs, err := os.ReadDir(".")
if err != nil {
t.Fatal(err)
}
for _, dir := range dirs {
if !dir.IsDir() {
continue
}

t.Run(dir.Name(), func(t *testing.T) {
m, c, want := setup(t, config{
Id: dir.Name(),
RefreshStrategy: manager.ForceRefresh,
})

got := m.Notifications

if !want.Equal(got) {
t.Fatalf("mismatch notifications\nwant %s\ngot %s", want.Debug(), got.Debug())
}

if err := c.Done(); err != nil {
t.Fatal(err)
}
})
}
}

0 comments on commit e85e521

Please sign in to comment.