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

new: colorize mock errors #27

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion checker/response_body/response_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func createWrongStatusError(statusCode int, known map[int]string) error {
for code := range known {
knownCodes = append(knownCodes, fmt.Sprintf("%d", code))
}
return colorize.NewNotEqualError("server responded with unexpected ", "status", ":", strings.Join(knownCodes, " / "), statusCode, nil)
return colorize.NewNotEqualError("server responded with unexpected %s:", "status", strings.Join(knownCodes, " / "), statusCode)
}

func (c *responseBodyChecker) Check(t models.TestInterface, result *models.Result) ([]error, error) {
Expand Down
13 changes: 4 additions & 9 deletions checker/response_db/response_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,25 @@ func compareDbResponseLength(expected, actual []string, query interface{}) error
diffCfg.Diffable = true
chunks := diff.DiffChunks(strings.Split(diffCfg.Sprint(expected), "\n"), strings.Split(diffCfg.Sprint(actual), "\n"))

tail := []*colorize.Part{
tail := []colorize.Part{
colorize.None("\n\n query: "),
colorize.Cyan(query),
colorize.None("\n diff (--- expected vs +++ actual):\n"),
}
tail = append(tail, colorize.MakeColorDiff(chunks)...)

return colorize.NewNotEqualError(
"quantity of ",
"quantity of %s do not match:",
"items in database",
" do not match:",
len(expected),
len(actual),
tail,
)
).AddParts(tail...)

return colorize.NewError(
colorize.None("quantity of items in database do not match (-expected: "),
"quantity of items in database do not match (-expected: %s +actual: %s)\n test query:\n%s\n result diff:\n%s",
colorize.Cyan(len(expected)),
colorize.None(" +actual: "),
colorize.Cyan(len(actual)),
colorize.None(")\n test query:\n"),
colorize.Cyan(query),
colorize.None("\n result diff:\n"),
colorize.Cyan(pretty.Compare(expected, actual)),
)
}
Expand Down
9 changes: 4 additions & 5 deletions checker/response_header/response_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (c *responseHeaderChecker) Check(t models.TestInterface, result *models.Res
actualValues, ok := result.ResponseHeaders[k]
if !ok {
errs = append(errs, colorize.NewError(
colorize.None("response does not include expected header "),
"response does not include expected header %s",
colorize.Cyan(k),
))
continue
Expand All @@ -44,16 +44,15 @@ func (c *responseHeaderChecker) Check(t models.TestInterface, result *models.Res
}
if len(actualValues) == 1 {
errs = append(errs, colorize.NewNotEqualError(
"response header ", k, " value does not match:",
"response header %s value does not match:",
k,
v,
actualValues[0],
nil,
))
} else {
errs = append(errs, colorize.NewError(
colorize.None("response header "),
"response header %s value does not match expected %s",
colorize.Cyan(k),
colorize.None(" value does not match expected "),
colorize.Green(v),
))
}
Expand Down
4 changes: 2 additions & 2 deletions checker/response_header/response_header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ func TestCheckWhenNotMatchedShouldReturnError(t *testing.T) {
t,
errText,
[]string{
"response does not include expected header Content-Type",
"response header Accept value does not match:\n expected: text/html\n actual: application/json",
"response does not include expected header 'Content-Type'",
"response header 'Accept' value does not match:\n expected: text/html\n actual: application/json",
},
)
}
8 changes: 2 additions & 6 deletions colorize/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package colorize

import (
"fmt"
"strings"

"github.com/kylelemons/godebug/diff"
)

func MakeColorDiff(chunks []diff.Chunk) []*Part {
parts := []*Part{}
func MakeColorDiff(chunks []diff.Chunk) []Part {
parts := []Part{}
for _, c := range chunks {
for _, line := range c.Added {
parts = append(parts, Red(fmt.Sprintf("+%s\n", line)))
Expand All @@ -20,8 +19,5 @@ func MakeColorDiff(chunks []diff.Chunk) []*Part {
parts = append(parts, None(fmt.Sprintf(" %s\n", line)))
}
}
if len(parts) != 0 {
parts[len(parts)-1].value = strings.TrimRight(parts[len(parts)-1].value, "\n")
}
return parts
}
103 changes: 64 additions & 39 deletions colorize/error.go
Original file line number Diff line number Diff line change
@@ -1,50 +1,51 @@
package colorize

import (
"errors"
"fmt"
"strings"

"github.com/fatih/color"
)

type Color int
func Red(v interface{}) Part {
return &partImpl{color.HiRedString, fmt.Sprintf("%v", v), false}
}

const (
ColorRed Color = iota
ColorCyan
ColorGreen
ColorNone
)
func Cyan(v interface{}) Part {
return &partImpl{color.HiCyanString, fmt.Sprintf("%v", v), true}
}

type Part struct {
attr Color
value string
func Green(v interface{}) Part {
return &partImpl{color.HiGreenString, fmt.Sprintf("%v", v), false}
}

func Red(v interface{}) *Part {
return &Part{ColorRed, fmt.Sprintf("%v", v)}
func None(v interface{}) Part {
return &partImpl{asIsString, fmt.Sprintf("%v", v), false}
}

func Cyan(v interface{}) *Part {
return &Part{ColorCyan, fmt.Sprintf("%v", v)}
func SubError(err error) Part {
return &subErrorImpl{err}
}

func Green(v interface{}) *Part {
return &Part{ColorGreen, fmt.Sprintf("%v", v)}
type Error struct {
parts []Part
}

func None(v interface{}) *Part {
return &Part{ColorNone, fmt.Sprintf("%v", v)}
func (e *Error) AddParts(values ...Part) *Error {
e.parts = append(e.parts, values...)
return e
}

type Error struct {
parts []*Part
func (e *Error) SetSubError(err error) *Error {
e.AddParts(SubError(err))
return e
}

func (e *Error) Error() string {
buf := strings.Builder{}
for _, p := range e.parts {
buf.WriteString(p.value)
buf.WriteString(p.Text())
}
return buf.String()
}
Expand All @@ -56,32 +57,56 @@ func asIsString(format string, a ...interface{}) string {
func (e *Error) ColorError() string {
buf := strings.Builder{}
for _, p := range e.parts {
if p.value == "" {
continue
buf.WriteString(p.ColorText())
}
return buf.String()
}

func alternateJoin(list1, list2 []Part) []Part {
result := []Part{}
i, j := 0, 0
for len(result) != len(list1)+len(list2) {
if i < len(list1) {
result = append(result, list1[i])
i++
}
f := asIsString
switch p.attr {
case ColorRed:
f = color.RedString
case ColorCyan:
f = color.CyanString
case ColorGreen:
f = color.GreenString
if j < len(list2) {
result = append(result, list2[j])
j++
}
}

return result
}

buf.WriteString(f(p.value))
func GetColoredValue(err error) string {
var pErr *Error
if errors.As(err, &pErr) {
return pErr.ColorError()
}
return buf.String()
return err.Error()
}

func NewError(format string, values ...Part) *Error {
plain := []Part{}
for _, s := range strings.Split(format, "%s") {
plain = append(plain, None(s))
}
return &Error{alternateJoin(plain, values)}
}

func NewEntityError(pattern, entity string) *Error {
return NewError(pattern, Cyan(entity))
}

func NewError(parts ...*Part) error {
return &Error{parts}
func NewNotEqualError(pattern, entity string, expected, actual interface{}) *Error {
pattern += "\n expected: %s\n actual: %s"
return NewError(pattern, Cyan(entity), Green(expected), Red(actual))
}

func NewNotEqualError(before, entity, after string, expected, actual interface{}, tail []*Part) error {
parts := []*Part{
func NewNotEqualError2(before, entity, after string, expected, actual interface{}) *Error {
parts := []Part{
None(before), Cyan(entity), None(after + "\n expected: "), Green(expected), None("\n actual: "), Red(actual),
}
parts = append(parts, tail...)
return NewError(parts...)
return NewError("", parts...)
}
35 changes: 35 additions & 0 deletions colorize/part.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package colorize

type Part interface {
Text() string
ColorText() string
}

type partImpl struct {
colorer func(format string, a ...interface{}) string
value string
entity bool
}

func (p *partImpl) Text() string {
if p.entity {
return "'" + p.value + "'"
}
return p.value
}

func (p *partImpl) ColorText() string {
return p.colorer(p.value)
}

type subErrorImpl struct {
err error
}

func (p *subErrorImpl) Text() string {
return ": " + p.err.Error()
}

func (p *subErrorImpl) ColorText() string {
return ": " + GetColoredValue(p.err)
}
10 changes: 4 additions & 6 deletions compare/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func leafMatchType(expected interface{}) leafsMatchType {
return pure
}

func diffStrings(a, b string) []*colorize.Part {
func diffStrings(a, b string) []colorize.Part {
chunks := diff.DiffChunks(strings.Split(a, "\n"), strings.Split(b, "\n"))
return colorize.MakeColorDiff(chunks)
}
Expand All @@ -232,18 +232,16 @@ func makeValueCompareError(path, msg string, expected, actual interface{}) error
}

// special case for multi-line strings
parts := []*colorize.Part{
colorize.None("at path "),
parts := []colorize.Part{
colorize.Cyan(path),
colorize.None(" " + msg + ":\n diff (--- expected vs +++ actual):\n"),
}

parts = append(parts, diffStrings(actualStr, expectedStr)...)
return colorize.NewError(parts...)
return colorize.NewError("at path %s "+msg+":\n diff (--- expected vs +++ actual):\n", parts...)
}

func makeError(path, msg string, expected, actual interface{}) error {
return colorize.NewNotEqualError("at path ", path, " "+msg+":", expected, actual, nil)
return colorize.NewNotEqualError("at path %s "+msg+":", path, expected, actual)
}

func convertToArray(array interface{}) []interface{} {
Expand Down
2 changes: 1 addition & 1 deletion compare/compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func makeErrorString(path, msg string, expected, actual interface{}) string {
return fmt.Sprintf(
"at path %s %s:\n expected: %v\n actual: %v",
"at path '%s' %s:\n expected: %v\n actual: %v",
path,
msg,
expected,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ require (
github.com/tidwall/gjson v1.17.0
golang.org/x/sync v0.4.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -22,4 +21,5 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
7 changes: 4 additions & 3 deletions mocks/constraint_body_json_field_matches_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package mocks

import (
"encoding/json"
"fmt"
"net/http"

"github.com/lansfy/gonkex/colorize"
"github.com/lansfy/gonkex/compare"

"github.com/tidwall/gjson"
)

Expand Down Expand Up @@ -58,10 +59,10 @@ func (c *bodyJSONFieldMatchesJSONConstraint) Verify(r *http.Request) []error {

value := gjson.Get(string(body), c.path)
if !value.Exists() {
return []error{fmt.Errorf("json field %s does not exist", c.path)}
return []error{colorize.NewEntityError("json field %s does not exist", c.path)}
}
if value.String() == "" {
return []error{fmt.Errorf("json field %s is empty", c.path)}
return []error{colorize.NewEntityError("json field %s is empty", c.path)}
}

var actual interface{}
Expand Down
4 changes: 2 additions & 2 deletions mocks/constraint_body_matches_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package mocks

import (
"encoding/json"
"fmt"
"errors"
"net/http"

"github.com/lansfy/gonkex/compare"
Expand Down Expand Up @@ -50,7 +50,7 @@ func (c *bodyMatchesJSONConstraint) Verify(r *http.Request) []error {
}

if len(body) == 0 {
return []error{fmt.Errorf("request is empty")}
return []error{errors.New("request is empty")}
}
var actual interface{}
err = json.Unmarshal(body, &actual)
Expand Down
Loading
Loading