Improved HTTP testingby TestServe util

This commit is contained in:
Maxim Lebedev 2021-10-01 03:24:40 +05:00
parent 7b9a687c9c
commit 3034cae45e
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
10 changed files with 101 additions and 127 deletions

3
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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)
}

View File

@ -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",
},
}
}

View File

@ -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",
}

View File

@ -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",
}
}

View File

@ -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",

View File

@ -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())))

View File

@ -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) {

View File

@ -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())
}
}