Skip to content

Commit

Permalink
Add mdbson.Pointer mechanism and various cleanup and tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
mAdkins committed Jun 6, 2023
1 parent 4f5b799 commit 397e0c7
Show file tree
Hide file tree
Showing 12 changed files with 470 additions and 36 deletions.
21 changes: 16 additions & 5 deletions mdb/access_test_suite_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package mdb

// Would prefer to name this file ending in _test.go
// so that it won't be included in generated code,
// but then it can't be referenced from other packages for some reason,
// so it couldn't be used (as designed) in tests in other packages.

import (
"github.com/stretchr/testify/suite"
)
Expand Down Expand Up @@ -37,6 +32,8 @@ func (suite *AccessTestSuite) TearDownSuite() {
suite.NoError(suite.access.Disconnect(), "disconnect from mongo")
}

// ConnectCollection connects to the specified collection and adds any provided indexes
// as necessary in a SetupSuite() with test checks so that any errors blow up the test.
func (suite *AccessTestSuite) ConnectCollection(
definition *CollectionDefinition, indexDescriptions ...*IndexDescription) *Collection {
collection, err := ConnectCollection(suite.access, definition)
Expand All @@ -48,3 +45,17 @@ func (suite *AccessTestSuite) ConnectCollection(
}
return collection
}

// ConnectTypedCollectionHelper is similar to AccessTestSuite.ConnectionCollection().
// Go doesn't support generic methods so this can't be a method on AccessTestSuite.
func ConnectTypedCollectionHelper[T any](
suite *AccessTestSuite, definition *CollectionDefinition, indexDescriptions ...*IndexDescription) *TypedCollection[T] {
collection, err := ConnectTypedCollection[T](suite.access, definition)
suite.Require().NoError(err)
suite.NotNil(collection)
suite.Require().NoError(collection.DeleteAll())
for _, indexDescription := range indexDescriptions {
suite.Require().NoError(suite.access.Index(&collection.Collection, indexDescription))
}
return collection
}
2 changes: 1 addition & 1 deletion mdb/cached_collection_db_test.nogo
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestCacheSuite(t *testing.T) {

func (suite *cacheTestSuite) SetupSuite() {
suite.AccessTestSuite.SetupSuite()
reg.Highlander().Clear()
reg.Singleton().Clear()
suite.Require().NoError(RegisterWrapped())
var err error
suite.cached, err = ConnectCachedCollection[*SimpleItem](suite.access, testCollectionValidation, time.Hour)
Expand Down
7 changes: 1 addition & 6 deletions mdb/collection_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,7 @@ func TestCollectionSuite(t *testing.T) {

func (suite *collectionTestSuite) SetupSuite() {
suite.AccessTestSuite.SetupSuite()
var err error
suite.collection, err = ConnectCollection(suite.access, testCollection)
suite.Require().NoError(err)
suite.NotNil(suite.collection)
suite.Require().NoError(suite.collection.DeleteAll())
suite.Require().NoError(suite.access.Index(suite.collection, NewIndexDescription(true, "alpha")))
suite.collection = suite.ConnectCollection(testCollection, NewIndexDescription(true, "alpha"))
}

func (suite *collectionTestSuite) TearDownTest() {
Expand Down
4 changes: 0 additions & 4 deletions mdb/collection_definitions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,4 @@ var (
testCollectionWrapped = &CollectionDefinition{
Name: "test-collection-wrapped",
}
testCollectionIndexFinisher = &CollectionDefinition{
Name: "test-collection-index-finisher",
ValidationJSON: SimpleValidatorJSON,
}
)
8 changes: 2 additions & 6 deletions mdb/identity_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ func TestIdentitySuite(t *testing.T) {

func (suite *identityTestSuite) SetupSuite() {
suite.AccessTestSuite.SetupSuite()
var err error
suite.collection, err = ConnectCollection(suite.access, testCollection)
suite.Require().NoError(err)
suite.NotNil(suite.collection)
suite.Require().NoError(suite.collection.DeleteAll())
suite.collection = suite.ConnectCollection(testCollection)
}

func (suite *identityTestSuite) TearDownTest() {
Expand All @@ -42,7 +38,7 @@ func (suite *identityTestSuite) TestIndex() {
ind.Text = findable
suite.Require().NoError(suite.collection.Create(ind))
found := suite.findStruct(bson.D{{"text", findable}})
// Do we have an ID?
// Do we have an email?
suite.Require().NotNil(found.ObjectID)
suite.Require().NotNil(found.ID())
suite.Equal(found.ObjectID, found.ID())
Expand Down
61 changes: 57 additions & 4 deletions mdb/index_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
package mdb

import (
"context"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

Expand All @@ -18,10 +22,7 @@ func TestIndexSuite(t *testing.T) {
}

func (suite *indexTestSuite) SetupTest() {
var err error
suite.collection, err = ConnectCollection(suite.access, testCollectionValidation)
suite.Require().NoError(err)
suite.NotNil(suite.collection)
suite.collection = suite.ConnectCollection(testCollectionValidation)
}

func (suite *indexTestSuite) TearDownTest() {
Expand Down Expand Up @@ -70,3 +71,55 @@ func (suite *indexTestSuite) TestIndexFinisher() {
suite.NotNil(collection)
NewIndexTester().TestIndexes(suite.T(), collection, index)
}

// IndexTester provides a utility for verifying index creation.
type IndexTester []indexDatum

type indexDatum struct {
Name string
Key map[string]int32
Unique bool
}

func NewIndexTester() IndexTester {
return make(IndexTester, 0, 2)
}

// =============================================================================

func (it IndexTester) TestIndexes(t *testing.T, collection *Collection, descriptions ...*IndexDescription) {
ctx := context.Background()
cursor, err := collection.Indexes().List(ctx)
require.NoError(t, err)
err = cursor.All(ctx, &it)
require.NoError(t, err)
assert.Len(t, it, len(descriptions)+1)
it.hasIndexNamed(t, "_id_", NewIndexDescription(false, "_id"))
for _, description := range descriptions {
nameMap := make([]string, 0, len(description.keys))
for _, key := range description.keys {
nameMap = append(nameMap, key+"_1")
}
it.hasIndexNamed(t, strings.Join(nameMap, "_"), description)
}
}

func (it IndexTester) hasIndexNamed(t *testing.T, name string, description *IndexDescription) {
for _, data := range it {
if data.Name == name {
assert.Equal(t, description.unique, data.Unique, "check unique for index %s", name)
keyMap := make(map[string]int32, len(description.keys))
for _, key := range description.keys {
keyMap[key] = 1
}
assert.Equal(t, keyMap, data.Key, "check keys for index %s", name)
return
}
}

names := make([]string, 0, len(it))
for _, data := range it {
names = append(names, data.Name)
}
assert.Fail(t, "missing index", "no index %s (%s)", name, strings.Join(names, ", "))
}
219 changes: 219 additions & 0 deletions mdb/pointer_db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package mdb

import (
"fmt"
"os"
"strconv"
"testing"

"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/suite"
"go.mongodb.org/mongo-driver/bson"

"github.com/madkins23/go-serial/pointer"

"github.com/madkins23/go-mongo/mdbson"
)

type pointerTestSuite struct {
AccessTestSuite
showSerialized bool
targetCollection *TypedCollection[TargetItem]
pointerCollection *TypedCollection[PointerDemo]
}

func TestPointerSuite(t *testing.T) {
suite.Run(t, new(pointerTestSuite))
}

func (suite *pointerTestSuite) SetupSuite() {
if showSerialized, found := os.LookupEnv("GO-TYPE-SHOW-SERIALIZED"); found {
var err error
suite.showSerialized, err = strconv.ParseBool(showSerialized)
suite.Require().NoError(err)
}
suite.AccessTestSuite.SetupSuite()
suite.targetCollection = ConnectTypedCollectionHelper[TargetItem](
&suite.AccessTestSuite, testCollectionValidation)
suite.pointerCollection = ConnectTypedCollectionHelper[PointerDemo](
&suite.AccessTestSuite, testCollection)
}

func (suite *pointerTestSuite) SetupTest() {
pointer.ClearTargetCache()
pointer.ClearFinderCache()
}

func (suite *pointerTestSuite) TearDownTest() {
suite.NoError(suite.targetCollection.DeleteAll())
suite.NoError(suite.pointerCollection.DeleteAll())
}

//////////////////////////////////////////////////////////////////////////

func (suite *pointerTestSuite) TestPointerFinder() {
// This test will rely on automatic loading of the target cache
// when the demo object is saved to the DB.
// The Finder will not be used in this case.
suite.testPointerFinder("finder", false)
}

func (suite *pointerTestSuite) TestPointerFinderWithDB() {
// This test will wipe the target cache after saving the demo object.
// The test case
suite.testPointerFinder("database", true)
}

//------------------------------------------------------------------------

func (suite *pointerTestSuite) testPointerFinder(demoName string, forceFinder bool) {
var targetItems = targetItems()

// Load suite.targetCollection with records.
for _, item := range targetItems {
suite.Require().False(pointer.HasTarget(item.Group(), item.Key()))
suite.Require().NoError(suite.targetCollection.Create(item))
}

// Add one record to the cache.
suite.Require().NoError(pointer.SetTarget(targetItems[0], false))

// Check to see if only the one item is in the target cache.
for i, item := range targetItems {
if i == 0 {
suite.True(pointer.HasTarget(item.Group(), item.Key()))
} else {
suite.False(pointer.HasTarget(item.Group(), item.Key()))
}
}

// Create PointerDemo object.
demo := &PointerDemo{
Name: demoName,
Single: mdbson.Point[*TargetItem](targetItems[0]),
Array: make([]*mdbson.Pointer[*TargetItem], 0),
Map: make(map[string]*mdbson.Pointer[*TargetItem]),
}
for _, item := range targetItems {
demo.Array = append(demo.Array, mdbson.Point[*TargetItem](item))
demo.Map[item.Charlie] = mdbson.Point[*TargetItem](item)
}
if suite.showSerialized {
spew.Dump(demo)
}

// Put the demo object into suite.pointerCollection.
// Note: This will fill the target cache as it goes along.
// The items in the cache will be pre-store to the DB,
// so they won't have Mongo ObjectIDs.
suite.Require().NoError(suite.pointerCollection.Create(demo))

finderCount := 0
if forceFinder {
// Clear the target cache to force the Finder to be called.
pointer.ClearTargetCache()

// Set a Finder in the target cache to pull targets from suite.targetCollection.
suite.Require().NoError(pointer.SetFinder("simple", func(key string) (pointer.Target, error) {
finderCount++
item, err := suite.targetCollection.Find(bson.D{
{"alpha", key},
})
if IsNotFound(err) {
return nil, err
} else if err != nil {
return nil, fmt.Errorf("find record: %w", err)
} else {
return item, nil
}
}, false))
}

// Read the demo object back from suite.pointerCollection.
readBack, err := suite.pointerCollection.Find(bson.D{{"name", demo.Name}})
if suite.showSerialized {
fmt.Println("-----------------------------")
spew.Dump(readBack)
}
suite.Require().NoError(err)
readBack.clearObjectIDs()
suite.Equal(demo, readBack)

if forceFinder {
// Make sure Finder executed:
suite.Len(targetItems, finderCount)

// Note: In this mode the target cache has been rebuilt from the DB.
// The TargetItem entries now have Mongo ObjectIDs
// so they won't match the ones in the original demo object.
} else {
// Make sure the Finder did NOT execute:
suite.Equal(0, finderCount)

// The Pointer items should be singletons from the target cache.
suite.True(demo.Single.Get() == readBack.Single.Get())
for index, item := range demo.Array {
suite.True(item.Get() == readBack.Array[index].Get())
}
for key, item := range demo.Map {
suite.True(item.Get() == readBack.Map[key].Get())
}
}

// Check for records in target cache, should all be present now.
for _, item := range targetItems {
suite.True(pointer.HasTarget(item.Group(), item.Key()))
}
}

//========================================================================

type PointerDemo struct {
Name string
Single *mdbson.Pointer[*TargetItem]
Array []*mdbson.Pointer[*TargetItem]
Map map[string]*mdbson.Pointer[*TargetItem]
}

func (pd *PointerDemo) clearObjectIDs() {
pd.Single.Get().clearID()
for _, ptr := range pd.Array {
ptr.Get().clearID()
}
for _, ptr := range pd.Map {
ptr.Get().clearID()
}
}

var _ pointer.Target = &TargetItem{}

type TargetItem struct {
SimpleItem `bson:"inline"`
}

func (ti *TargetItem) clearID() {
ti.ObjectID = [12]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}

func (ti *TargetItem) Group() string {
return "simple"
}

func (ti *TargetItem) Key() string {
return ti.Alpha
}

var tgtItems []*TargetItem

func targetItems() []*TargetItem {
if tgtItems == nil {
for _, simpleItem := range simpleItems {
tgtItems = append(tgtItems, &TargetItem{SimpleItem: *simpleItem})
}
}
return tgtItems
}

var simpleItems = []*SimpleItem{
SimpleItem1, SimpleItem1x, SimpleItem2, SimpleItem3, UnfilteredItem,
}
Loading

0 comments on commit 397e0c7

Please sign in to comment.