Some testing QoL functions (#4927)
Functions help check things like
* are two items equal when taking into account nil, empty slices, and
other zero-values?
* is something fully populated?
---
#### Does this PR need a docs update or release note?
- [ ] ✅ Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x] ⛔ No
#### Type of change
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup
#### Test Plan
- [x] 💪 Manual
- [ ] ⚡ Unit test
- [ ] 💚 E2E
This commit is contained in:
parent
1c18131122
commit
f63a6e9b4f
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
||||||
odStub "github.com/alcionai/corso/src/internal/m365/service/onedrive/stub"
|
odStub "github.com/alcionai/corso/src/internal/m365/service/onedrive/stub"
|
||||||
m365Stub "github.com/alcionai/corso/src/internal/m365/stub"
|
m365Stub "github.com/alcionai/corso/src/internal/m365/stub"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
@ -160,6 +161,10 @@ func recipientEqual(
|
|||||||
expected models.Recipientable,
|
expected models.Recipientable,
|
||||||
got models.Recipientable,
|
got models.Recipientable,
|
||||||
) bool {
|
) bool {
|
||||||
|
if expected == nil {
|
||||||
|
return tester.NilOrZero(got)
|
||||||
|
}
|
||||||
|
|
||||||
// Don't compare names as M365 will override the name if the address is known.
|
// Don't compare names as M365 will override the name if the address is known.
|
||||||
return reflect.DeepEqual(
|
return reflect.DeepEqual(
|
||||||
ptr.Val(expected.GetEmailAddress().GetAddress()),
|
ptr.Val(expected.GetEmailAddress().GetAddress()),
|
||||||
@ -173,9 +178,9 @@ func checkMessage(
|
|||||||
) {
|
) {
|
||||||
testElementsMatch(t, expected.GetAttachments(), got.GetAttachments(), false, attachmentEqual)
|
testElementsMatch(t, expected.GetAttachments(), got.GetAttachments(), false, attachmentEqual)
|
||||||
|
|
||||||
assert.Equal(t, expected.GetBccRecipients(), got.GetBccRecipients(), "BccRecipients")
|
tester.AssertEmptyOrEqual(t, expected.GetBccRecipients(), got.GetBccRecipients(), "BccRecipients")
|
||||||
|
|
||||||
assert.Equal(
|
tester.AssertEmptyOrEqual(
|
||||||
t,
|
t,
|
||||||
ptr.Val(expected.GetBody().GetContentType()),
|
ptr.Val(expected.GetBody().GetContentType()),
|
||||||
ptr.Val(got.GetBody().GetContentType()),
|
ptr.Val(got.GetBody().GetContentType()),
|
||||||
@ -187,9 +192,9 @@ func checkMessage(
|
|||||||
// always just the first 255 characters if the message is HTML and has
|
// always just the first 255 characters if the message is HTML and has
|
||||||
// multiple paragraphs.
|
// multiple paragraphs.
|
||||||
|
|
||||||
assert.Equal(t, expected.GetCategories(), got.GetCategories(), "Categories")
|
tester.AssertEmptyOrEqual(t, expected.GetCategories(), got.GetCategories(), "Categories")
|
||||||
|
|
||||||
assert.Equal(t, expected.GetCcRecipients(), got.GetCcRecipients(), "CcRecipients")
|
tester.AssertEmptyOrEqual(t, expected.GetCcRecipients(), got.GetCcRecipients(), "CcRecipients")
|
||||||
|
|
||||||
// Skip ChangeKey as it's tied to this specific instance of the item.
|
// Skip ChangeKey as it's tied to this specific instance of the item.
|
||||||
|
|
||||||
@ -202,33 +207,41 @@ func checkMessage(
|
|||||||
checkFlags(t, expected.GetFlag(), got.GetFlag())
|
checkFlags(t, expected.GetFlag(), got.GetFlag())
|
||||||
|
|
||||||
checkRecipentables(t, expected.GetFrom(), got.GetFrom())
|
checkRecipentables(t, expected.GetFrom(), got.GetFrom())
|
||||||
assert.Equal(t, ptr.Val(expected.GetHasAttachments()), ptr.Val(got.GetHasAttachments()), "HasAttachments")
|
tester.AssertEmptyOrEqual(t, ptr.Val(expected.GetHasAttachments()), ptr.Val(got.GetHasAttachments()), "HasAttachments")
|
||||||
|
|
||||||
// Skip Id as it's tied to this specific instance of the item.
|
// Skip Id as it's tied to this specific instance of the item.
|
||||||
|
|
||||||
assert.Equal(t, ptr.Val(expected.GetImportance()), ptr.Val(got.GetImportance()), "Importance")
|
tester.AssertEmptyOrEqual(t, ptr.Val(expected.GetImportance()), ptr.Val(got.GetImportance()), "Importance")
|
||||||
|
|
||||||
assert.Equal(
|
tester.AssertEmptyOrEqual(
|
||||||
t,
|
t,
|
||||||
ptr.Val(expected.GetInferenceClassification()),
|
ptr.Val(expected.GetInferenceClassification()),
|
||||||
ptr.Val(got.GetInferenceClassification()),
|
ptr.Val(got.GetInferenceClassification()),
|
||||||
"InferenceClassification")
|
"InferenceClassification")
|
||||||
|
|
||||||
assert.Equal(t, expected.GetInternetMessageHeaders(), got.GetInternetMessageHeaders(), "InternetMessageHeaders")
|
tester.AssertEmptyOrEqual(
|
||||||
|
t,
|
||||||
|
expected.GetInternetMessageHeaders(),
|
||||||
|
got.GetInternetMessageHeaders(),
|
||||||
|
"InternetMessageHeaders")
|
||||||
|
|
||||||
assert.Equal(t, ptr.Val(expected.GetInternetMessageId()), ptr.Val(got.GetInternetMessageId()), "InternetMessageId")
|
tester.AssertEmptyOrEqual(
|
||||||
|
t,
|
||||||
|
ptr.Val(expected.GetInternetMessageId()),
|
||||||
|
ptr.Val(got.GetInternetMessageId()),
|
||||||
|
"InternetMessageId")
|
||||||
|
|
||||||
assert.Equal(
|
tester.AssertEmptyOrEqual(
|
||||||
t,
|
t,
|
||||||
ptr.Val(expected.GetIsDeliveryReceiptRequested()),
|
ptr.Val(expected.GetIsDeliveryReceiptRequested()),
|
||||||
ptr.Val(got.GetIsDeliveryReceiptRequested()),
|
ptr.Val(got.GetIsDeliveryReceiptRequested()),
|
||||||
"IsDeliverReceiptRequested")
|
"IsDeliverReceiptRequested")
|
||||||
|
|
||||||
assert.Equal(t, ptr.Val(expected.GetIsDraft()), ptr.Val(got.GetIsDraft()), "IsDraft")
|
tester.AssertEmptyOrEqual(t, ptr.Val(expected.GetIsDraft()), ptr.Val(got.GetIsDraft()), "IsDraft")
|
||||||
|
|
||||||
assert.Equal(t, ptr.Val(expected.GetIsRead()), ptr.Val(got.GetIsRead()), "IsRead")
|
tester.AssertEmptyOrEqual(t, ptr.Val(expected.GetIsRead()), ptr.Val(got.GetIsRead()), "IsRead")
|
||||||
|
|
||||||
assert.Equal(
|
tester.AssertEmptyOrEqual(
|
||||||
t,
|
t,
|
||||||
ptr.Val(expected.GetIsReadReceiptRequested()),
|
ptr.Val(expected.GetIsReadReceiptRequested()),
|
||||||
ptr.Val(got.GetIsReadReceiptRequested()),
|
ptr.Val(got.GetIsReadReceiptRequested()),
|
||||||
@ -238,21 +251,25 @@ func checkMessage(
|
|||||||
|
|
||||||
// Skip ParentFolderId as we restore to a different folder by default.
|
// Skip ParentFolderId as we restore to a different folder by default.
|
||||||
|
|
||||||
assert.Equal(t, ptr.Val(expected.GetReceivedDateTime()), ptr.Val(got.GetReceivedDateTime()), "ReceivedDateTime")
|
tester.AssertEmptyOrEqual(
|
||||||
|
t,
|
||||||
|
ptr.Val(expected.GetReceivedDateTime()),
|
||||||
|
ptr.Val(got.GetReceivedDateTime()),
|
||||||
|
"ReceivedDateTime")
|
||||||
|
|
||||||
assert.Equal(t, expected.GetReplyTo(), got.GetReplyTo(), "ReplyTo")
|
tester.AssertEmptyOrEqual(t, expected.GetReplyTo(), got.GetReplyTo(), "ReplyTo")
|
||||||
|
|
||||||
checkRecipentables(t, expected.GetSender(), got.GetSender())
|
checkRecipentables(t, expected.GetSender(), got.GetSender())
|
||||||
|
|
||||||
assert.Equal(t, ptr.Val(expected.GetSentDateTime()), ptr.Val(got.GetSentDateTime()), "SentDateTime")
|
tester.AssertEmptyOrEqual(t, ptr.Val(expected.GetSentDateTime()), ptr.Val(got.GetSentDateTime()), "SentDateTime")
|
||||||
|
|
||||||
assert.Equal(t, ptr.Val(expected.GetSubject()), ptr.Val(got.GetSubject()), "Subject")
|
tester.AssertEmptyOrEqual(t, ptr.Val(expected.GetSubject()), ptr.Val(got.GetSubject()), "Subject")
|
||||||
|
|
||||||
testElementsMatch(t, expected.GetToRecipients(), got.GetToRecipients(), false, recipientEqual)
|
testElementsMatch(t, expected.GetToRecipients(), got.GetToRecipients(), false, recipientEqual)
|
||||||
|
|
||||||
// Skip WebLink as it's tied to this specific instance of the item.
|
// Skip WebLink as it's tied to this specific instance of the item.
|
||||||
|
|
||||||
assert.Equal(t, expected.GetUniqueBody(), got.GetUniqueBody(), "UniqueBody")
|
tester.AssertEmptyOrEqual(t, expected.GetUniqueBody(), got.GetUniqueBody(), "UniqueBody")
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkFlags is a helper function to check equality of models.FollowupFlabables
|
// checkFlags is a helper function to check equality of models.FollowupFlabables
|
||||||
@ -261,6 +278,11 @@ func checkFlags(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
expected, got models.FollowupFlagable,
|
expected, got models.FollowupFlagable,
|
||||||
) {
|
) {
|
||||||
|
if expected == nil {
|
||||||
|
assert.True(t, tester.NilOrZero(got))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, expected.GetCompletedDateTime(), got.GetCompletedDateTime())
|
assert.Equal(t, expected.GetCompletedDateTime(), got.GetCompletedDateTime())
|
||||||
assert.Equal(t, expected.GetDueDateTime(), got.GetDueDateTime())
|
assert.Equal(t, expected.GetDueDateTime(), got.GetDueDateTime())
|
||||||
assert.Equal(t, expected.GetFlagStatus(), got.GetFlagStatus())
|
assert.Equal(t, expected.GetFlagStatus(), got.GetFlagStatus())
|
||||||
@ -274,6 +296,11 @@ func checkRecipentables(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
expected, got models.Recipientable,
|
expected, got models.Recipientable,
|
||||||
) {
|
) {
|
||||||
|
if expected == nil {
|
||||||
|
assert.True(t, tester.NilOrZero(got))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
checkEmailAddressables(t, expected.GetEmailAddress(), got.GetEmailAddress())
|
checkEmailAddressables(t, expected.GetEmailAddress(), got.GetEmailAddress())
|
||||||
assert.Equal(t, expected.GetAdditionalData(), got.GetAdditionalData())
|
assert.Equal(t, expected.GetAdditionalData(), got.GetAdditionalData())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,7 @@ package operations
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -369,33 +367,11 @@ func TestBackupOpUnitSuite(t *testing.T) {
|
|||||||
suite.Run(t, &BackupOpUnitSuite{Suite: tester.NewUnitSuite(t)})
|
suite.Run(t, &BackupOpUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPopulatedInner(v reflect.Value) error {
|
|
||||||
if v.IsZero() {
|
|
||||||
return clues.New("zero-valued field")
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Kind() != reflect.Struct {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs *clues.Err
|
|
||||||
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
f := v.Field(i)
|
|
||||||
|
|
||||||
if err := checkPopulatedInner(f); err != nil {
|
|
||||||
errs = clues.Stack(errs, clues.Wrap(err, fmt.Sprintf("field at index %d", i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs.OrNil()
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPopulated ensures that input has no zero-valued fields. That helps
|
// checkPopulated ensures that input has no zero-valued fields. That helps
|
||||||
// ensure that even as future updates to input happen in other files the changes
|
// ensure that even as future updates to input happen in other files the changes
|
||||||
// are propagated here due to test failures.
|
// are propagated here due to test failures.
|
||||||
func checkPopulated(t *testing.T, input control.Options) {
|
func checkPopulated(t *testing.T, input control.Options) {
|
||||||
err := checkPopulatedInner(reflect.ValueOf(input))
|
err := tester.CheckPopulated(input)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
121
src/internal/tester/check.go
Normal file
121
src/internal/tester/check.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package tester
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkPopulated(v reflect.Value) error {
|
||||||
|
if v.IsZero() {
|
||||||
|
return clues.New("zero-valued field")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs *clues.Err
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
f := v.Field(i)
|
||||||
|
|
||||||
|
if err := checkPopulated(f); err != nil {
|
||||||
|
errs = clues.Stack(errs, clues.Wrap(err, fmt.Sprintf("field at index %d", i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs.OrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyContainer(v reflect.Value) bool {
|
||||||
|
// Handle pointers to things.
|
||||||
|
deref := v
|
||||||
|
|
||||||
|
for k := deref.Kind(); k == reflect.Pointer; k = deref.Kind() {
|
||||||
|
deref = deref.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for empty maps, slices, or arrays.
|
||||||
|
if (deref.Kind() == reflect.Slice && deref.Len() == 0) ||
|
||||||
|
(deref.Kind() == reflect.Map && deref.Len() == 0) ||
|
||||||
|
(deref.Kind() == reflect.Array && deref.Len() == 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPopulated returns an error if input is not fully populated. To be
|
||||||
|
// considered fully populated it must be non-zero-valued. For basic types this
|
||||||
|
// just means it isn't the zero value. For structs this means that every field
|
||||||
|
// is not zero-valued. This check is recursive for structs.
|
||||||
|
func CheckPopulated(input any) error {
|
||||||
|
return checkPopulated(reflect.ValueOf(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNotPopulated(v reflect.Value) error {
|
||||||
|
if isEmptyContainer(v) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.IsZero() {
|
||||||
|
return clues.New("non-zero-valued field")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs *clues.Err
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
f := v.Field(i)
|
||||||
|
|
||||||
|
if err := checkNotPopulated(f); err != nil {
|
||||||
|
errs = clues.Stack(errs, clues.Wrap(err, fmt.Sprintf("field at index %d", i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs.OrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NilOrZero return true if the input is nil or if it's the zero value for the
|
||||||
|
// type. If the input is a struct then all fields are recursively checked to
|
||||||
|
// see if they're the zero value for their type.
|
||||||
|
func NilOrZero(input any) bool {
|
||||||
|
if input == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isEmptyContainer(reflect.ValueOf(input)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := checkNotPopulated(reflect.ValueOf(input))
|
||||||
|
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertEmptyOrEqual checks either:
|
||||||
|
// - got is nil or the zero value if expected is nil
|
||||||
|
// - expected and got are equal if expected is not nil
|
||||||
|
func AssertEmptyOrEqual(
|
||||||
|
t *testing.T,
|
||||||
|
expect any,
|
||||||
|
got any,
|
||||||
|
msgAndArgs ...any,
|
||||||
|
) bool {
|
||||||
|
if expect == nil {
|
||||||
|
return assert.True(t, NilOrZero(got), "empty got value: %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isEmptyContainer(reflect.ValueOf(expect)) {
|
||||||
|
return assert.True(t, NilOrZero(got), "empty got value: %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
return assert.Equal(t, expect, got, msgAndArgs...)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user