kindrid/gotest

View on GitHub
should/jsonapi.go

Summary

Maintainability
A
1 hr
Test Coverage
package should

import (
    "reflect"

    "github.com/Jeffail/gabs"
)

// This file relies on parseJSON() in json.go

// BeJSONAPIResourceIdentifier passes if actual seems to be a JSONAPI resource
// typeentifier. Unlike a full JSONAPI resource object, it has no attributes but
// MUST refer to a resource in the included list.
func BeJSONAPIResourceIdentifier(actual interface{}, expected ...interface{}) (fail string) {
    usage := "BeJSONAPIResourceIdentifier expects a single string argument and passes if that argument parses as a JSONAPI resource identifier."
    if actual == nil {
        return usage
    }
    json, err := parseJSON(actual)
    if err != nil {
        return err.Error()
    }
    return beJSONAPIResourceIdentifier(json)
}

func beJSONAPIResourceIdentifier(json *gabs.Container) (fail string) {
    var fields = []interface{}{"id", reflect.String, "type", reflect.String}
    fail = HaveFields(json, fields...)
    if fail == "" {
        fail += HaveOnlyFields(json, fields...)
    }
    return
}

// BeJSONAPIRecord passes if actual seems to be a complete JSONAPI-format
// response where response.data is a single JSON object.
func BeJSONAPIRecord(actual interface{}, expected ...interface{}) (fail string) {
    usage := "BeJSONAPIRecord expects a single string argument and passes if that argument parses as a JSONAPI single-object record."
    if actual == nil {
        return usage
    }
    json, err := parseJSON(actual)
    if err != nil {
        return err.Error()
    }

    // var links, included string
    // These aren't checked yet, so this throws an ineffectual assignment warning
    // if element := json.Search("links"); element != nil {
    //     links = BeValidLinks(element)
    // }
    // if element := json.Search("included"); element != nil {
    //     included = BeValidIncluded(element)
    // }

    fail = FailFirst(
        NotJSONAPIError(json),
        HaveFields(json, "meta", reflect.Map, "data", reflect.Map),
        HaveOnlyFields(json, "meta", reflect.Map, "data", reflect.Map, "links", reflect.Map, "included", reflect.Slice),
        BeValidRecord(json.Search("data")),
        HaveOnlyCamelcaseKeys(json),
        // BeValidMeta(json.Search("meta")),
        // links,
        // included,
    )
    return
}

// NotJSONAPIError returns a non-blank string if the JSON appears to be a
// JSONAPI error response
func NotJSONAPIError(actual interface{}, expected ...interface{}) (fail string) {
    usage := "BeJSONAPIArray expects a single string argument and passes if that argument parses as a JSONAPI multi-object array."
    if actual == nil {
        return usage
    }
    json, err := ParseJSON(actual)
    if err != nil {
        return err.Error()
    }
    if json.PathExists("error") {
        return FormatFailure(
            "You got what looks like a malformed jsonapi error. (It should only have .errors, an array, instead of .error, a singleton.)",
            json.GetPath("error").String(),
            "",
            "",
        )
    }
    if json.PathExists("errors") {
        return FormatFailure(
            "You expected a jsonapi return, but you got what looks like a jsonapi error.",
            json.GetPath("errors").String(),
            "",
            "",
        )
    }
    return
}

// BeJSONAPIArray passes if actual seems to be a complete JSONAPI-format
// response where response.data is a multi-object array.
func BeJSONAPIArray(actual interface{}, expected ...interface{}) (fail string) {
    usage := "BeJSONAPIArray expects a single string argument and passes if that argument parses as a JSONAPI multi-object array."
    if actual == nil {
        return usage
    }
    json, err := parseJSON(actual)
    if err != nil {
        return err.Error()
    }

    FailFirst(
        NotJSONAPIError(json),
        HaveFields(json, "meta", reflect.Map, "data", reflect.Slice),
        HaveOnlyFields(json, "meta", reflect.Map, "data", reflect.Slice, "links", reflect.Map, "included", reflect.Slice),
        BeValidRecordArray(json.Search("data")),
        // check links
        // check includes
    )

    return
}

// BeJSONAPI passes if actual seems to be a complete JSONAPI-format response
// where response.data is a multi-object array or a single JSON object.
func BeJSONAPI(actual interface{}, expected ...interface{}) (fail string) {
    usage := "BeJSONAPIArray expects a single string argument and passes if that argument parses as a JSONAPI return value."
    if actual == nil {
        return usage
    }
    json, err := parseJSON(actual)
    if err != nil {
        return err.Error()
    }
    if HaveFields(json, "data", reflect.Slice) == "" {
        return BeJSONAPIArray(actual, expected)
    }
    return BeJSONAPIRecord(actual, expected)
}

// func BeValidMeta(json *gabs.Container) (fail string) {
//     // Kindrid Specific:
//     // fail += HaveFields(json, "apiVersion", reflect.String, "formatVersion", reflect.String)
//     return
// }

// BeValidRecord returns a non-blank string if json doesn't comply with
// JSONAPI rules for a resource record
func BeValidRecord(json *gabs.Container) (fail string) {
    fail = HaveFields(json, "id", reflect.String, "type", reflect.String)
    return
}

// BeValidRecordArray returns a non-blank string if json doesn't comply with
// JSONAPI rules for an array of resource records
func BeValidRecordArray(json *gabs.Container) (fail string) {
    children, err := json.Children()
    if err != nil {
        return err.Error()
    }
    for _, record := range children {
        if fail = BeValidRecord(record); fail != "" {
            return
        }
    }
    return
}

//
// func BeValidLinks(json *gabs.Container) (fail string) {
//     return
// }
//
// func BeValidIncluded(json *gabs.Container) (fail string) {
//     return
// }

func BeJsonapiError(actual interface{}, expected ...interface{}) (fail string) {
    usage := "BeJsonapiError expects a single string argument and passes if that argument parses as a JSON:API error response."
    if actual == nil {
        return usage
    }
    json, err := ParseJSON(actual)
    if err != nil {
        return err.Error()
    }
    fail = FailFirst(
        HaveFields(json, "errors", reflect.Slice),
        AllowFields(json, "errors", reflect.Slice, "meta", reflect.Map,
            "jsonapi", reflect.Map, "links", reflect.Map, "included", reflect.Slice))
    if fail != "" {
        return
    }
    errors := json.GetPath("errors")
    for i := 0; i < errors.Len(); i++ {
        e := errors.GetElement(i)
        fail = testJsonapiErrorObject(e)
        if fail != "" {
            return
        }
    }
    return
}

func testJsonapiErrorObject(e StructureExplorer) (fail string) {
    if fail = AllowFields(e, "id", reflect.String, "links", reflect.Map,
        "status", reflect.String, "code", reflect.String,
        "title", reflect.String, "detail", reflect.String,
        "source", reflect.Map, "meta", reflect.Map,
    ); fail != "" {
        return
    }
    // .links is supposed to have .about
    if links, ok := e.GetPathCheck("links"); ok {
        if fail = HaveFields(links, "about", reflect.String); fail != "" {
            return
        }
    }
    // .source can only have .path and .parameter
    if source, ok := e.GetPathCheck("source"); ok {
        if fail = AllowFields(source, "paramater", reflect.String,
            "path", reflect.String,
        ); fail != "" {
            return
        }
    }
    return
}