Skip to content

Commit

Permalink
Add verify patch for fx tests and more add op. tests
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Rutkowski <[email protected]>
  • Loading branch information
mrutkows committed Jan 18, 2024
1 parent 51b747f commit 091e4f0
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 84 deletions.
8 changes: 0 additions & 8 deletions cmd/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,6 @@ const (
MSG_LICENSES_NOT_FOUND = "licenses not found"
)

// Query error messages
const (
MSG_QUERY_CLAUSE_NOT_FOUND = "required clause not found"
MSG_QUERY_INVALID_REQUEST = "invalid query request"
MSG_QUERY_INVALID_RESPONSE = "invalid query response"
MSG_QUERY_INVALID_DATATYPE = "invalid data type"
)

// Query error details
const (
MSG_QUERY_ERROR_FROM_KEY_NOT_FOUND = "key not found in path"
Expand Down
185 changes: 136 additions & 49 deletions cmd/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"io"
"log"
"os"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -72,8 +73,13 @@ const (
TEST_PATCH_RFC_6902_APPX_A_9_PATCH_1 = "test/patch/rfc6902/rfc6902-appendix-a-9-patch-1.json"

// CycloneDX BOM "patch" files
TEST_PATCH_BOM_ADD_SLICE_1 = "test/patch/cdx-patch-add-slice-1.json"
TEST_PATCH_METADATA_PROPERTIES_1 = "test/patch/cdx-patch-metadata-properties-1.json"
TEST_PATCH_BOM_ADD_SLICE_1 = "test/patch/cdx-patch-add-slice-1.json"
//TEST_PATCH_METADATA_PROPERTIES_1 = "test/patch/cdx-patch-metadata-properties-1.json"
TEST_PATCH_BOM_ADD_METADATA_PROPS_MIXED = "test/patch/cdx-patch-add-metadata-properties-mixed.json"
TEST_PATCH_BOM_ADD_METADATA_PROP_AT_END = "test/patch/cdx-patch-add-metadata-property-at-end.json"
TEST_PATCH_BOM_ADD_METADATA_PROP_AT_INDEX = "test/patch/cdx-patch-add-metadata-property-at-index.json"
TEST_PATCH_BOM_ADD_ROOT_UPDATE_VERSION = "test/patch/cdx-patch-add-root-update-version.json"
TEST_PATCH_BOM_ADD_ROOT_INVALID_KEY_MODIFIED = "test/patch/cdx-patch-add-root-invalid-key-modified.json"

// CycloneDX BOM "patch" files (error tests)
TEST_PATCH_ERR_ADD_MISSING_VALUE = "test/patch/cdx-patch-add-err-missing-value.json"
Expand Down Expand Up @@ -199,7 +205,6 @@ func retrieveQueryPathFromPatchRecord(recordPath string) (queryPath string, key
if err != nil {
return
}
fmt.Printf("path: %s", paths)

lenPaths := len(paths)
// if record's path is not root, separate it from the last path segment
Expand Down Expand Up @@ -239,28 +244,14 @@ func VerifyPatchedOutputFileResult(t *testing.T, originalTest PatchTestInfo) (er
t.Errorf("%s: %v", ERR_TYPE_UNEXPECTED_ERROR, err)
return
}
getLogger().Tracef("request: %s", request.String())
getLogger().Tracef("request: %s\n", request.String())

// Verify each key was removed
var pResult interface{}
for _, record := range patchDocument.Records {

var queryPath, key string
// var paths []string
// paths, err = parseMapKeysFromPath(record.Path)
// if err != nil {
// return
// }
// fmt.Printf("path: %s", paths)
// lenPaths := len(paths)
// if lenPaths > 1 {
// queryPath = strings.Join(paths[0:lenPaths-1], ".")

// } else {
// queryPath = paths[0]
// }
queryPath, key, err = retrieveQueryPathFromPatchRecord(record.Path)
fmt.Printf("key: %s\n", key)
//fmt.Printf("queryPath: %s, key: %s\n", queryPath, key)
if err != nil {
t.Errorf("%s: %v", "unable to parse patch record path.", err)
return
Expand All @@ -269,7 +260,7 @@ func VerifyPatchedOutputFileResult(t *testing.T, originalTest PatchTestInfo) (er

// use a buffered query on the temp. output file on the (parent) path
pResult, _, err = innerQuery(t, queryTestInfo, request)
if err != nil {
if err != nil && !ErrorTypesMatch(err, &common.QueryResultInvalidTypeError{}) {
t.Errorf("%s: %v", ERR_TYPE_UNEXPECTED_ERROR, err)
return
}
Expand All @@ -280,44 +271,110 @@ func VerifyPatchedOutputFileResult(t *testing.T, originalTest PatchTestInfo) (er
return
}

// // verify the "key" was removed from the (parent) JSON map
// err = VerifyPatched(pResult, key)
// verify the "key" was removed from the (parent) JSON map
err = VerifyPatched(record, pResult, key)
if err != nil {
return
}
}

return
}

func VerifyPatched(pResult interface{}, key string, value interface{}) (err error) {
func VerifyPatched(record IETF6902Record, pResult interface{}, key string) (err error) {
// verify the "key" was removed from the (parent) JSON map
if pResult != nil {
// switch typedValue := pResult.(type) {
// case map[string]interface{}:
// // verify map key was removed
// if _, ok := typedValue[key]; ok {
// formattedValue, _ := utils.MarshalAnyToFormattedJsonString(typedValue)
// err = getLogger().Errorf("trim failed. Key `%s`, found in: `%s`", key, formattedValue)
// return
// }
// case []interface{}:
// if len(typedValue) == 0 {
// err = getLogger().Errorf("empty slice found at from clause.")
// return
// }
// // Verify all elements of slice
// for _, value := range typedValue {
// err = VerifyTrimmed(value, key)
// return err
// }
// default:
// err = getLogger().Errorf("trim failed. Unexpected JSON type: `%T`", typedValue)
// return
// }
switch typedResult := pResult.(type) {
case map[string]interface{}:
// buf, _ := utils.EncodeAnyToDefaultIndentedJSONStr(typedResult)
// fmt.Printf("result (map):\n%s", buf.String())
// NOTE: this is for "Add" operation only
if record.Operation == IETF_RFC6902_OP_ADD {
if _, ok := typedResult[key]; !ok {
formattedResult, _ := utils.EncodeAnyToDefaultIndentedJSONStr(typedResult)
err = getLogger().Errorf("patch failed. Key `%s`, found in: `%s`", key, formattedResult.String())
return
}
}
case []interface{}:
buf, _ := utils.EncodeAnyToDefaultIndentedJSONStr(typedResult)
fmt.Printf("result (slice):\n%s", buf.String())
// NOTE: this is for "Add" operation only
if record.Operation == IETF_RFC6902_OP_ADD {
if len(typedResult) == 0 {
err = getLogger().Errorf("empty slice found at from clause.")
return
}

if record.Value == nil {
err = getLogger().Errorf("value nil.")
return
}
var contains bool
contains, err = sliceContainsValue(typedResult, record.Value)
if !contains {
err = getLogger().Errorf("empty slice found at from clause.")
return
}
}
case string: // TODO
return
case bool: // TODO
return
case float64: // TODO
return
default:
err = getLogger().Errorf("verify failed. Unexpected JSON type: `%T`", typedResult)
return
}
} else {
// TODO: return typed error
getLogger().Trace("nil results")
}
return
}

func sliceContainsValue(slice []interface{}, value interface{}) (contains bool, err error) {
switch typedValue := value.(type) {
case map[string]interface{}:
var tempValue map[string]interface{}
var ok bool
for _, entry := range slice {
if tempValue, ok = entry.(map[string]interface{}); !ok {
err = fmt.Errorf("type mismatch error. Slice values: %v (%T), value: %v (%T)", entry, entry, tempValue, tempValue)
return
}
if reflect.DeepEqual(tempValue, typedValue) {
contains = true
return
}
}
return
case []interface{}:
var tempValue []interface{}
for _, entry := range slice {
if tempValue, ok := entry.([]interface{}); !ok {
err = fmt.Errorf("type mismatch error. Slice values: %v (%T), value: %v (%T)", entry, entry, tempValue, tempValue)
return
}
if reflect.DeepEqual(tempValue, typedValue) {
contains = true
return
}
}
case string: // TODO
// NOTE: "slices.Contains()" does not work on []interface{}
return
case bool: // TODO
return
case float64: // TODO
return
default:
getLogger().Errorf("contains test failed. Unexpected JSON type: `%T`", typedValue)
}
return
}

// ----------------
// Error tests
// ----------------
Expand Down Expand Up @@ -515,8 +572,8 @@ func TestPatchRFC6902AppendixA10Patch1(t *testing.T) {
// CycloneDX Tests
// ----------------

func TestPatchCdx15(t *testing.T) {
ti := NewPatchTestInfo(TEST_PATCH_BOM_1_5_SIMPLE_BASE, TEST_PATCH_METADATA_PROPERTIES_1, nil)
func TestPatchCdx15InvalidAddRootModified(t *testing.T) {
ti := NewPatchTestInfo(TEST_PATCH_BOM_1_5_SIMPLE_BASE, TEST_PATCH_BOM_ADD_METADATA_PROPS_MIXED, nil)
ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_PATCH_BOM_1_5_SIMPLE_BASE)
buffer, _, err := innerTestPatch(t, ti)
if err != nil {
Expand All @@ -525,11 +582,41 @@ func TestPatchCdx15(t *testing.T) {
// NOTE: patch record: { "op": "add", "path": "/modified", "value": true }
// will NOT be written to the output BOM as "modified" is not a CycloneDX key
getLogger().Tracef("%s\n", buffer.String())
// TODO: verify results
err = VerifyPatchedOutputFileResult(t, *ti)
}

func TestPatchCdx15AddPropertiesAtEnd(t *testing.T) {
ti := NewPatchTestInfo(TEST_PATCH_BOM_1_5_SIMPLE_BASE, TEST_PATCH_BOM_ADD_METADATA_PROP_AT_END, nil)
ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_PATCH_BOM_1_5_SIMPLE_BASE)
buffer, _, err := innerTestPatch(t, ti)
if err != nil {
t.Error(err)
}
getLogger().Tracef("%s\n", buffer.String())
// verify JSON document has applied all patch records
// err = VerifyPatchedOutputFileResult(t, *ti)
// if err != nil {
// t.Error(err)
// }
}

func TestPatchCdx15AddPropertiesAtIndex(t *testing.T) {
ti := NewPatchTestInfo(TEST_PATCH_BOM_1_5_SIMPLE_BASE, TEST_PATCH_BOM_ADD_METADATA_PROP_AT_INDEX, nil)
ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_PATCH_BOM_1_5_SIMPLE_BASE)
buffer, _, err := innerTestPatch(t, ti)
if err != nil {
t.Error(err)
}
getLogger().Tracef("%s\n", buffer.String())
}

func TestPatchCdx15AddPropertiesMixed(t *testing.T) {
ti := NewPatchTestInfo(TEST_PATCH_BOM_1_5_SIMPLE_BASE, TEST_PATCH_BOM_ADD_METADATA_PROPS_MIXED, nil)
ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_PATCH_BOM_1_5_SIMPLE_BASE)
buffer, _, err := innerTestPatch(t, ti)
if err != nil {
t.Error(err)
}
getLogger().Tracef("%s\n", buffer.String())
}

func TestPatchCdx15SliceAdd(t *testing.T) {
Expand Down
28 changes: 15 additions & 13 deletions cmd/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ func Query(writer io.Writer, request *common.QueryRequest, response *common.Quer

// Validate we have query request/response structs
if request == nil {
err = fmt.Errorf(MSG_QUERY_INVALID_REQUEST)
err = fmt.Errorf(common.MSG_QUERY_INVALID_REQUEST)
return
}

if response == nil {
err = fmt.Errorf(MSG_QUERY_INVALID_RESPONSE)
err = fmt.Errorf(common.MSG_QUERY_INVALID_RESPONSE)
return
}

Expand Down Expand Up @@ -234,10 +234,10 @@ func QueryJSONMap(jsonMap map[string]interface{}, request *common.QueryRequest)

// SELECT specific fields from the FROM object(s)
// logic varies depending on data type of FROM object (i.e., map or slice)
switch t := resultJson.(type) {
switch typedResult := resultJson.(type) {
case map[string]interface{}:
// TODO: return this (map) output instead of the one from the "find" stage
resultJson, err = selectFieldsFromMap(request, resultJson.(map[string]interface{}))
// use this (map) output instead of the one from the "find" stage
resultJson, err = selectFieldsFromMap(request, typedResult)
if err != nil {
getLogger().Debugf("selectFieldsFromMap() failed. QueryRequest: %s", request.String())
return
Expand All @@ -258,9 +258,10 @@ func QueryJSONMap(jsonMap map[string]interface{}, request *common.QueryRequest)
return
}
default:
// NOTE: this SHOULD never be invoked as the FROM logic should have caught this already
err = common.NewQueryFromClauseError(request,
fmt.Sprintf("%s: %T", MSG_QUERY_INVALID_DATATYPE, t))
// NOTE: this SHOULD never be reached from the "query" command
// as the FROM should always reference a map or []interface{}
// which is required for JSON output results.
err = common.NewQueryResultInvalidTypeError(request, typedResult)
return
}

Expand Down Expand Up @@ -293,11 +294,11 @@ func findFromObject(request *common.QueryRequest, jsonMap map[string]interface{}
return
}

switch t := pResults.(type) {
switch typedResult := pResults.(type) {
case map[string]interface{}:
// If the resulting value is indeed another map type, we expect for a Json Map
// we preserve that pointer for the next iteration
tempMap = pResults.(map[string]interface{})
tempMap = typedResult //pResults.(map[string]interface{})
case []interface{}:
// TODO: We only support a slice (i.e., []interface{}) as the last selector
// in theory, we could support arrays (perhaps array notation) in the FROM clause
Expand All @@ -312,9 +313,10 @@ func findFromObject(request *common.QueryRequest, jsonMap map[string]interface{}
return
}
default:
getLogger().Debugf("Invalid datatype of query: key: %s (%t)", key, t)
err = common.NewQueryFromClauseError(request,
fmt.Sprintf("%s: %T", MSG_QUERY_INVALID_DATATYPE, t))
// NOTE: this SHOULD never be reached from the "query" command
// as the FROM should always reference a map or []interface{}
// which is required for JSON output results.
err = common.NewQueryResultInvalidTypeError(request, typedResult)
return
}
}
Expand Down
10 changes: 2 additions & 8 deletions cmd/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,8 @@ func TestQueryFailCdx14MetadataComponentInvalidDataType(t *testing.T) {
request, _ := common.NewQueryRequestSelectFrom(
common.QUERY_TOKEN_WILDCARD,
"metadata.component.name")
expectedErrorStrings := []string{
common.MSG_QUERY_INVALID_FROM_CLAUSE,
MSG_QUERY_INVALID_DATATYPE,
}
// Expect a QueryError
_, err := innerQueryError(t, cti, request, &common.QueryError{})
// Assure we received an error with the expected key phrases
EvaluateErrorAndKeyPhrases(t, err, expectedErrorStrings)
// Expect a QueryResultInvalidTypeError
innerQueryError(t, cti, request, &common.QueryResultInvalidTypeError{})
}

func TestQueryFailCdx14MetadataComponentInvalidSelectClause(t *testing.T) {
Expand Down
Loading

0 comments on commit 091e4f0

Please sign in to comment.