From 3034cae45ee5b201b9e5248ceff22a2e8e5dcca9 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 1 Oct 2021 03:24:40 +0500 Subject: [PATCH] :white_check_mark: Improved HTTP testingby TestServe util --- go.mod | 3 +- go.sum | 8 +- .../repository/http/http_client_test.go | 82 ++++++------------- internal/domain/client.go | 10 +-- internal/domain/login.go | 6 +- internal/domain/profile.go | 4 +- internal/domain/token.go | 4 +- .../token/delivery/http/token_http_test.go | 24 +++--- .../token/repository/bolt/bolt_token_test.go | 54 +++++------- internal/util/util.go | 33 ++++++-- 10 files changed, 101 insertions(+), 127 deletions(-) diff --git a/go.mod b/go.mod index c99919b..ddd8e29 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 266b4a9..9d5db08 100644 --- a/go.sum +++ b/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= diff --git a/internal/client/repository/http/http_client_test.go b/internal/client/repository/http/http_client_test.go index ee5ee8f..d7efd64 100644 --- a/internal/client/repository/http/http_client_test.go +++ b/internal/client/repository/http/http_client_test.go @@ -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 = ` - - - - - - Example App - - - -
- - Example App -
- - -` - 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, ` + + + + + + Example App + + + +
+ + Example App +
+ + + `) + ctx.Response.Header.Set(http.HeaderLink, `; 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, - `; 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) } diff --git a/internal/domain/client.go b/internal/domain/client.go index 21b9741..224a56a 100644 --- a/internal/domain/client.go +++ b/internal/domain/client.go @@ -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", }, } } diff --git a/internal/domain/login.go b/internal/domain/login.go index 6893dbd..fab0c09 100644 --- a/internal/domain/login.go +++ b/internal/domain/login.go @@ -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", } diff --git a/internal/domain/profile.go b/internal/domain/profile.go index 00d9877..142432a 100644 --- a/internal/domain/profile.go +++ b/internal/domain/profile.go @@ -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", } } diff --git a/internal/domain/token.go b/internal/domain/token.go index 66195a5..9de9b58 100644 --- a/internal/domain/token.go +++ b/internal/domain/token.go @@ -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", diff --git a/internal/token/delivery/http/token_http_test.go b/internal/token/delivery/http/token_http_test.go index d4e7cc6..404aac6 100644 --- a/internal/token/delivery/http/token_http_test.go +++ b/internal/token/delivery/http/token_http_test.go @@ -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()))) diff --git a/internal/token/repository/bolt/bolt_token_test.go b/internal/token/repository/bolt/bolt_token_test.go index c539ad7..acb1aec 100644 --- a/internal/token/repository/bolt/bolt_token_test.go +++ b/internal/token/repository/bolt/bolt_token_test.go @@ -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) { diff --git a/internal/util/util.go b/internal/util/util.go index 6e4c467..11be78a 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -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()) + } }