SimonBaeumer/commander

View on GitHub
pkg/matcher/matcher.go

Summary

Maintainability
A
0 mins
Test Coverage
A
95%
package matcher

import (
    "fmt"
    "log"
    "os"
    "strings"

    "github.com/antchfx/xmlquery"
    "github.com/pmezard/go-difflib/difflib"
    "github.com/tidwall/gjson"
)

const (
    // Text matcher type
    Text = "text"
    // Contains matcher type
    Contains = "contains"
    // Equal matcher type
    Equal = "equal"
    // Not contains matcher type
    NotContains = "notcontains"
    JSON        = "json"
    XML         = "xml"
    File        = "file"
)

var (
    _ Matcher = (*TextMatcher)(nil)
    _ Matcher = (*ContainsMatcher)(nil)
    _ Matcher = (*EqualMatcher)(nil)
    _ Matcher = (*NotContainsMatcher)(nil)
    _ Matcher = (*JSONMatcher)(nil)
    _ Matcher = (*XMLMatcher)(nil)
    _ Matcher = (*FileMatcher)(nil)
)

// The function used to open files when necessary for matching
// Allows the file IO to be overridden during tests
var ReadFile = os.ReadFile

// NewMatcher creates a new matcher by type
func NewMatcher(matcher string) Matcher {
    switch matcher {
    case Text:
        return TextMatcher{}
    case Contains:
        return ContainsMatcher{}
    case Equal:
        return EqualMatcher{}
    case NotContains:
        return NotContainsMatcher{}
    case JSON:
        return JSONMatcher{}
    case XML:
        return XMLMatcher{}
    case File:
        return FileMatcher{}
    default:
        panic(fmt.Sprintf("Validator '%s' does not exist!", matcher))
    }
}

// Matcher interface which is implemented by all matchers
type Matcher interface {
    Match(actual interface{}, expected interface{}) MatcherResult
}

// MatcherResult will be returned by the executed matcher
type MatcherResult struct {
    Success bool
    Diff    string
}

// TextMatcher matches if two texts are equals
type TextMatcher struct{}

// Match matches both texts
func (m TextMatcher) Match(got interface{}, expected interface{}) MatcherResult {
    result := got == expected

    diff := difflib.UnifiedDiff{
        A:        difflib.SplitLines(got.(string)),
        B:        difflib.SplitLines(expected.(string)),
        FromFile: "Got",
        ToFile:   "Expected",
        Context:  3,
    }
    diffText, _ := difflib.GetUnifiedDiffString(diff)

    return MatcherResult{
        Diff:    diffText,
        Success: result,
    }
}

// ContainsMatcher tests if the expected value is in the got variable
type ContainsMatcher struct{}

// Match matches on the given values
func (m ContainsMatcher) Match(got interface{}, expected interface{}) MatcherResult {
    result := strings.Contains(got.(string), expected.(string))

    diff := `
Expected

%s

to contain

%s
`
    diff = fmt.Sprintf(diff, got, expected)

    return MatcherResult{
        Success: result,
        Diff:    diff,
    }
}

// EqualMatcher matches if given values are equal
type EqualMatcher struct{}

// Match matches the values if they are equal
func (m EqualMatcher) Match(got interface{}, expected interface{}) MatcherResult {
    if got == expected {
        return MatcherResult{
            Success: true,
        }
    }

    diff := difflib.UnifiedDiff{
        A:        difflib.SplitLines(fmt.Sprintf("%v", got)),
        B:        difflib.SplitLines(fmt.Sprintf("%v", expected)),
        FromFile: "Got",
        ToFile:   "Expected",
        Context:  3,
    }
    diffText, _ := difflib.GetUnifiedDiffString(diff)

    return MatcherResult{
        Success: false,
        Diff:    diffText,
    }
}

type NotContainsMatcher struct{}

func (m NotContainsMatcher) Match(got interface{}, expected interface{}) MatcherResult {
    result := !strings.Contains(got.(string), expected.(string))

    diff := `
Expected

%s

to not contain

%s
`
    diff = fmt.Sprintf(diff, got, expected)

    return MatcherResult{
        Success: result,
        Diff:    diff,
    }
}

type JSONMatcher struct{}

func (m JSONMatcher) Match(got interface{}, expected interface{}) MatcherResult {
    result := MatcherResult{
        Success: true,
        Diff:    "",
    }

    json := expected.(map[string]string)
    for q, e := range json {
        r := gjson.Get(got.(string), q)
        if r.Value() == nil {
            result.Success = false
            result.Diff = fmt.Sprintf(`Query "%s" did not match a path`, q)
            break
        }

        if fmt.Sprintf("%v", r.Value()) != e {
            result.Success = false
            result.Diff = fmt.Sprintf(`Expected json path "%s" with result

%s

to be equal to

%s`, q, e, r.Value())
            break
        }
    }

    return result
}

type XMLMatcher struct{}

func (m XMLMatcher) Match(got interface{}, expected interface{}) MatcherResult {
    result := MatcherResult{
        Success: true,
        Diff:    "",
    }

    xml := expected.(map[string]string)
    for q, e := range xml {
        doc, err := xmlquery.Parse(strings.NewReader(got.(string)))
        if err != nil {
            log.Fatal(err)
        }

        node, err := xmlquery.Query(doc, q)
        if err != nil {
            return MatcherResult{
                Success: false,
                Diff:    fmt.Sprintf("Error occurred: %s", err),
            }
        }

        if node == nil {
            return MatcherResult{
                Success: false,
                Diff:    fmt.Sprintf(`Query "%s" did not match a path`, q),
            }
        }

        if node.InnerText() != e {
            result.Success = false
            result.Diff = fmt.Sprintf(`Expected xml path "%s" with result

%s

to be equal to

%s`, q, e, node.InnerText())
        }
    }

    return result
}

// FileMatcher matches output captured from stdout or stderr
// against the contents of a file
type FileMatcher struct{}

func (m FileMatcher) Match(got interface{}, expected interface{}) MatcherResult {
    expectedText, err := ReadFile(expected.(string))
    if err != nil {
        panic(err.Error())
    }
    expectedString := strings.TrimSpace(strings.ReplaceAll(string(expectedText), "\r\n", "\n"))

    result := got == expectedString

    diff := difflib.UnifiedDiff{
        A:        difflib.SplitLines(got.(string)),
        B:        difflib.SplitLines(expectedString),
        FromFile: "Got",
        ToFile:   "Expected",
        Context:  3,
    }
    diffText, _ := difflib.GetUnifiedDiffString(diff)

    return MatcherResult{
        Diff:    diffText,
        Success: result,
    }
}