Skip to content

Commit

Permalink
Merge pull request #6 from gouline/testability
Browse files Browse the repository at this point in the history
Big restructure for handler testability
  • Loading branch information
gouline authored Oct 9, 2024
2 parents 0df6694 + cbd98b3 commit 04517e9
Show file tree
Hide file tree
Showing 22 changed files with 988 additions and 475 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ require (
github.com/labstack/echo/v4 v4.12.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/slack-go/slack v0.14.0
github.com/stretchr/testify v1.9.0
github.com/sykesm/zap-logfmt v0.0.4
go.uber.org/zap v1.27.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.uber.org/multierr v1.10.0 // indirect
Expand All @@ -25,4 +27,5 @@ require (
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
Expand Down Expand Up @@ -84,6 +87,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
48 changes: 48 additions & 0 deletions internal/pkg/scache/scache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package scache

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestHit(t *testing.T) {
cache := New(5*time.Minute, 10*time.Minute)
key := "test1"
value := "value1"

counter := 0
retriever := func(key string) (interface{}, error) {
counter++
return value, nil
}
response := <-cache.ResponseChan(key, retriever)
assert.Equal(t, value, response.Value)
assert.Equal(t, 1, counter)

response = <-cache.ResponseChan(key, retriever)
assert.Equal(t, value, response.Value)
assert.Equal(t, 1, counter)
}

func TestExpire(t *testing.T) {
cache := New(50*time.Millisecond, 100*time.Millisecond)
key := "test2"
value := "value2"

counter := 0
retriever := func(key string) (interface{}, error) {
counter++
return value, nil
}
response := <-cache.ResponseChan(key, retriever)
assert.Equal(t, value, response.Value)
assert.Equal(t, 1, counter)

time.Sleep(100 * time.Millisecond)

response = <-cache.ResponseChan(key, retriever)
assert.Equal(t, value, response.Value)
assert.Equal(t, 2, counter)
}
16 changes: 8 additions & 8 deletions internal/pkg/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import (
"github.com/labstack/echo/v4"
)

// APISuggest handles /api/suggest.
// handleAPISuggest handles /api/suggest.
func (s *Server) handleAPISuggest(c echo.Context) error {
slackCtx := s.slack.Context(c)
if !slackCtx.Authorized {
return c.String(http.StatusUnauthorized, "no token")
session := s.session(c)
if !session.IsAuthenticated() {
return c.NoContent(http.StatusUnauthorized)
}

destinations, err := slackCtx.Destinations()
destinations, err := session.GetDestinations()
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
Expand All @@ -28,8 +28,8 @@ func (s *Server) handleAPISuggest(c echo.Context) error {

// handleAPISend handles /api/send.
func (s *Server) handleAPISend(c echo.Context) error {
slackCtx := s.slack.Context(c)
if !slackCtx.Authorized {
session := s.session(c)
if !session.IsAuthenticated() {
return c.NoContent(http.StatusUnauthorized)
}

Expand All @@ -38,7 +38,7 @@ func (s *Server) handleAPISend(c echo.Context) error {
return c.String(http.StatusBadRequest, err.Error())
}

if err := slackCtx.SendMessage(request.User, request.Message, request.AsUser); err != nil {
if err := session.PostMessage(request.User, request.Message, request.AsUser); err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}

Expand Down
154 changes: 123 additions & 31 deletions internal/pkg/server/api_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,90 @@
package server

import (
"slices"
"errors"
"net/http"
"strings"
"testing"

"github.com/gouline/blaster/internal/pkg/slack"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)

func Test_sanitizeSearchTerm(t *testing.T) {
func TestHandleAPIUnauthenticated(t *testing.T) {
for _, test := range []struct {
f func(r *requestTester) error
}{
{
func(r *requestTester) error {
return r.Server.handleAPISuggest(r.Context)
},
},
{
func(r *requestTester) error {
return r.Server.handleAPISend(r.Context)
},
},
} {
r := newRequestTester(http.MethodGet, "/", nil)
if assert.NoError(t, test.f(r)) {
assert.Equal(t, http.StatusUnauthorized, r.Response.Code)
}
}
}

func TestHandleAPISuggest(t *testing.T) {
r := newRequestTester(http.MethodGet, "/", nil)
r.Authenticate("1", "")

if assert.NoError(t, r.Server.handleAPISuggest(r.Context)) {
assert.Equal(t, http.StatusOK, r.Response.Code)
}
}

func TestHandleAPISuggestError(t *testing.T) {
r := newRequestTester(http.MethodGet, "/", nil)
r.Authenticate("1", "")
r.Session.GetDestinationsError = errors.New("simulated")

if assert.NoError(t, r.Server.handleAPISuggest(r.Context)) {
assert.Equal(t, http.StatusInternalServerError, r.Response.Code)
assert.Contains(t, r.Response.Body.String(), "simulated")
}
}

func TestHandleAPISend(t *testing.T) {
r := newRequestTester(http.MethodPost, "/", strings.NewReader("{\"user\":\"1\",\"message\":\"test\",\"as_user\":true}"))
r.Request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
r.Authenticate("1", "")

if assert.NoError(t, r.Server.handleAPISend(r.Context)) {
assert.Equal(t, http.StatusOK, r.Response.Code)
}
}

func TestHandleAPISendBindError(t *testing.T) {
r := newRequestTester(http.MethodPost, "/", strings.NewReader("{\"user:\"1\",\"message\":\"test\",\"as_user\":true}"))
r.Request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
r.Authenticate("1", "")

if assert.NoError(t, r.Server.handleAPISend(r.Context)) {
assert.Equal(t, http.StatusBadRequest, r.Response.Code)
}
}

func TestHandleAPISendError(t *testing.T) {
r := newRequestTester(http.MethodPost, "/", nil)
r.Authenticate("1", "")
r.Session.PostMessageError = errors.New("simulated")

if assert.NoError(t, r.Server.handleAPISend(r.Context)) {
assert.Equal(t, http.StatusInternalServerError, r.Response.Code)
assert.Contains(t, r.Response.Body.String(), "simulated")
}
}

func TestSanitizeSearchTerm(t *testing.T) {
for _, test := range []struct {
s string
expected string
Expand All @@ -25,14 +102,11 @@ func Test_sanitizeSearchTerm(t *testing.T) {
expected: "测试字符串56",
},
} {
actual := sanitizeSearchTerm(test.s)
if actual != test.expected {
t.Errorf("for %s: got %s, expected %s", test.s, actual, test.expected)
}
assert.Equal(t, test.expected, sanitizeSearchTerm(test.s))
}
}

func Test_sanitizeCSV(t *testing.T) {
func TestSanitizeCSV(t *testing.T) {
for _, test := range []struct {
s string
expected string
Expand All @@ -54,14 +128,11 @@ func Test_sanitizeCSV(t *testing.T) {
expected: "something else",
},
} {
actual := sanitizeCSV(test.s)
if actual != test.expected {
t.Errorf("for %s: got %s, expected %s", test.s, actual, test.expected)
}
assert.Equal(t, test.expected, sanitizeCSV(test.s))
}
}

func Test_suggestionLabel(t *testing.T) {
func TestSuggestionLabel(t *testing.T) {
for _, test := range []struct {
name string
displayName string
Expand All @@ -88,14 +159,11 @@ func Test_suggestionLabel(t *testing.T) {
expected: "mg",
},
} {
actual := suggestionLabel(test.name, test.displayName)
if actual != test.expected {
t.Errorf("for %s, %s: got %s, expected %s", test.name, test.displayName, actual, test.expected)
}
assert.Equal(t, test.expected, suggestionLabel(test.name, test.displayName), "name: %s", test.name)
}
}

func Test_suggestDestinations(t *testing.T) {
func TestSuggestDestinations(t *testing.T) {
destinations := []*slack.Destination{
{
Type: "user",
Expand All @@ -115,33 +183,57 @@ func Test_suggestDestinations(t *testing.T) {
DisplayName: "Mark Knopfler",
ID: "mk",
},
{
Type: "usergroup",
Name: "developers",
DisplayName: "Developers",
ID: "ug",
Children: []*slack.Destination{
{
Type: "user",
Name: "jane",
DisplayName: "Jane",
ID: "jd",
},
},
},
}

for _, test := range []struct {
term string
expectedIDs []string
term string
expectedIDs []string
expectedChildrenIDs []string
}{
{
term: "mi",
expectedIDs: []string{"mg"},
term: "mi",
expectedIDs: []string{"mg"},
expectedChildrenIDs: []string{},
},
{
term: "mark",
expectedIDs: []string{"mark", "mk"},
expectedChildrenIDs: []string{},
},
{
term: "mark",
expectedIDs: []string{"mark", "mk"},
term: "kno",
expectedIDs: []string{"mk"},
expectedChildrenIDs: []string{},
},
{
term: "kno",
expectedIDs: []string{"mk"},
term: "dev",
expectedIDs: []string{"ug"},
expectedChildrenIDs: []string{"jd"},
},
} {
actuals := suggestDestinations(test.term, destinations)
actualValues := []string{}
for _, actual := range actuals {
actualChildren := []string{}
for _, actual := range suggestDestinations(test.term, destinations) {
actualValues = append(actualValues, actual.Value)
for _, child := range actual.Children {
actualChildren = append(actualChildren, child.Value)
}
}

if !slices.Equal(actualValues, test.expectedIDs) {
t.Errorf("for %s: got %s, expected %s", test.term, actualValues, test.expectedIDs)
}
assert.Equal(t, test.expectedIDs, actualValues, "term: %s:", test.term)
assert.Equal(t, test.expectedChildrenIDs, actualChildren, "term: %s:", test.term)
}
}
Loading

0 comments on commit 04517e9

Please sign in to comment.