Skip to content
Open
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
82 changes: 82 additions & 0 deletions test/utils/mockutils/jsonComparator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mockutils

import (
"encoding/json"

"github.com/amp-labs/connectors/test/utils/testutils"
)

// JSONComparator offers helpers for structural equality checks between
// arbitrary Go values and JSON documents.
//
// It provides a robust comparison mechanism for verifying JSON
// equivalence in tests, ignoring superficial differences such as
// whitespace, field order, or formatting.
//
// Example:
//
// type response struct {
// ID string `json:"id"`
// Name string `json:"name"`
// }
//
// actual := response{ID: "123", Name: "Alice"}
// expected := mockutils.JSONErrorWrapper(`{"name":"Alice","id":"123"}`)
//
// ok := mockutils.JSONComparator.Equals(actual, expected)
// // ok == true (field order does not matter)
//
// This utility is primarily used by ErrorNormalizedComparator
// to compare JSON-encoded error objects but can be reused
// directly in tests where normalized JSON equality is needed.
var JSONComparator = jsonComparator{}

type jsonComparator struct{}

// Equals reports whether the given Go value and a JSON-encoded
// expected representation are structurally equivalent.
//
// It returns true if both JSON documents represent identical key-value
// structures, ignoring field order and insignificant formatting.
//
// Any marshal or unmarshal failure results in false.
func (jsonComparator) Equals(expected string, actual any) *testutils.CompareResult {
result := testutils.NewCompareResult()

actualBytes, err := json.Marshal(actual)
if err != nil {
result.AddDiff("jsonComparator.Equals: couldn't marshall: %v", actual)
return result
}

actualJSON := make(map[string]any)
if err = json.Unmarshal(actualBytes, &actualJSON); err != nil {
result.AddDiff("jsonComparator.Equals: couldn't unmarshall 'actual' into map[string]any")
return result
}

expectedJSON := make(map[string]any)
if err = json.Unmarshal([]byte(expected), &expectedJSON); err != nil {
result.AddDiff("jsonComparator.Equals: couldn't unmarshall 'expected' into map[string]any")
return result
}

result.Assert("jsonComparator.Equals", actualJSON, expectedJSON)

return result
}

func (jsonComparator) ListsEqual(expected []string, actual []any) (result *testutils.CompareResult) {
result = testutils.NewCompareResult()

if !result.Assert("jsonComparator.ListsEqual lists are of different sizes", len(expected), len(actual)) {
return result
}

for index, e := range expected {
a := actual[index]
result.Merge(JSONComparator.Equals(e, a))
}

return result
}
2 changes: 1 addition & 1 deletion test/utils/mockutils/mockserver/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (c Switch) Server() *httptest.Server {
return
}

// Default fail behaviour.
// Default fail behavior.
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"error": {"message": "condition failed"}}`))
})
Expand Down
26 changes: 2 additions & 24 deletions test/utils/mockutils/normalizedErrors.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package mockutils

import (
"encoding/json"
"errors"
"fmt"
"reflect"
Expand Down Expand Up @@ -51,17 +50,10 @@ func (errorNormalizedComparator) ErrorEquals(actualErr, expectedErr any) *testut

// 3. Handle JSON case if expected is a JSON string.
if expectedJSON, ok := expectedErr.(JSONErrorWrapper); ok {
aJSON, err := json.Marshal(actualErr)
if err != nil {
result.AddDiff("failed to marshal actual error to JSON: %v", err)
return result
if equals := JSONComparator.Equals(string(expectedJSON), actualErr); !equals.OK {
result.Merge(equals)
}

if jsonBodyMatch(aJSON, string(expectedJSON)) {
return result // good
}

result.Assert("JSON error", string(expectedJSON), string(aJSON))
return result
}

Expand Down Expand Up @@ -96,17 +88,3 @@ func (c errorNormalizedComparator) EachErrorEquals(actual, expected []any) *test

return result
}

func jsonBodyMatch(actual []byte, expected string) bool {
first := make(map[string]any)
if err := json.Unmarshal(actual, &first); err != nil {
return false
}

second := make(map[string]any)
if err := json.Unmarshal([]byte(expected), &second); err != nil {
return false
}

return reflect.DeepEqual(first, second)
}
25 changes: 25 additions & 0 deletions test/utils/mockutils/subscriptionResult.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mockutils

import (
"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/test/utils/testutils"
)

var SubscriptionResultComparator = subscriptionResultComparator{}

type subscriptionResultComparator struct{}

func (subscriptionResultComparator) CompareWithoutResultArg(
actual, expected *common.SubscriptionResult,
) *testutils.CompareResult {
result := testutils.NewCompareResult()

result.Assert("ObjectEvents", expected.ObjectEvents, actual.ObjectEvents)
result.Assert("Status", expected.Status, actual.Status)
result.Assert("Objects", expected.Objects, actual.Objects)
result.Assert("Events", expected.Events, actual.Events)
result.Assert("UpdateFields", expected.UpdateFields, actual.UpdateFields)
result.Assert("PassThroughEvents", expected.PassThroughEvents, actual.PassThroughEvents)

return result
}
33 changes: 33 additions & 0 deletions test/utils/testroutines/comparator.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ func ComparatorSubsetRead(serverURL string, actual, expected *common.ReadResult)
return result
}

// ComparatorSubsetReadByIds compares two slices of ReadResultRow as a subset,
// ignoring order and focusing only on relevant fields, raw data, associations, and identifiers.
func ComparatorSubsetReadByIds(serverURL string, actual, expected []common.ReadResultRow) *testutils.CompareResult {
return ComparatorSubsetRead(serverURL,
&common.ReadResult{
Rows: int64(len(actual)),
Data: actual,
},
&common.ReadResult{
Rows: int64(len(expected)),
Data: expected,
},
)
}

// ComparatorPagination will check pagination related fields.
// Note: you may use an alias for Mock-Server-URL which will be dynamically resolved at runtime.
// Example:
Expand Down Expand Up @@ -215,6 +230,24 @@ func ComparatorSubsetUpsertMetadata(_ string, actual, expected *common.UpsertMet
return result
}

func ComparatorSubscriptionWithResult(
resultComparator func(expectedResult, actualResult any) *testutils.CompareResult,
) Comparator[*common.SubscriptionResult] {
return func(_ string, actual, expected *common.SubscriptionResult) *testutils.CompareResult {
result := testutils.NewCompareResult()
result.Merge(mockutils.SubscriptionResultComparator.CompareWithoutResultArg(actual, expected))
result.Merge(resultComparator(expected.Result, actual.Result))

return result
}
}

func ComparatorSubscriptionWithoutResult(
_ string, actual, expected *common.SubscriptionResult,
) *testutils.CompareResult {
return mockutils.SubscriptionResultComparator.CompareWithoutResultArg(actual, expected)
}

func mapIsSubsetMap(subset, superset map[string]any) bool {
for key, expected := range subset {
actual, ok := superset[key]
Expand Down
3 changes: 3 additions & 0 deletions test/utils/testroutines/outline.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type TestCase[Input any, Output any] struct {
ExpectedErrs []error
}

// None can be used to indicate no Input or no Output type.
type None struct{}

func (c TestCase[Input, Output]) Close() {
c.Server.Close()
}
Expand Down
36 changes: 36 additions & 0 deletions test/utils/testroutines/read-by-ids.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package testroutines

import (
"testing"

"github.com/amp-labs/connectors"
"github.com/amp-labs/connectors/common"
)

type (
ReadByIdsType = TestCase[ReadByIdsParams, []common.ReadResultRow]
// ReadByIds is a test suite useful for testing connectors.BatchRecordReaderConnector interface.
ReadByIds ReadByIdsType
)

type ReadByIdsParams struct {
ObjectName string
RecordIds []string
Fields []string
Associations []string
}

// Run provides a procedure to test connectors.BatchRecordReaderConnector
func (r ReadByIds) Run(t *testing.T, builder ConnectorBuilder[connectors.BatchRecordReaderConnector]) {
t.Helper()
t.Cleanup(func() {
ReadByIdsType(r).Close()
})

conn := builder.Build(t, r.Name)
output, err := conn.GetRecordsByIds(t.Context(),
r.Input.ObjectName, r.Input.RecordIds,
r.Input.Fields, r.Input.Associations,
)
ReadByIdsType(r).Validate(t, err, output)
}
33 changes: 33 additions & 0 deletions test/utils/testroutines/subscription-delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package testroutines

import (
"testing"

"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/internal/components"
)

type (
DeleteSubscriptionType = TestCase[common.SubscriptionResult, None]
// DeleteSubscription is a test suite useful for testing part of connectors.SubscribeConnector interface.
DeleteSubscription DeleteSubscriptionType
)

type DeleteSubscriptionParams struct {
Params common.SubscribeParams
PreviousResult *common.SubscriptionResult
}

// Run provides a procedure to test connectors.SubscribeConnector
func (s DeleteSubscription) Run(t *testing.T, builder ConnectorBuilder[components.SubscriptionRemover]) {
t.Helper()
t.Cleanup(func() {
DeleteSubscriptionType(s).Close()
})

s.Expected = None{}

conn := builder.Build(t, s.Name)
err := conn.DeleteSubscription(t.Context(), s.Input)
DeleteSubscriptionType(s).Validate(t, err, None{})
}
Loading
Loading