From 8a4ce9c47eb429ab90dbbb29c28b7ded81b14cc8 Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Thu, 12 Oct 2023 21:05:19 +0900 Subject: [PATCH 1/6] Add clean target --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 7420b12..2c8d033 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ test: ## Run unit tests test-examples: ## Run unit tests for examples directory make -C examples test +clean: ## Clean up + rm -f coverage.out coverage.html + .DEFAULT_GOAL := help help: ## Show this help @grep -E '^[0-9a-zA-Z_-]+[[:blank:]]*:.*?## .*$$' $(MAKEFILE_LIST) | sort \ From 457e908009a4603cb033a75bdb18cfce65d73f82 Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Thu, 12 Oct 2023 21:05:51 +0900 Subject: [PATCH 2/6] Refactor: add new methods() and network struct --- network.go | 24 ++++++++++++++++++++++++ request.go | 34 +++++++++++++++++++++++----------- response.go | 16 ++++++++++++++-- spectest.go | 45 +++++++++++++++++---------------------------- 4 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 network.go diff --git a/network.go b/network.go new file mode 100644 index 0000000..4ea7203 --- /dev/null +++ b/network.go @@ -0,0 +1,24 @@ +package spectest + +import "net/http" + +// network is used to enable/disable networking for the test +type network struct { + // Client is the http client used when networking is enabled. + // By default, http.DefaultClient is used. + *http.Client + // enabled will enable networking for provided clients + enabled bool +} + +// newNetwork creates a new network setting +func newNetwork() *network { + return &network{ + Client: http.DefaultClient, + } +} + +// isEnable returns true if networking is enabled +func (n *network) isEnable() bool { + return n.enabled +} diff --git a/request.go b/request.go index 2758d2e..520b589 100644 --- a/request.go +++ b/request.go @@ -14,6 +14,7 @@ import ( // Request is the user defined request that will be invoked on the handler under test type Request struct { + specTest *SpecTest interceptor Intercept method string url string @@ -27,7 +28,18 @@ type Request struct { cookies []*Cookie basicAuth string context context.Context - apiTest *SpecTest +} + +// newRequest creates a new request +func newRequest(s *SpecTest) *Request { + return &Request{ + specTest: s, + query: map[string][]string{}, + queryCollection: map[string][]string{}, + headers: map[string][]string{}, + formData: map[string][]string{}, + cookies: []*Cookie{}, + } } // GraphQLRequestBody represents the POST request body as per the GraphQL spec @@ -65,7 +77,7 @@ func (r *Request) Bodyf(format string, args ...interface{}) *Request { func (r *Request) BodyFromFile(f string) *Request { b, err := os.ReadFile(filepath.Clean(f)) if err != nil { - r.apiTest.t.Fatal(err) + r.specTest.t.Fatal(err) } r.body = string(b) return r @@ -82,7 +94,7 @@ func (r *Request) JSON(v interface{}) *Request { default: asJSON, err := json.Marshal(x) if err != nil { - r.apiTest.t.Fatal(err) + r.specTest.t.Fatal(err) return nil } r.body = string(asJSON) @@ -117,7 +129,7 @@ func (r *Request) GraphQLRequest(body GraphQLRequestBody) *Request { data, err := json.Marshal(body) if err != nil { - r.apiTest.t.Fatal(err) + r.specTest.t.Fatal(err) } r.body = string(data) return r @@ -210,7 +222,7 @@ func (r *Request) MultipartFormData(name string, values ...string) *Request { for _, value := range values { if err := r.multipart.WriteField(name, value); err != nil { - r.apiTest.t.Fatal(err) + r.specTest.t.Fatal(err) } } return r @@ -227,17 +239,17 @@ func (r *Request) MultipartFile(name string, ff ...string) *Request { func() { file, err := os.Open(filepath.Clean(f)) if err != nil { - r.apiTest.t.Fatal(err) + r.specTest.t.Fatal(err) } defer file.Close() //nolint part, err := r.multipart.CreateFormFile(name, filepath.Base(file.Name())) if err != nil { - r.apiTest.t.Fatal(err) + r.specTest.t.Fatal(err) } if _, err = io.Copy(part, file); err != nil { - r.apiTest.t.Fatal(err) + r.specTest.t.Fatal(err) } }() } @@ -254,12 +266,12 @@ func (r *Request) setMultipartWriter() { func (r *Request) checkCombineFormDataWithMultipart() { if r.multipart != nil && len(r.formData) > 0 { - r.apiTest.t.Fatal("FormData (application/x-www-form-urlencoded) and MultiPartFormData(multipart/form-data) cannot be combined") + r.specTest.t.Fatal("FormData (application/x-www-form-urlencoded) and MultiPartFormData(multipart/form-data) cannot be combined") } } // Expect marks the request spec as complete and following code will define the expected response func (r *Request) Expect(t TestingT) *Response { - r.apiTest.t = t - return r.apiTest.response + r.specTest.t = t + return r.specTest.response } diff --git a/response.go b/response.go index 07b2547..0bfcd7a 100644 --- a/response.go +++ b/response.go @@ -12,6 +12,7 @@ import ( // Response is the user defined expected response from the application under test type Response struct { + specTest *SpecTest status int body string headers map[string][]string @@ -20,10 +21,21 @@ type Response struct { cookies []*Cookie cookiesPresent []string cookiesNotPresent []string - specTest *SpecTest assert []Assert } +func newResponse(s *SpecTest) *Response { + return &Response{ + specTest: s, + headers: map[string][]string{}, + headersPresent: []string{}, + headersNotPresent: []string{}, + cookies: []*Cookie{}, + cookiesPresent: []string{}, + cookiesNotPresent: []string{}, + } +} + // Body is the expected response body func (r *Response) Body(b string) *Response { r.body = b @@ -126,7 +138,7 @@ func (r *Response) End() Result { } }() - if specTest.handler == nil && !specTest.networkingEnabled { + if specTest.handler == nil && !specTest.network.isEnable() { specTest.t.Fatal("either define a http.Handler or enable networking") } diff --git a/spectest.go b/spectest.go index 506f60e..db8a886 100644 --- a/spectest.go +++ b/spectest.go @@ -23,10 +23,8 @@ type SpecTest struct { debugEnabled bool // mockResponseDelayEnabled will turn on mock response delays (defaults to OFF) mockResponseDelayEnabled bool - // networkingEnabled will enable networking for provided clients - networkingEnabled bool - // networkingHTTPClient is the http client used when networking is enabled - networkingHTTPClient *http.Client + // network is used to enable/disable networking for the test + network *network // reporter is the report formatter. reporter ReportFormatter // verifier is the assertion implementation. Default is DefaultVerifier. @@ -66,30 +64,20 @@ type SpecTest struct { // Observe will be called by with the request and response on completion type Observe func(*http.Response, *http.Request, *SpecTest) -// New creates a new api test. The name is optional and will appear in test reports +// New creates a new api test. The name is optional and will appear in test reports. +// The name is only used name[0]. name[1]... are ignored. func New(name ...string) *SpecTest { specTest := &SpecTest{ - meta: newMeta(), + interval: NewInterval(), + meta: newMeta(), + network: newNetwork(), } - - request := &Request{ - apiTest: specTest, - headers: map[string][]string{}, - query: map[string][]string{}, - formData: map[string][]string{}, - } - response := &Response{ - specTest: specTest, - headers: map[string][]string{}, - } - specTest.request = request - specTest.response = response + specTest.request = newRequest(specTest) + specTest.response = newResponse(specTest) if len(name) > 0 { specTest.name = name[0] } - specTest.interval = NewInterval() - return specTest } @@ -104,13 +92,14 @@ func HandlerFunc(handlerFunc http.HandlerFunc) *SpecTest { } // EnableNetworking will enable networking for provided clients +// If no clients are provided, the default http client will be used. +// If multiple clients are provided, the first client will be used. func (s *SpecTest) EnableNetworking(clients ...*http.Client) *SpecTest { - s.networkingEnabled = true + s.network.enabled = true if len(clients) == 1 { - s.networkingHTTPClient = clients[0] + s.network.Client = clients[0] return s } - s.networkingHTTPClient = http.DefaultClient return s } @@ -470,11 +459,11 @@ func (s *SpecTest) doRequest() (*http.Response, *http.Request) { var res *http.Response var err error - if !s.networkingEnabled { + if !s.network.isEnable() { s.serveHTTP(resRecorder, copyHTTPRequest(req)) res = resRecorder.Result() } else { - res, err = s.networkingHTTPClient.Do(copyHTTPRequest(req)) + res, err = s.network.Do(copyHTTPRequest(req)) if err != nil { s.t.Fatal(err) } @@ -615,7 +604,7 @@ func (s *SpecTest) buildRequest() *http.Request { if s.request.multipart != nil { err := s.request.multipart.Close() if err != nil { - s.request.apiTest.t.Fatal(err) + s.request.specTest.t.Fatal(err) } s.request.Header("Content-Type", s.request.multipart.FormDataContentType()) @@ -629,7 +618,7 @@ func (s *SpecTest) buildRequest() *http.Request { req.URL.RawQuery = formatQuery(s.request) req.Host = SystemUnderTestDefaultName - if s.networkingEnabled { + if s.network.isEnable() { req.Host = req.URL.Host } From 4a23a0fe46b2e910c16fac4ba5164be0d94ae0ef Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Thu, 12 Oct 2023 22:17:14 +0900 Subject: [PATCH 3/6] Refactor mock --- CHANGELOG.md | 7 +++- assert.go | 4 +-- debug.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ mocks.go | 91 +++++++++++++++++++++++++++------------------------- response.go | 7 ++-- spectest.go | 26 +++++---------- 6 files changed, 151 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91bc7f6..e7054e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## [0.0.5, unreleased] - 2023-XX-XX +## [0.0.8, unreleased] - 2023-xx-xx +### Changed +- Refactoring + - Refactoring for internal code. For example, introduce network struct. + +## [0.0.5 - 0.0.7] - 2023-10-12 ### Added - GitHub Actions workflow for [gosec](https://github.com/securego/gosec). - Fix gosec issue diff --git a/assert.go b/assert.go index c7869d2..bfee2c4 100644 --- a/assert.go +++ b/assert.go @@ -324,13 +324,13 @@ type labeledContent struct { // NoopVerifier is a verifier that does not perform verification type NoopVerifier struct{} +var _ Verifier = NoopVerifier{} + // True is always true func (n NoopVerifier) True(_ TestingT, _ bool, _ ...interface{}) bool { return true } -var _ Verifier = NoopVerifier{} - // Equal does not perform any assertion and always returns true func (n NoopVerifier) Equal(_ TestingT, _, _ interface{}, _ ...interface{}) bool { return true diff --git a/debug.go b/debug.go index 46a6de0..941d152 100644 --- a/debug.go +++ b/debug.go @@ -2,17 +2,103 @@ package spectest import ( "fmt" + "net/http" + "net/http/httputil" "strings" ) +// debug is used to enable/disable debug logging +type debug struct { + enabled bool +} + +// newDebug creates a new debug setting +func newDebug() *debug { + return &debug{} +} + +// enable will enable debug logging +func (d *debug) enable() { + d.enabled = true +} + +// disable will disable debug logging +func (d *debug) disable() { + d.enabled = false +} + +// isEnable returns true if debug logging is enabled +func (d *debug) isEnable() bool { + return d.enabled +} + +// dumpResponse is used to dump the response. +// If debug logging is disabled, this method will do nothing. +func (d *debug) dumpRequest(req *http.Request) { + if !d.isEnable() { + return + } + requestDump, err := httputil.DumpRequest(req, true) + if err == nil { + debugLog(requestDebugPrefix(), "inbound http request", string(requestDump)) + } + // TODO: handle error +} + +// dumpResponse is used to dump the response. +// If debug logging is disabled, this method will do nothing. +func (d *debug) dumpResponse(res *http.Response) { + if !d.isEnable() { + return + } + responseDump, err := httputil.DumpResponse(res, true) + if err == nil { + debugLog(responseDebugPrefix(), "final response", string(responseDump)) + } + // TODO: handle error +} + +// duration is used to print the duration of the test. +// If debug logging is disabled, this method will do nothing. +func (d *debug) duration(interval *Interval) { + if !d.isEnable() { + return + } + fmt.Printf("Duration: %s\n", interval.Duration()) +} + +// mock is used to print the request and response from the mock. +func (d *debug) mock(res *http.Response, req *http.Request) { + if !d.isEnable() { + return + } + + requestDump, err := httputil.DumpRequestOut(req, true) + if err == nil { + debugLog(requestDebugPrefix(), "request to mock", string(requestDump)) + } + + if res != nil { + responseDump, err := httputil.DumpResponse(res, true) + if err == nil { + debugLog(responseDebugPrefix(), "response from mock", string(responseDump)) + } + } else { + debugLog(responseDebugPrefix(), "response from mock", "") + } +} + +// debugLog is used to print debug information func debugLog(prefix, header, msg string) { fmt.Printf("\n%s %s\n%s\n", prefix, header, msg) } +// requestDebugLog is used to print debug information for the request func requestDebugPrefix() string { return fmt.Sprintf("%s>", strings.Repeat("-", 10)) } +// responseDebugLog is used to print debug information for the response func responseDebugPrefix() string { return fmt.Sprintf("<%s", strings.Repeat("-", 10)) } diff --git a/mocks.go b/mocks.go index 69fff5f..403cafe 100644 --- a/mocks.go +++ b/mocks.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "net/http" - "net/http/httputil" "net/textproto" "net/url" "os" @@ -25,7 +24,7 @@ import ( // Transport wraps components used to observe and manipulate the real request and response objects type Transport struct { - debugEnabled bool + debug *debug mockResponseDelayEnabled bool mocks []*Mock nativeTransport http.RoundTripper @@ -37,18 +36,19 @@ type Transport struct { func newTransport( mocks []*Mock, httpClient *http.Client, - debugEnabled bool, + debug *debug, mockResponseDelayEnabled bool, observers []Observe, apiTest *SpecTest) *Transport { t := &Transport{ mocks: mocks, httpClient: httpClient, - debugEnabled: debugEnabled, + debug: debug, mockResponseDelayEnabled: mockResponseDelayEnabled, observers: observers, apiTest: apiTest, } + if httpClient != nil { t.nativeTransport = httpClient.Transport } else { @@ -99,11 +99,9 @@ func (u *unmatchedMockError) orderedMockKeys() []int { // RoundTrip implementation intended to match a given expected mock request or throw an error with a list of reasons why no match was found. func (r *Transport) RoundTrip(req *http.Request) (mockResponse *http.Response, matchErrors error) { - if r.debugEnabled { - defer func() { - debugMock(mockResponse, req) - }() - } + defer func() { + r.debug.mock(mockResponse, req) + }() if r.observers != nil && len(r.observers) > 0 { defer func() { @@ -129,29 +127,12 @@ func (r *Transport) RoundTrip(req *http.Request) (mockResponse *http.Response, m return res, nil } - if r.debugEnabled { + if r.debug.isEnable() { fmt.Printf("failed to match mocks. Errors: %s\n", matchErrors) } - return nil, matchErrors } -func debugMock(res *http.Response, req *http.Request) { - requestDump, err := httputil.DumpRequestOut(req, true) - if err == nil { - debugLog(requestDebugPrefix(), "request to mock", string(requestDump)) - } - - if res != nil { - responseDump, err := httputil.DumpResponse(res, true) - if err == nil { - debugLog(responseDebugPrefix(), "response from mock", string(responseDump)) - } - } else { - debugLog(responseDebugPrefix(), "response from mock", "") - } -} - // Hijack replace the transport implementation of the interaction under test in order to observe, mock and inject expectations func (r *Transport) Hijack() { if r.httpClient != nil { @@ -220,7 +201,7 @@ type Mock struct { request *MockRequest response *MockResponse httpClient *http.Client - debugStandalone bool + debugStandalone *debug times int timesSet bool } @@ -274,6 +255,26 @@ type MockRequest struct { matchers []Matcher } +// newMockRequest return new MockRequest +func newMockRequest(m *Mock) *MockRequest { + return &MockRequest{ + mock: m, + headers: map[string][]string{}, + headerPresent: []string{}, + headerNotPresent: []string{}, + formData: map[string][]string{}, + formDataPresent: []string{}, + formDataNotPresent: []string{}, + query: map[string][]string{}, + queryPresent: []string{}, + queryNotPresent: []string{}, + cookie: []Cookie{}, + cookiePresent: []string{}, + cookieNotPresent: []string{}, + matchers: defaultMatchers, + } +} + // UnmatchedMock exposes some information about mocks that failed to match a request type UnmatchedMock struct { URL url.URL @@ -290,17 +291,27 @@ type MockResponse struct { fixedDelayMillis int64 } +// newMockResponse return new MockResponse +func newMockResponse(m *Mock) *MockResponse { + return &MockResponse{ + mock: m, + headers: map[string][]string{}, + cookies: []*Cookie{}, + } +} + // StandaloneMocks for using mocks outside of API tests context type StandaloneMocks struct { mocks []*Mock httpClient *http.Client - debug bool + debug *debug } // NewStandaloneMocks create a series of StandaloneMocks func NewStandaloneMocks(mocks ...*Mock) *StandaloneMocks { return &StandaloneMocks{ mocks: mocks, + debug: newDebug(), } } @@ -312,7 +323,7 @@ func (r *StandaloneMocks) HTTPClient(cli *http.Client) *StandaloneMocks { // Debug switch on debugging mode func (r *StandaloneMocks) Debug() *StandaloneMocks { - r.debug = true + r.debug.enable() return r } @@ -334,27 +345,19 @@ func (r *StandaloneMocks) End() func() { // NewMock create a new mock, ready for configuration using the builder pattern func NewMock() *Mock { mock := &Mock{ - m: &sync.Mutex{}, - times: 1, - } - mock.request = &MockRequest{ - mock: mock, - headers: map[string][]string{}, - formData: map[string][]string{}, - query: map[string][]string{}, - matchers: defaultMatchers, - } - mock.response = &MockResponse{ - mock: mock, - headers: map[string][]string{}, + debugStandalone: newDebug(), + m: &sync.Mutex{}, + times: 1, } + mock.request = newMockRequest(mock) + mock.response = newMockResponse(mock) return mock } // Debug is used to set debug mode for mocks in standalone mode. // This is overridden by the debug setting in the `SpecTest` struct func (m *Mock) Debug() *Mock { - m.debugStandalone = true + m.debugStandalone.enable() return m } diff --git a/response.go b/response.go index 0bfcd7a..9df0ad8 100644 --- a/response.go +++ b/response.go @@ -132,10 +132,7 @@ func (r *Response) Assert(fn func(*http.Response, *http.Request) error) *Respons func (r *Response) End() Result { specTest := r.specTest defer func() { - if specTest.debugEnabled { - specTest.interval.End() - fmt.Printf("Duration: %s\n", specTest.interval.Duration()) - } + specTest.debug.duration(specTest.interval) }() if specTest.handler == nil && !specTest.network.isEnable() { @@ -174,7 +171,7 @@ func (r *Response) runTest() *http.Response { a.transport = newTransport( a.mocks, a.httpClient, - a.debugEnabled, + a.debug, a.mockResponseDelayEnabled, a.mocksObservers, r.specTest, diff --git a/spectest.go b/spectest.go index db8a886..58d5859 100644 --- a/spectest.go +++ b/spectest.go @@ -9,9 +9,8 @@ import ( "io" "net/http" "net/http/httptest" - "net/http/httputil" "net/url" - "runtime/debug" + runtimeDebug "runtime/debug" "sort" "strings" "time" @@ -19,8 +18,8 @@ import ( // SpecTest is the top level struct holding the test spec type SpecTest struct { - // debugEnabled will log the http wire representation of all http interactions - debugEnabled bool + // debug is used to log the http wire representation of all http interactions + debug *debug // mockResponseDelayEnabled will turn on mock response delays (defaults to OFF) mockResponseDelayEnabled bool // network is used to enable/disable networking for the test @@ -68,6 +67,7 @@ type Observe func(*http.Response, *http.Request, *SpecTest) // The name is only used name[0]. name[1]... are ignored. func New(name ...string) *SpecTest { specTest := &SpecTest{ + debug: newDebug(), interval: NewInterval(), meta: newMeta(), network: newNetwork(), @@ -114,7 +114,7 @@ func (s *SpecTest) EnableMockResponseDelay() *SpecTest { // under test, the response returned by the application and any interactions that are // intercepted by the mock server. func (s *SpecTest) Debug() *SpecTest { - s.debugEnabled = true + s.debug.enable() return s } @@ -450,12 +450,7 @@ func (s *SpecTest) doRequest() (*http.Response, *http.Request) { } resRecorder := httptest.NewRecorder() - if s.debugEnabled { - requestDump, err := httputil.DumpRequest(req, true) - if err == nil { - debugLog(requestDebugPrefix(), "inbound http request", string(requestDump)) - } - } + s.debug.dumpRequest(req) var res *http.Response var err error @@ -469,12 +464,7 @@ func (s *SpecTest) doRequest() (*http.Response, *http.Request) { } } - if s.debugEnabled { - responseDump, err := httputil.DumpResponse(res, true) - if err == nil { - debugLog(responseDebugPrefix(), "final response", string(responseDump)) - } - } + s.debug.dumpResponse(res) return res, req } @@ -482,7 +472,7 @@ func (s *SpecTest) doRequest() (*http.Response, *http.Request) { func (s *SpecTest) serveHTTP(res *httptest.ResponseRecorder, req *http.Request) { defer func() { if err := recover(); err != nil { - s.t.Fatalf("%s: %s", err, debug.Stack()) + s.t.Fatalf("%s: %s", err, runtimeDebug.Stack()) } }() From adb16d29086553e5b8c8ff0fccaa8eda6018bdc3 Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Thu, 12 Oct 2023 22:31:48 +0900 Subject: [PATCH 4/6] Refactor basic authentication --- authentication.go | 44 +++++++++++++++++++++++++++ authentication_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++ mocks.go | 32 ++++++-------------- 3 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 authentication.go create mode 100644 authentication_test.go diff --git a/authentication.go b/authentication.go new file mode 100644 index 0000000..60f0ae6 --- /dev/null +++ b/authentication.go @@ -0,0 +1,44 @@ +package spectest + +import "fmt" + +// basicAuth is represents the basic auth credentials +type basicAuth struct { + // userName is the userName for basic auth + userName string + // password is the password for basic auth + password string +} + +// newBasicAuth creates a new basic auth +func newBasicAuth(userName, password string) basicAuth { + return basicAuth{ + userName: userName, + password: password, + } +} + +// isUserNameEmpty returns true if the userName is empty +func (b basicAuth) isUserNameEmpty() bool { + return b.userName == "" +} + +// isPasswordEmpty returns true if the password is empty +func (b basicAuth) isPasswordEmpty() bool { + return b.password == "" +} + +// auth will authenticate the user +// auth will return an error if the user is not authenticated +func (b basicAuth) auth(userName, password string) error { + if b.userName != userName { + return fmt.Errorf("basic auth request username '%s' did not match mock username '%s'", + userName, b.userName) + } + + if b.password != password { + return fmt.Errorf("basic auth request password '%s' did not match mock password '%s'", + password, b.password) + } + return nil +} diff --git a/authentication_test.go b/authentication_test.go new file mode 100644 index 0000000..c4da58b --- /dev/null +++ b/authentication_test.go @@ -0,0 +1,69 @@ +package spectest + +import "testing" + +func TestBasicAuthAuth(t *testing.T) { + type fields struct { + userName string + password string + } + type args struct { + userName string + password string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "auth with valid credentials", + fields: fields{ + userName: "user", + password: "password", + }, + args: args{ + userName: "user", + password: "password", + }, + wantErr: false, + }, + { + name: "auth with invalid credentials. bad password", + fields: fields{ + userName: "user", + password: "password", + }, + args: args{ + userName: "user", + password: "invalid-password", + }, + wantErr: true, + }, + { + name: "auth with invalid credentials. bad user name", + fields: fields{ + userName: "user", + password: "password", + }, + args: args{ + userName: "invalid-user", + password: "password", + }, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + b := &basicAuth{ + userName: tt.fields.userName, + password: tt.fields.password, + } + if err := b.auth(tt.args.userName, tt.args.password); (err != nil) != tt.wantErr { + t.Errorf("basicAuth.auth() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/mocks.go b/mocks.go index 403cafe..3149247 100644 --- a/mocks.go +++ b/mocks.go @@ -30,7 +30,7 @@ type Transport struct { nativeTransport http.RoundTripper httpClient *http.Client observers []Observe - apiTest *SpecTest + specTest *SpecTest } func newTransport( @@ -39,14 +39,14 @@ func newTransport( debug *debug, mockResponseDelayEnabled bool, observers []Observe, - apiTest *SpecTest) *Transport { + specTest *SpecTest) *Transport { t := &Transport{ mocks: mocks, httpClient: httpClient, debug: debug, mockResponseDelayEnabled: mockResponseDelayEnabled, observers: observers, - apiTest: apiTest, + specTest: specTest, } if httpClient != nil { @@ -106,7 +106,7 @@ func (r *Transport) RoundTrip(req *http.Request) (mockResponse *http.Response, m if r.observers != nil && len(r.observers) > 0 { defer func() { for _, observe := range r.observers { - observe(mockResponse, req, r.apiTest) + observe(mockResponse, req, r.specTest) } }() } @@ -237,8 +237,7 @@ type MockRequest struct { url *url.URL method string headers map[string][]string - basicAuthUsername string - basicAuthPassword string + basicAuth basicAuth headerPresent []string headerNotPresent []string formData map[string][]string @@ -583,9 +582,8 @@ func (r *MockRequest) HeaderNotPresent(key string) *MockRequest { } // BasicAuth configures the mock request to match the given basic auth parameters -func (r *MockRequest) BasicAuth(username, password string) *MockRequest { - r.basicAuthUsername = username - r.basicAuthPassword = password +func (r *MockRequest) BasicAuth(userName, password string) *MockRequest { + r.basicAuth = newBasicAuth(userName, password) return r } @@ -867,26 +865,14 @@ var headerMatcher = func(req *http.Request, spec *MockRequest) error { } var basicAuthMatcher = func(req *http.Request, spec *MockRequest) error { - if spec.basicAuthUsername == "" { + if spec.basicAuth.isUserNameEmpty() || spec.basicAuth.isPasswordEmpty() { return nil } - username, password, ok := req.BasicAuth() if !ok { return errors.New("request did not contain valid HTTP Basic Authentication string") } - - if spec.basicAuthUsername != username { - return fmt.Errorf("basic auth request username '%s' did not match mock username '%s'", - username, spec.basicAuthUsername) - } - - if spec.basicAuthPassword != password { - return fmt.Errorf("basic auth request password '%s' did not match mock password '%s'", - password, spec.basicAuthPassword) - } - - return nil + return spec.basicAuth.auth(username, password) } var headerPresentMatcher = func(req *http.Request, spec *MockRequest) error { From 915906130792dd131a3ad42ae9aad3050400126f Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Thu, 12 Oct 2023 22:37:48 +0900 Subject: [PATCH 5/6] Fix linter --- .github/workflows/govulncheck.yml | 15 --------------- README.md | 1 - examples/Makefile | 17 ++++++++++++++++- 3 files changed, 16 insertions(+), 17 deletions(-) delete mode 100644 .github/workflows/govulncheck.yml diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml deleted file mode 100644 index d5aa717..0000000 --- a/.github/workflows/govulncheck.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Vuluncheck - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - govulncheck_job: - runs-on: ubuntu-latest - name: Run govulncheck - steps: - - id: govulncheck - uses: golang/govulncheck-action@v1 diff --git a/README.md b/README.md index 904b9fb..2056d3e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![LinuxUnitTest](https://github.com/go-spectest/spectest/actions/workflows/linux_test.yml/badge.svg)](https://github.com/go-spectest/spectest/actions/workflows/linux_test.yml) [![MacUnitTest](https://github.com/go-spectest/spectest/actions/workflows/mac_test.yml/badge.svg)](https://github.com/go-spectest/spectest/actions/workflows/mac_test.yml) [![WindowsUnitTest](https://github.com/go-spectest/spectest/actions/workflows/windows_test.yml/badge.svg)](https://github.com/go-spectest/spectest/actions/workflows/windows_test.yml) -[![Vuluncheck](https://github.com/go-spectest/spectest/actions/workflows/govulncheck.yml/badge.svg)](https://github.com/go-spectest/spectest/actions/workflows/govulncheck.yml) [![UnitTestExampleCodes](https://github.com/go-spectest/spectest/actions/workflows/test-examples.yml/badge.svg)](https://github.com/go-spectest/spectest/actions/workflows/test-examples.yml) [![reviewdog](https://github.com/go-spectest/spectest/actions/workflows/reviewdog.yml/badge.svg)](https://github.com/go-spectest/spectest/actions/workflows/reviewdog.yml) [![Gosec](https://github.com/go-spectest/spectest/actions/workflows/gosec.yml/badge.svg)](https://github.com/go-spectest/spectest/actions/workflows/gosec.yml) diff --git a/examples/Makefile b/examples/Makefile index 22541f4..cd19e03 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -11,4 +11,19 @@ test: cd sequence-diagrams && go test -v ./... cd sequence-diagrams-with-mysql-database && go test -v ./... cd sequence-diagrams-with-postgres-database && go test -v ./... - cd sequence-diagrams-with-sqlite-database && go test -v -covermode=atomic ./... \ No newline at end of file + cd sequence-diagrams-with-sqlite-database && go test -v -covermode=atomic ./... + +mod: + cd echo && go mod tidy + cd fiber && go mod tidy + cd gin && go mod tidy + cd ginkgo && go mod tidy + cd gorilla && go mod tidy + cd httprouter && go mod tidy + #cd iris && go mod tidy + cd mocks && go mod tidy + cd plantuml && go mod tidy + cd sequence-diagrams && go mod tidy + cd sequence-diagrams-with-mysql-database && go mod tidy + cd sequence-diagrams-with-postgres-database && go mod tidy + cd sequence-diagrams-with-sqlite-database && go mod tidy \ No newline at end of file From 1af24648428e90437c72717249b28fdf7d989b3f Mon Sep 17 00:00:00 2001 From: CHIKAMATSU Naohiro Date: Thu, 12 Oct 2023 22:41:28 +0900 Subject: [PATCH 6/6] Change spectest version --- examples/sequence-diagrams/go.mod | 2 +- examples/sequence-diagrams/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/sequence-diagrams/go.mod b/examples/sequence-diagrams/go.mod index 1de2b90..18d950c 100644 --- a/examples/sequence-diagrams/go.mod +++ b/examples/sequence-diagrams/go.mod @@ -3,7 +3,7 @@ module github.com/go-spectest/spectest/examples/sequence-diagrams go 1.18 require ( - github.com/go-spectest/spectest v0.0.5 + github.com/go-spectest/spectest v0.0.6 github.com/gorilla/mux v1.8.0 ) diff --git a/examples/sequence-diagrams/go.sum b/examples/sequence-diagrams/go.sum index d2a462e..dfb6ef2 100644 --- a/examples/sequence-diagrams/go.sum +++ b/examples/sequence-diagrams/go.sum @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-spectest/diff v0.0.0-20231006143314-ce490574d4a9 h1:I05FIUaZLNe9Jo6ZCvODDGBqHMk3TYd7V/wV9dZhwXk= github.com/go-spectest/diff v0.0.0-20231006143314-ce490574d4a9/go.mod h1:wWRXl4ClWLDIPkL/lS8SSxojHRg1cGngvPcya/mYhf0= -github.com/go-spectest/spectest v0.0.5 h1:f3Bi4cIFbS8IxAJawd0L28OszzAqvw8tlaXGh/G4fjI= -github.com/go-spectest/spectest v0.0.5/go.mod h1:XIYK9byIF4TJDO2wrdXMcc06Gg2PfxiJF0Oc7uEfJXM= +github.com/go-spectest/spectest v0.0.6 h1:IqQUw1+Tq49ml75C6C3DBtXFilnOXUW8ZBFKlftxBQM= +github.com/go-spectest/spectest v0.0.6/go.mod h1:XIYK9byIF4TJDO2wrdXMcc06Gg2PfxiJF0Oc7uEfJXM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=