Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

D-34366 Add support for query parameters for test until #63

Merged
merged 10 commits into from
May 24, 2024
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
}
Loading