Skip to content

Commit

Permalink
D-34366 Add support for query parameters for test until (#63)
Browse files Browse the repository at this point in the history
* D-34366 Add support for query paramters for test until

* D-34366 Optimize mapping

* D-34366 Fix query match

* D-34366 Fix calculateHash for empty values

* D-34366 Fix missing response body

* D-34366 Allow skip of query params check if it's missing in mock

* D-34366 Add fallback to check for non-query-check paths

* D-34366 Fix skip check error

* D-34366 Fix wrong logic around ignoring queries

* D-34366 Add missing documentation comment
  • Loading branch information
gpugar committed May 24, 2024
1 parent 13ab132 commit 3c288bc
Showing 1 changed file with 101 additions and 38 deletions.
139 changes: 101 additions & 38 deletions test/mock_http_client.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,63 @@
package test

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"k8s.io/client-go/rest"
"net/http"
"net/url"
"os"
"reflect"
"sort"
"strings"
)

// Mock is a definition of mock consisting of mocked path, queries and mock response body which is returned on matched mock pattern
type Mock struct {
method string
path string
queryParams url.Values
responseBody *MockBody
}

// flattenValues flat values map to be represented as string, with keeping in mind order of keys and paired values array
func flattenValues(values url.Values) string {
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)

var flattened []string
for _, key := range keys {
params := values[key]
sort.Strings(params)
flattened = append(flattened, fmt.Sprintf("%s:%s", key, strings.Join(params, ",")))
}

return strings.Join(flattened, ";")
}

// hash creates a unique hash key from method, path and query params map
func (m Mock) hash() string {
return calculateHash(m.method, m.path, m.queryParams)
}

// calculateHash creates a unique hash key from method, path and query params map
func calculateHash(method string, path string, queryParams url.Values) string {
combinedStr := fmt.Sprintf("%s::%s", method, path)
flattenedValues := ""
if len(queryParams) > 0 {
flattenedValues = flattenValues(queryParams)
}
finalStr := fmt.Sprintf("%s::%s", combinedStr, flattenedValues)
hash := sha256.Sum256([]byte(finalStr))
hashKey := hex.EncodeToString(hash[:])
return hashKey
}

// MockBody is a mock implementation of the http.Response.Body interface.
type MockBody struct {
offset int
Expand Down Expand Up @@ -37,16 +87,33 @@ func (b *MockBody) Close() error {

// MockResult represents a mocked HTTP response.
type MockResult struct {
Method string
Path string
Filename string
Data []byte
StatusCode int
Method string
Path string
QueryParams url.Values
Filename string
Data []byte
StatusCode int
}

// ToMock create Mock reference out of the MockResult
func (mr MockResult) ToMock() *Mock {
mockBody := &MockBody{statusCode: mr.StatusCode}
if mr.Filename != "" {
mockBody.filename = mr.Filename
} else {
mockBody.response = mr.Data
}
return &Mock{
method: mr.Method,
path: mr.Path,
queryParams: mr.QueryParams,
responseBody: mockBody,
}
}

// MockHttpClient is a mock implementation of the rest.HTTPClient interface.
type MockHttpClient struct {
mocks map[string]map[string]*MockBody
mocks map[string]*Mock
}

// the404Response represents HTTP mock response with 404 code
Expand All @@ -59,52 +126,48 @@ var the404Response = &http.Response{

// NewMockHttpClient creates a new instance of MockHttpClient based on the provided mock results.
func NewMockHttpClient(mocks []MockResult) rest.HTTPClient {
mockBodiesMap := make(map[string]map[string]*MockBody)
for _, mock := range mocks {
ref := mockBodiesMap[mock.Method]
if ref == nil {
ref = make(map[string]*MockBody)
}
if mock.Filename != "" {
ref[mock.Path] = &MockBody{
filename: mock.Filename,
statusCode: mock.StatusCode,
}
} else {
ref[mock.Path] = &MockBody{
response: mock.Data,
statusCode: mock.StatusCode,
}
}
mockBodiesMap[mock.Method] = ref
mockMap := make(map[string]*Mock)
for _, mockResults := range mocks {
mock := mockResults.ToMock()
mockMap[mock.hash()] = mock
}
return &MockHttpClient{
mocks: mockBodiesMap,
mocks: mockMap,
}
}

// getPath returns the path from the provided URL.
func getPath(url *url.URL) string {
var path string
if url.RawPath != "" {
path = url.RawPath
return url.RawPath
} else {
path = url.Path
return url.Path
}
if url.RawQuery != "" {
return path + "?" + url.RawQuery
}
return path
}

// Do performs the mock HTTP request.
// Do processes the mock HTTP request.
func (c MockHttpClient) Do(req *http.Request) (*http.Response, error) {
mock, exists := c.mocks[req.Method][getPath(req.URL)]
path := getPath(req.URL)
requestQueryParams, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
return nil, err
}

hash := calculateHash(req.Method, path, requestQueryParams)
mockData, exists := c.mocks[hash]

// fallback and check if we ignore query params check
if !exists {
hash = calculateHash(req.Method, path, nil)
mockData, exists = c.mocks[hash]
}
if exists {
return &http.Response{
Body: mock,
StatusCode: mock.statusCode,
}, nil
if len(mockData.queryParams) == 0 || reflect.DeepEqual(requestQueryParams, mockData.queryParams) {
return &http.Response{
Body: mockData.responseBody,
StatusCode: mockData.responseBody.statusCode,
}, nil
}
}
return the404Response, nil
}

0 comments on commit 3c288bc

Please sign in to comment.