✅ Improved HTTP testingby TestServe util
This commit is contained in:
parent
7b9a687c9c
commit
3034cae45e
3
go.mod
3
go.mod
|
@ -3,9 +3,9 @@ module source.toby3d.me/website/oauth
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/brianvoe/gofakeit v3.18.0+incompatible
|
||||
github.com/fasthttp/router v1.4.1
|
||||
github.com/goccy/go-json v0.7.6
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
|
@ -15,6 +15,7 @@ require (
|
|||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
willnorris.com/go/microformats v1.1.1
|
||||
|
|
8
go.sum
8
go.sum
|
@ -50,8 +50,6 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
|||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8=
|
||||
github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
|
@ -128,8 +126,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
|
@ -440,8 +439,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
@ -2,80 +2,46 @@ package http_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
http "github.com/valyala/fasthttp"
|
||||
httputil "github.com/valyala/fasthttp/fasthttputil"
|
||||
|
||||
repository "source.toby3d.me/website/oauth/internal/client/repository/http"
|
||||
"source.toby3d.me/website/oauth/internal/common"
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/util"
|
||||
)
|
||||
|
||||
const testBody string = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Example App</title>
|
||||
<link rel="redirect_uri" href="/redirect">
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-app">
|
||||
<img src="/logo.png" class="u-logo">
|
||||
<a href="/" class="u-url p-name">Example App</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ln := httputil.NewInmemoryListener()
|
||||
u := http.AcquireURI()
|
||||
u.SetScheme("http")
|
||||
u.SetHost(ln.Addr().String())
|
||||
|
||||
t.Cleanup(func() {
|
||||
http.ReleaseURI(u)
|
||||
assert.NoError(t, ln.Close())
|
||||
httpClient, _, cleanup := util.TestServe(t, func(ctx *http.RequestCtx) {
|
||||
ctx.SuccessString(common.MIMETextHTML, `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Example App</title>
|
||||
<link rel="redirect_uri" href="/redirect">
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-app">
|
||||
<img src="/logo.png" class="u-logo">
|
||||
<a href="/" class="u-url p-name">Example App</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
ctx.Response.Header.Set(http.HeaderLink, `<http://app.example.com/redirect>; rel="redirect_uri">`)
|
||||
})
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
go func(t *testing.T) {
|
||||
t.Helper()
|
||||
require.NoError(t, http.Serve(ln, func(ctx *http.RequestCtx) {
|
||||
ctx.SuccessString(common.MIMETextHTML, testBody)
|
||||
ctx.Response.Header.Set(http.HeaderLink,
|
||||
`<https://app.example.com/redirect>; rel="redirect_uri">`)
|
||||
}))
|
||||
}(t)
|
||||
client := domain.TestClient(t)
|
||||
|
||||
client := new(http.Client)
|
||||
client.Dial = func(addr string) (net.Conn, error) {
|
||||
conn, err := ln.Dial()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to dial the address")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
result, err := repository.NewHTTPClientRepository(client).Get(context.TODO(), u.String())
|
||||
result, err := repository.NewHTTPClientRepository(httpClient).Get(context.TODO(), client.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &domain.Client{
|
||||
ID: u.String(),
|
||||
Name: "Example App",
|
||||
Logo: u.String() + "logo.png",
|
||||
URL: u.String(),
|
||||
RedirectURI: []string{
|
||||
"https://app.example.com/redirect",
|
||||
u.String() + "redirect",
|
||||
},
|
||||
}, result)
|
||||
assert.Equal(t, client, result)
|
||||
}
|
||||
|
|
|
@ -21,13 +21,13 @@ func TestClient(tb testing.TB) *Client {
|
|||
tb.Helper()
|
||||
|
||||
return &Client{
|
||||
ID: "http://127.0.0.1:2368/",
|
||||
ID: "http://app.example.com/",
|
||||
Name: "Example App",
|
||||
Logo: "http://127.0.0.1:2368/logo.png",
|
||||
URL: "http://127.0.0.1:2368/",
|
||||
Logo: "http://app.example.com/logo.png",
|
||||
URL: "http://app.example.com/",
|
||||
RedirectURI: []string{
|
||||
"https://app.example.com/redirect",
|
||||
"http://127.0.0.1:2368/redirect",
|
||||
"http://app.example.com/redirect",
|
||||
"http://app.example.com/redirect",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,13 +23,13 @@ func TestLogin(tb testing.TB) *Login {
|
|||
tb.Helper()
|
||||
|
||||
return &Login{
|
||||
ClientID: "https://app.example.com/",
|
||||
ClientID: "http://app.example.com/",
|
||||
Code: random.New().String(16),
|
||||
CodeChallenge: "OfYAxt8zU2dAPDWQxTAUIteRzMsoj9QBdMIVEDOErUo",
|
||||
CodeChallengeMethod: PKCEMethodS256,
|
||||
CodeVerifier: "a6128783714cfda1d388e2e98b6ae8221ac31aca31959e59512c59f5",
|
||||
Me: "https://user.example.net/",
|
||||
RedirectURI: "https://app.example.com/redirect",
|
||||
Me: "http://user.example.net/",
|
||||
RedirectURI: "http://app.example.com/redirect",
|
||||
Scopes: []string{"profile", "create", "update", "delete"},
|
||||
State: "1234567890",
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ func TestProfile(tb testing.TB) *Profile {
|
|||
|
||||
return &Profile{
|
||||
Name: "Example User",
|
||||
URL: "https://user.example.net/",
|
||||
Photo: "https://user.example.net/photo.jpg",
|
||||
URL: "http://user.example.net/",
|
||||
Photo: "http://user.example.net/photo.jpg",
|
||||
Email: "user@example.net",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ func TestToken(tb testing.TB) *Token {
|
|||
|
||||
return &Token{
|
||||
AccessToken: random.New().String(32),
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://user.example.net/",
|
||||
ClientID: "http://app.example.com/",
|
||||
Me: "http://user.example.net/",
|
||||
Profile: TestProfile(tb),
|
||||
Scopes: []string{"create", "update", "delete"},
|
||||
Type: "Bearer",
|
||||
|
|
|
@ -22,24 +22,26 @@ import (
|
|||
func TestVerification(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := new(sync.Map)
|
||||
repo := repository.NewMemoryTokenRepository(store)
|
||||
repo := repository.NewMemoryTokenRepository(new(sync.Map))
|
||||
accessToken := domain.TestToken(t)
|
||||
|
||||
require.NoError(t, repo.Create(context.TODO(), accessToken))
|
||||
|
||||
client, _, cleanup := util.TestServe(t, delivery.NewRequestHandler(usecase.NewTokenUseCase(repo)).Read)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
req := http.AcquireRequest()
|
||||
defer http.ReleaseRequest(req)
|
||||
|
||||
req.Header.SetMethod(http.MethodGet)
|
||||
req.SetRequestURI("http://localhost/token")
|
||||
req.SetRequestURI("http://app.example.com/token")
|
||||
req.Header.Set(http.HeaderAccept, common.MIMEApplicationJSON)
|
||||
req.Header.Set(http.HeaderAuthorization, "Bearer "+accessToken.AccessToken)
|
||||
|
||||
resp := http.AcquireResponse()
|
||||
defer http.ReleaseResponse(resp)
|
||||
|
||||
require.NoError(t, util.Serve(delivery.NewRequestHandler(usecase.NewTokenUseCase(repo)).Read, req, resp))
|
||||
require.NoError(t, client.Do(req, resp))
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode())
|
||||
|
||||
token := new(delivery.VerificationResponse)
|
||||
|
@ -54,17 +56,18 @@ func TestVerification(t *testing.T) {
|
|||
func TestRevocation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := new(sync.Map)
|
||||
repo := repository.NewMemoryTokenRepository(store)
|
||||
repo := repository.NewMemoryTokenRepository(new(sync.Map))
|
||||
accessToken := domain.TestToken(t)
|
||||
|
||||
require.NoError(t, repo.Create(context.TODO(), domain.TestToken(t)))
|
||||
|
||||
client, _, cleanup := util.TestServe(t, delivery.NewRequestHandler(usecase.NewTokenUseCase(repo)).Update)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
req := http.AcquireRequest()
|
||||
defer http.ReleaseRequest(req)
|
||||
|
||||
req.Header.SetMethod(http.MethodPost)
|
||||
req.SetRequestURI("http://localhost/token")
|
||||
req.SetRequestURI("http://app.example.com/token")
|
||||
req.Header.SetContentType(common.MIMEApplicationXWWWFormUrlencoded)
|
||||
req.Header.Set(http.HeaderAccept, common.MIMEApplicationJSON)
|
||||
req.PostArgs().Set("action", "revoke")
|
||||
|
@ -73,7 +76,8 @@ func TestRevocation(t *testing.T) {
|
|||
resp := http.AcquireResponse()
|
||||
defer http.ReleaseResponse(resp)
|
||||
|
||||
require.NoError(t, util.Serve(delivery.NewRequestHandler(usecase.NewTokenUseCase(repo)).Update, req, resp))
|
||||
require.NoError(t, client.Do(req, resp))
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode())
|
||||
assert.Equal(t, `{}`, strings.TrimSpace(string(resp.Body())))
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
json "github.com/goccy/go-json"
|
||||
|
@ -49,73 +50,60 @@ func TestMain(m *testing.M) {
|
|||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
accessToken := random.New().String(32)
|
||||
accessToken := domain.TestToken(t)
|
||||
accessToken.Profile = nil
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = db.Update(func(tx *bbolt.Tx) error {
|
||||
//nolint: exhaustivestruct
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken))
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken.AccessToken))
|
||||
})
|
||||
})
|
||||
|
||||
src, err := json.Marshal(&bolt.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scope: "read update delete",
|
||||
Type: "Bearer",
|
||||
AccessToken: accessToken.AccessToken,
|
||||
ClientID: accessToken.ClientID,
|
||||
Me: accessToken.Me,
|
||||
Scope: strings.Join(accessToken.Scopes, " "),
|
||||
Type: accessToken.Type,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.Update(func(tx *bbolt.Tx) error {
|
||||
//nolint: exhaustivestruct
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Put([]byte(accessToken), src)
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Put([]byte(accessToken.AccessToken), src)
|
||||
}))
|
||||
|
||||
tkn, err := repo.Get(context.TODO(), accessToken)
|
||||
tkn, err := repo.Get(context.TODO(), accessToken.AccessToken)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &domain.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scopes: []string{"read", "update", "delete"},
|
||||
Type: "Bearer",
|
||||
Profile: nil,
|
||||
}, tkn)
|
||||
assert.Equal(t, accessToken, tkn)
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
accessToken := random.New().String(32)
|
||||
accessToken := domain.TestToken(t)
|
||||
accessToken.Profile = nil
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = db.Update(func(tx *bbolt.Tx) error {
|
||||
//nolint: exhaustivestruct
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken))
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken.AccessToken))
|
||||
})
|
||||
})
|
||||
|
||||
tkn := &domain.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scopes: []string{"read", "update", "delete"},
|
||||
Type: "Bearer",
|
||||
Profile: nil,
|
||||
}
|
||||
require.NoError(t, repo.Create(context.TODO(), accessToken))
|
||||
|
||||
require.NoError(t, repo.Create(context.TODO(), tkn))
|
||||
|
||||
result := domain.NewToken()
|
||||
result := new(domain.Token)
|
||||
|
||||
require.NoError(t, db.View(func(tx *bbolt.Tx) error {
|
||||
//nolint: exhaustivestruct
|
||||
return new(bolt.Token).Bind(tx.Bucket(bolt.Token{}.Bucket()).Get([]byte(tkn.AccessToken)), result)
|
||||
return new(bolt.Token).Bind(tx.Bucket(bolt.Token{}.Bucket()).Get([]byte(accessToken.AccessToken)),
|
||||
result)
|
||||
}))
|
||||
assert.Equal(t, tkn, result)
|
||||
|
||||
assert.EqualError(t, repo.Create(context.TODO(), tkn), token.ErrExist.Error())
|
||||
assert.Equal(t, accessToken, result)
|
||||
assert.EqualError(t, repo.Create(context.TODO(), accessToken), token.ErrExist.Error())
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
|
|
|
@ -2,26 +2,41 @@ package util
|
|||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
http "github.com/valyala/fasthttp"
|
||||
httputil "github.com/valyala/fasthttp/fasthttputil"
|
||||
)
|
||||
|
||||
func Serve(handler http.RequestHandler, req *http.Request, res *http.Response) error {
|
||||
//nolint: exhaustivestruct
|
||||
func TestServe(tb testing.TB, handler http.RequestHandler) (*http.Client, *http.Server, func()) {
|
||||
tb.Helper()
|
||||
|
||||
ln := httputil.NewInmemoryListener()
|
||||
defer ln.Close()
|
||||
server := &http.Server{
|
||||
Handler: handler,
|
||||
DisableKeepalive: true,
|
||||
CloseOnShutdown: true,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := http.Serve(ln, handler); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.NoError(tb, server.Serve(ln))
|
||||
}()
|
||||
|
||||
client := http.Client{
|
||||
Dial: func(addr string) (net.Conn, error) {
|
||||
return ln.Dial()
|
||||
client := &http.Client{
|
||||
Dial: func(_ string) (net.Conn, error) {
|
||||
conn, err := ln.Dial()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot dial to address")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
|
||||
return client.Do(req, res)
|
||||
return client, server, func() {
|
||||
assert.NoError(tb, server.Shutdown())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue