-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
680 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package testpilot | ||
|
||
import ( | ||
"bufio" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"github.com/google/go-cmp/cmp" | ||
"reflect" | ||
"strings" | ||
) | ||
|
||
type AssertionFunc func(body []byte) error | ||
|
||
// AssertEqual asserts that the response body is equal to the given value | ||
// It uses reflect.DeepEqual to compare the response body with the given value | ||
func AssertEqual(response any) AssertionFunc { | ||
return func(body []byte) error { | ||
t := reflect.TypeOf(response) | ||
if t.Kind() == reflect.Ptr { | ||
t = t.Elem() // If it's a pointer, get the type it points to | ||
} | ||
newVar := reflect.New(t).Interface() | ||
var got any | ||
if t.Kind() == reflect.Struct || t.Kind() == reflect.Map || t.Kind() == reflect.Slice || t.Kind() == reflect.Array { | ||
if err := json.Unmarshal(body, newVar); err != nil { | ||
return err | ||
} | ||
got = reflect.ValueOf(newVar).Elem().Interface() | ||
} else { | ||
got = strings.TrimSpace(string(body)) | ||
} | ||
if !reflect.DeepEqual(response, got) { | ||
diff := cmp.Diff(response, got) | ||
coloredDiff := formatDiff(diff) | ||
return errors.New(fmt.Sprintf("response body does not match. \n "+ | ||
"expected: %v \n "+ | ||
"got : %v \n\n Diff: \n %v", truncate(response), truncate(got), coloredDiff)) | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
// AssertPath asserts that the value at the given path in the response body satisfies the given assertion | ||
// the path is a dot separated string representing the path to the value in the response body | ||
// e.g. "data.user.0.name" will navigate to the first user in the data array and check if the name field satisfies the given assertion | ||
func AssertPath[T comparable](path string, assert func(val T) error) AssertionFunc { | ||
return func(body []byte) error { | ||
var data any | ||
if err := json.Unmarshal(body, &data); err != nil { | ||
return err | ||
} | ||
if path[0] == '.' { | ||
path = strings.Replace(path, ".", "", 1) | ||
} | ||
value, err := navigateJSON(data, path) | ||
if err != nil { | ||
return err | ||
} | ||
v, err := convertToType[T](value) | ||
if err != nil { | ||
return err | ||
} | ||
if err := assert(v); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
func truncate(data interface{}) string { | ||
value := fmt.Sprintf("%#v", data) | ||
maxSize := bufio.MaxScanTokenSize - 100 | ||
if len(value) > maxSize { | ||
value = value[0:maxSize] + "..." | ||
} | ||
return value | ||
} | ||
func formatDiff(diff string) string { | ||
lines := strings.Split(diff, "\n") | ||
var formattedDiff string | ||
for _, line := range lines { | ||
if strings.HasPrefix(line, "-") { | ||
formattedDiff += fmt.Sprintf("\033[31m%s\033[0m\n", line) | ||
} else if strings.HasPrefix(line, "+") { | ||
formattedDiff += fmt.Sprintf("\033[32m%s\033[0m\n", line) | ||
} else { | ||
formattedDiff += fmt.Sprintf("%s\n", line) | ||
} | ||
} | ||
return formattedDiff | ||
} | ||
|
||
func convertToType[T comparable](value interface{}) (T, error) { | ||
var result T | ||
targetType := reflect.TypeOf(result) | ||
valueReflect := reflect.ValueOf(value) | ||
if valueReflect.Type().ConvertibleTo(targetType) { | ||
convertedValue := valueReflect.Convert(targetType) | ||
result = convertedValue.Interface().(T) | ||
return result, nil | ||
} | ||
if targetType.Kind() == reflect.Struct && valueReflect.Kind() == reflect.Map { | ||
valueMap := valueReflect.Interface().(map[string]interface{}) | ||
structValue := reflect.ValueOf(&result).Elem() | ||
|
||
for key, val := range valueMap { | ||
field := structValue.FieldByName(key) | ||
if field.IsValid() && field.CanSet() { | ||
field.Set(reflect.ValueOf(val).Convert(field.Type())) | ||
} | ||
} | ||
return result, nil | ||
} | ||
return result, fmt.Errorf("cannot convert value of type %s to type %s", valueReflect.Type(), targetType) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package testpilot | ||
|
||
import ( | ||
"errors" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestAssertEqual(t *testing.T) { | ||
type args struct { | ||
response any | ||
body []byte | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "Test AssertEqual", | ||
args: args{ | ||
response: map[string]interface{}{ | ||
"key": "value", | ||
}, | ||
body: []byte(`{"key":"value"}`), | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test Failure AssertEqual", | ||
args: args{ | ||
response: map[string]interface{}{ | ||
"key": "value", | ||
}, | ||
body: []byte(`{"key":"value1"}`), | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Test assert string", | ||
args: args{ | ||
response: "value", | ||
body: []byte(`value`), | ||
}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := AssertEqual(tt.args.response)(tt.args.body) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("AssertEqual() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestAssertPath(t *testing.T) { | ||
type args[T comparable] struct { | ||
path string | ||
assert func(val T) error | ||
body []byte | ||
} | ||
type testCase[T comparable] struct { | ||
name string | ||
args args[T] | ||
wantErr bool | ||
} | ||
tests := []testCase[int]{ | ||
{ | ||
name: "Test AssertPath", | ||
args: args[int]{ | ||
path: "key", | ||
assert: func(val int) error { | ||
if val != 1 { | ||
return errors.New("value is not 1") | ||
} | ||
return nil | ||
}, | ||
body: []byte(`{"key":1}`), | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Test Failure AssertPath", | ||
args: args[int]{ | ||
path: "key", | ||
assert: func(val int) error { | ||
if val != 1 { | ||
return errors.New("value is not 1") | ||
} | ||
return nil | ||
}, | ||
body: []byte(`{"key":2}`), | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Test AssertPath with nested object", | ||
args: args[int]{ | ||
path: "key.subkey", | ||
assert: func(val int) error { | ||
if val != 1 { | ||
return errors.New("value is not 1") | ||
} | ||
return nil | ||
}, | ||
body: []byte(`{"key":{"subkey":1}}`), | ||
}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := AssertPath(tt.args.path, tt.args.assert)(tt.args.body) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("AssertPath() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Test_convertToType_String(t *testing.T) { | ||
t.Run("Test convertToType string", func(t *testing.T) { | ||
got, err := convertToType[string]("value") | ||
if err != nil { | ||
t.Errorf("convertToType() error = %v", err) | ||
} | ||
if got != "value" { | ||
t.Errorf("convertToType() got = %v, want value", got) | ||
} | ||
}) | ||
t.Run("Test convertToType struct", func(t *testing.T) { | ||
type testStruct struct { | ||
Key string | ||
} | ||
got, err := convertToType[testStruct](map[string]any{"Key": "value"}) | ||
if err != nil { | ||
t.Errorf("convertToType() error = %v", err) | ||
} | ||
if !reflect.DeepEqual(got, testStruct{"value"}) { | ||
t.Errorf("convertToType() got = %v, want value", got) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package testpilot | ||
|
||
type Expect struct { | ||
expectedResponseCode *int | ||
expectedResponseBody *AssertionFunc | ||
} | ||
|
||
// Status sets the expected response code | ||
func (e *Expect) Status(code int) *Expect { | ||
e.expectedResponseCode = &code | ||
return e | ||
} | ||
|
||
// Body sets the expected response body | ||
func (e *Expect) Body(assertionFunc AssertionFunc) *Expect { | ||
e.expectedResponseBody = &assertionFunc | ||
return e | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= |
Oops, something went wrong.