func checkReferenceQueryResultsInOrder(t *testing.T, it spi.ReferenceIterator, expectedTotalItems int,
	expectedIRIs ...*url.URL) {
	t.Helper()
	require.NotNil(t, it)
	for i := 0; i < len(expectedIRIs); i++ {
		iri, err := it.Next()
		require.NoError(t, err)
		require.NotNil(t, iri)
		require.Equal(t, expectedIRIs[i].String(), iri.String())
	}
	totalItems, err := it.TotalItems()
	require.NoError(t, err)
	require.Equal(t, expectedTotalItems, totalItems)
	iri, err := it.Next()
	require.Error(t, err)
	require.True(t, errors.Is(err, spi.ErrNotFound))
	require.Nil(t, iri)
	require.NoError(t, it.Close())
}
expectedIRIs is with respect to the query's page settings.
Since Iterator.TotalItems' count is not affected by page settings, expectedTotalItems must be passed in explicitly.
It can't be determined by looking at the length of expectedIRIs.
package ariesstore_test
import (
	"context"
	"errors"
	"net/url"
	"testing"
	"time"
	"github.com/cenkalti/backoff"
	"github.com/google/uuid"
	ariesmongodbstorage "github.com/hyperledger/aries-framework-go-ext/component/storage/mongodb"
	"github.com/hyperledger/aries-framework-go/component/storageutil/mem"
	"github.com/hyperledger/aries-framework-go/component/storageutil/mock"
	"github.com/hyperledger/aries-framework-go/spi/storage"
	dctest "github.com/ory/dockertest/v3"
	dc "github.com/ory/dockertest/v3/docker"
	"github.com/stretchr/testify/require"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"github.com/trustbloc/orb/pkg/activitypub/store/ariesstore"
	"github.com/trustbloc/orb/pkg/activitypub/store/spi"
	"github.com/trustbloc/orb/pkg/activitypub/vocab"
	"github.com/trustbloc/orb/pkg/internal/testutil"
)
const mongoDBConnString = "mongodb://localhost:27017"
type mockStore struct {
	openStoreNameToFailOn      string
	setStoreConfigNameToFailOn string
}
func (m *mockStore) OpenStore(name string) (storage.Store, error) {
	if name == m.openStoreNameToFailOn {
		return nil, errors.New("open store error")
	}
	return nil, nil
}
func (m *mockStore) SetStoreConfig(name string, _ storage.StoreConfiguration) error {
	if name == m.setStoreConfigNameToFailOn {
		return errors.New("set store config error")
	}
	return nil
}
func (m *mockStore) GetStoreConfig(string) (storage.StoreConfiguration, error) {
	panic("implement me")
}
func (m *mockStore) GetOpenStores() []storage.Store {
	panic("implement me")
}
func (m *mockStore) Close() error {
	panic("implement me")
}
func startMongoDBContainer(t *testing.T) (*dctest.Pool, *dctest.Resource) {
	t.Helper()
	pool, err := dctest.NewPool("")
	require.NoError(t, err)
	mongoDBResource, err := pool.RunWithOptions(&dctest.RunOptions{
		Repository: "mongo",
		Tag:        "4.0.0",
		PortBindings: map[dc.Port][]dc.PortBinding{
			"27017/tcp": {{HostIP: "", HostPort: "27017"}},
		},
	})
	require.NoError(t, err)
	require.NoError(t, waitForMongoDBToBeUp())
	return pool, mongoDBResource
}
func waitForMongoDBToBeUp() error {
	return backoff.Retry(pingMongoDB, backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 30))
}
func pingMongoDB() error {
	var err error
	clientOpts := options.Client().ApplyURI(mongoDBConnString)
	mongoClient, err := mongo.NewClient(clientOpts)
	if err != nil {
		return err
	}
	err = mongoClient.Connect(context.Background())
	if err != nil {
		return err
	}
	db := mongoClient.Database("test")
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	return db.Client().Ping(ctx, nil)
}
func TestNew(t *testing.T) {
	t.Run("Failed to open activities store", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mockStore{
			openStoreNameToFailOn: "activity",
		}, false)
		require.EqualError(t, err, "failed to open stores: failed to open activity store: open store error")
		require.Nil(t, provider)
	})
	t.Run("Failed to set store config on activities store", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mockStore{
			setStoreConfigNameToFailOn: "activity",
		}, false)
		require.EqualError(t, err, "failed to open stores: failed to set store configuration on "+
			"activity store: set store config error")
		require.Nil(t, provider)
	})
	t.Run("Failed to open inbox store", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mockStore{
			openStoreNameToFailOn: string(spi.Inbox),
		}, false)
		require.EqualError(t, err, "failed to open stores: failed to open reference stores: "+
			"failed to open INBOX store: open store error")
		require.Nil(t, provider)
	})
	t.Run("Failed to set store config on inbox store", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mockStore{
			setStoreConfigNameToFailOn: string(spi.Inbox),
		}, false)
		require.EqualError(t, err, "failed to open stores: failed to open reference stores: "+
			"failed to set store configuration on INBOX store: set store config error")
		require.Nil(t, provider)
	})
	t.Run("Failed to open actor store", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mockStore{
			openStoreNameToFailOn: "actor",
		}, false)
		require.EqualError(t, err, "failed to open stores: failed to open actor store: open store error")
		require.Nil(t, provider)
	})
}
func TestFunctionalityUsingMongoDB(t *testing.T) {
	pool, mongoDBResource := startMongoDBContainer(t)
	defer func() {
		require.NoError(t, pool.Purge(mongoDBResource), "failed to purge MongoDB resource")
	}()
	t.Run("Activity tests", func(t *testing.T) {
		serviceName := generateRandomServiceName()
		mongoDBProvider, err := ariesmongodbstorage.NewProvider(mongoDBConnString,
			ariesmongodbstorage.WithDBPrefix(serviceName))
		require.NoError(t, err)
		s, err := ariesstore.New(serviceName, mongoDBProvider, true)
		require.NoError(t, err)
		serviceID1 := testutil.MustParseURL("https://example.com/services/service1")
		activityID1 := testutil.MustParseURL("https://example.com/activities/activity1")
		activityID2 := testutil.MustParseURL("https://example.com/activities/activity2")
		activityID3 := testutil.MustParseURL("https://example.com/activities/activity3")
		a, err := s.GetActivity(activityID1)
		require.Error(t, err)
		require.True(t, errors.Is(err, spi.ErrNotFound))
		require.Nil(t, a)
		activity1 := vocab.NewCreateActivity(vocab.NewObjectProperty(vocab.WithIRI(serviceID1)),
			vocab.WithID(activityID1))
		require.NoError(t, s.AddActivity(activity1))
		receivedActivity1, err := s.GetActivity(activityID1)
		require.NoError(t, err)
		receivedActivity1Bytes, err := receivedActivity1.MarshalJSON()
		require.NoError(t, err)
		expectedActivity1Bytes, err := activity1.MarshalJSON()
		require.NoError(t, err)
		require.Equal(t, string(expectedActivity1Bytes), string(receivedActivity1Bytes))
		activity2 := vocab.NewAnnounceActivity(vocab.NewObjectProperty(vocab.WithIRI(serviceID1)),
			vocab.WithID(activityID2))
		require.NoError(t, s.AddActivity(activity2))
		activity3 := vocab.NewCreateActivity(vocab.NewObjectProperty(vocab.WithIRI(serviceID1)),
			vocab.WithID(activityID3))
		require.NoError(t, s.AddActivity(activity3))
		it, err := s.QueryActivities(
			spi.NewCriteria(spi.WithReferenceType(spi.Inbox), spi.WithObjectIRI(serviceID1)))
		require.NoError(t, err)
		require.NotNil(t, it)
		checkActivityQueryResultsInOrder(t, it, 0)
		require.NoError(t, s.AddReference(spi.Inbox, serviceID1, activityID1))
		require.NoError(t, s.AddReference(spi.Inbox, serviceID1, activityID2))
		require.NoError(t, s.AddReference(spi.Inbox, serviceID1, activityID3))
		t.Run("Query all", func(t *testing.T) {
			t.Run("Ascending (default) order", func(t *testing.T) {
				it, err := s.QueryActivities(spi.NewCriteria())
				require.NoError(t, err)
				require.NotNil(t, it)
				checkActivityQueryResultsInOrder(t, it, 3, activityID1, activityID2, activityID3)
			})
			t.Run("Descending order", func(t *testing.T) {
				it, err := s.QueryActivities(spi.NewCriteria(), spi.WithSortOrder(spi.SortDescending))
				require.NoError(t, err)
				require.NotNil(t, it)
				checkActivityQueryResultsInOrder(t, it, 3, activityID3, activityID2, activityID1)
			})
		})
		t.Run("Query by reference", func(t *testing.T) {
			t.Run("Ascending (default) order", func(t *testing.T) {
				t.Run("Default page size", func(t *testing.T) {
					it, err := s.QueryActivities(
						spi.NewCriteria(spi.WithReferenceType(spi.Inbox), spi.WithObjectIRI(serviceID1)))
					require.NoError(t, err)
					require.NotNil(t, it)
					checkActivityQueryResultsInOrder(t, it, 3, activityID1, activityID2, activityID3)
				})
				t.Run("Page size 2", func(t *testing.T) {
					it, err := s.QueryActivities(
						spi.NewCriteria(spi.WithReferenceType(spi.Inbox), spi.WithObjectIRI(serviceID1)),
						spi.WithPageSize(2))
					require.NoError(t, err)
					require.NotNil(t, it)
					checkActivityQueryResultsInOrder(t, it, 3, activityID1, activityID2)
				})
			})
			t.Run("Descending order", func(t *testing.T) {
				it, err := s.QueryActivities(
					spi.NewCriteria(spi.WithReferenceType(spi.Inbox), spi.WithObjectIRI(serviceID1)),
					spi.WithSortOrder(spi.SortDescending))
				require.NoError(t, err)
				require.NotNil(t, it)
				checkActivityQueryResultsInOrder(t, it, 3, activityID3, activityID2, activityID1)
			})
			t.Run("Fail to get total items from reference iterator", func(t *testing.T) {
				mockAriesStore, err := ariesstore.New(serviceName, &mock.Provider{
					OpenStoreReturn: &mock.Store{
						QueryReturn: &mock.Iterator{
							ErrTotalItems: errors.New("total items error"),
						},
					},
				}, true)
				require.NoError(t, err)
				it, err := mockAriesStore.QueryActivities(
					spi.NewCriteria(spi.WithReferenceType(spi.Inbox), spi.WithObjectIRI(serviceID1)))
				require.EqualError(t, err,
					"failed to get total items from reference iterator: total items error")
				require.Nil(t, it)
			})
		})
	})
	t.Run("Actor tests", func(t *testing.T) {
		serviceName := generateRandomServiceName()
		mongoDBProvider, err := ariesmongodbstorage.NewProvider(mongoDBConnString,
			ariesmongodbstorage.WithDBPrefix(serviceName))
		require.NoError(t, err)
		s, err := ariesstore.New(serviceName, mongoDBProvider, false)
		require.NoError(t, err)
		actor1IRI := testutil.MustParseURL("https://actor1")
		actor2IRI := testutil.MustParseURL("https://actor2")
		a, err := s.GetActor(actor1IRI)
		require.EqualError(t, err, spi.ErrNotFound.Error())
		require.Nil(t, a)
		actor1 := vocab.NewService(actor1IRI)
		actor2 := vocab.NewService(actor2IRI)
		require.NoError(t, s.PutActor(actor1))
		require.NoError(t, s.PutActor(actor2))
		receivedActor1, err := s.GetActor(actor1IRI)
		require.NoError(t, err)
		expectedActor1Bytes, err := actor1.MarshalJSON()
		require.NoError(t, err)
		receivedActor1Bytes, err := receivedActor1.MarshalJSON()
		require.NoError(t, err)
		require.Equal(t, string(expectedActor1Bytes), string(receivedActor1Bytes))
		receivedActor2, err := s.GetActor(actor2IRI)
		require.NoError(t, err)
		expectedActor2Bytes, err := actor2.MarshalJSON()
		require.NoError(t, err)
		receivedActor2Bytes, err := receivedActor2.MarshalJSON()
		require.NoError(t, err)
		require.Equal(t, string(expectedActor2Bytes), string(receivedActor2Bytes))
	})
	t.Run("Reference tests", func(t *testing.T) {
		serviceName := generateRandomServiceName()
		mongoDBProvider, err := ariesmongodbstorage.NewProvider(mongoDBConnString,
			ariesmongodbstorage.WithDBPrefix(serviceName))
		require.NoError(t, err)
		s, err := ariesstore.New(serviceName, mongoDBProvider, true)
		require.NoError(t, err)
		actor1 := testutil.MustParseURL("https://actor1")
		actor2 := testutil.MustParseURL("https://actor2")
		actor3 := testutil.MustParseURL("https://actor3")
		actor4 := testutil.MustParseURL("https://actor4")
		it, err := s.QueryReferences(spi.Follower, spi.NewCriteria())
		require.EqualError(t, err, "object IRI is required")
		require.Nil(t, it)
		it, err = s.QueryReferences(spi.Follower, spi.NewCriteria(spi.WithObjectIRI(actor1)))
		require.NoError(t, err)
		require.NotNil(t, it)
		checkReferenceQueryResultsInOrder(t, it, 0)
		require.NoError(t, s.AddReference(spi.Follower, actor1, actor2))
		require.NoError(t, s.AddReference(spi.Follower, actor1, actor3))
		it, err = s.QueryReferences(spi.Follower, spi.NewCriteria(spi.WithObjectIRI(actor1)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 2, actor2, actor3)
		it, err = s.QueryReferences(spi.Follower, spi.NewCriteria(spi.WithObjectIRI(actor1)),
			spi.WithSortOrder(spi.SortDescending))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 2, actor3, actor2)
		it, err = s.QueryReferences(spi.Following, spi.NewCriteria(spi.WithObjectIRI(actor1)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 0)
		require.NoError(t, s.AddReference(spi.Following, actor1, actor2))
		it, err = s.QueryReferences(spi.Following, spi.NewCriteria(spi.WithObjectIRI(actor1)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 1, actor2)
		require.NoError(t, s.DeleteReference(spi.Follower, actor1, actor2))
		it, err = s.QueryReferences(spi.Follower, spi.NewCriteria(spi.WithObjectIRI(actor1)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 1, actor3)
		it, err = s.QueryReferences(spi.Follower, spi.NewCriteria(spi.WithObjectIRI(actor2)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 0)
		require.NoError(t, s.AddReference(spi.Follower, actor2, actor3))
		it, err = s.QueryReferences(spi.Follower, spi.NewCriteria(spi.WithObjectIRI(actor2)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 1, actor3)
		it, err = s.QueryReferences(spi.Follower,
			spi.NewCriteria(spi.WithObjectIRI(actor2), spi.WithReferenceIRI(actor3)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 1, actor3)
		it, err = s.QueryReferences(spi.Follower,
			spi.NewCriteria(spi.WithObjectIRI(actor2), spi.WithType(vocab.TypeCreate)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 0)
		require.NoError(t, s.AddReference(spi.Follower, actor2, actor4, spi.WithActivityType(vocab.TypeCreate)))
		it, err = s.QueryReferences(spi.Follower,
			spi.NewCriteria(spi.WithObjectIRI(actor2), spi.WithType(vocab.TypeCreate)))
		require.NoError(t, err)
		checkReferenceQueryResultsInOrder(t, it, 1, actor4)
	})
}
func TestStore_Activity_Failures(t *testing.T) {
	t.Run("Fail to add activity", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mock.Provider{
			OpenStoreReturn: &mock.Store{
				ErrPut: errors.New("put error"),
			},
		}, false)
		require.NoError(t, err)
		serviceID1 := testutil.MustParseURL("https://example.com/services/service1")
		activityID1 := testutil.MustParseURL("https://example.com/activities/activity1")
		err = provider.AddActivity(vocab.NewCreateActivity(vocab.NewObjectProperty(vocab.WithIRI(serviceID1)),
			vocab.WithID(activityID1)))
		require.EqualError(t, err, "failed to store activity: put error")
	})
	t.Run("Fail to get activity", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mock.Provider{
			OpenStoreReturn: &mock.Store{
				ErrGet: errors.New("get error"),
			},
		}, false)
		require.NoError(t, err)
		_, err = provider.GetActivity(testutil.MustParseURL("https://example.com/activities/activity1"))
		require.EqualError(t, err, "unexpected failure while getting activity from store: get error")
	})
	t.Run("Fail to query", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mock.Provider{
			OpenStoreReturn: &mock.Store{
				ErrQuery: errors.New("query error"),
			},
		}, false)
		require.NoError(t, err)
		_, err = provider.QueryActivities(spi.NewCriteria())
		require.EqualError(t, err, "failed to query store: query error")
	})
	t.Run("Unsupported query criteria", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", mem.NewProvider(), false)
		require.NoError(t, err)
		serviceID1 := testutil.MustParseURL("https://example.com/services/service1")
		_, err = provider.QueryActivities(spi.NewCriteria(spi.WithObjectIRI(serviceID1),
			spi.WithActivityIRIs(testutil.MustParseURL("https://example.com/activities/activity1"),
				testutil.MustParseURL("https://example.com/activities/activity1"))))
		require.EqualError(t, err, "unsupported query criteria")
	})
}
func TestStore_Actor_Failures(t *testing.T) {
	t.Run("Fail to put actor", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mock.Provider{
			OpenStoreReturn: &mock.Store{
				ErrPut: errors.New("put error"),
			},
		}, false)
		require.NoError(t, err)
		err = provider.PutActor(vocab.NewService(testutil.MustParseURL("https://actor1")))
		require.EqualError(t, err, "failed to store actor: put error")
	})
	t.Run("Fail to get actor", func(t *testing.T) {
		provider, err := ariesstore.New("ServiceName", &mock.Provider{
			OpenStoreReturn: &mock.Store{
				ErrGet: errors.New("get error"),
			},
		}, false)
		require.NoError(t, err)
		_, err = provider.GetActor(testutil.MustParseURL("https://actor1"))
		require.EqualError(t, err, "unexpected failure while getting actor from store: get error")
	})
}
func TestStore_Reference_Failures(t *testing.T) {
	t.Run("Fail to add reference", func(t *testing.T) {
		t.Run("Fail to store in underlying storage", func(t *testing.T) {
			provider, err := ariesstore.New("ServiceName", &mock.Provider{
				OpenStoreReturn: &mock.Store{
					ErrPut: errors.New("put error"),
				},
			}, false)
			require.NoError(t, err)
			actor1 := testutil.MustParseURL("https://actor1")
			actor2 := testutil.MustParseURL("https://actor2")
			err = provider.AddReference(spi.Following, actor1, actor2)
			require.EqualError(t, err, "failed to store reference: put error")
		})
		t.Run("No store found for the reference type", func(t *testing.T) {
			provider, err := ariesstore.New("ServiceName", mem.NewProvider(), false)
			require.NoError(t, err)
			actor1 := testutil.MustParseURL("https://actor1")
			actor2 := testutil.MustParseURL("https://actor2")
			err = provider.AddReference("UnknownReferenceType", actor1, actor2)
			require.EqualError(t, err, "no store found for UnknownReferenceType")
		})
	})
	t.Run("Fail to delete reference", func(t *testing.T) {
		t.Run("Fail to delete in underlying storage", func(t *testing.T) {
			provider, err := ariesstore.New("ServiceName", &mock.Provider{
				OpenStoreReturn: &mock.Store{
					ErrDelete: errors.New("delete error"),
				},
			}, false)
			require.NoError(t, err)
			actor1 := testutil.MustParseURL("https://actor1")
			actor2 := testutil.MustParseURL("https://actor2")
			err = provider.DeleteReference(spi.Following, actor1, actor2)
			require.EqualError(t, err, "failed to delete reference: delete error")
		})
		t.Run("No store found for the reference type", func(t *testing.T) {
			provider, err := ariesstore.New("ServiceName", mem.NewProvider(), false)
			require.NoError(t, err)
			actor1 := testutil.MustParseURL("https://actor1")
			actor2 := testutil.MustParseURL("https://actor2")
			err = provider.DeleteReference("UnknownReferenceType", actor1, actor2)
			require.EqualError(t, err, "no store found for UnknownReferenceType")
		})
	})
	t.Run("Fail to query references", func(t *testing.T) {
		t.Run("Fail to query in underlying storage", func(t *testing.T) {
			provider, err := ariesstore.New("ServiceName", &mock.Provider{
				OpenStoreReturn: &mock.Store{
					ErrQuery: errors.New("query error"),
				},
			}, false)
			require.NoError(t, err)
			actor1 := testutil.MustParseURL("https://actor1")
			_, err = provider.QueryReferences(spi.Following, spi.NewCriteria(spi.WithObjectIRI(actor1)))
			require.EqualError(t, err, "failed to query store: query error")
		})
		t.Run("No store found for the reference type", func(t *testing.T) {
			provider, err := ariesstore.New("ServiceName", mem.NewProvider(), false)
			require.NoError(t, err)
			actor1 := testutil.MustParseURL("https://actor1")
			_, err = provider.QueryReferences("UnknownReferenceType",
				spi.NewCriteria(spi.WithObjectIRI(actor1)))
			require.EqualError(t, err, "no store found for UnknownReferenceType")
		})
		t.Run("Fail to query with both object IRI and activity type", func(t *testing.T) {
			provider, err := ariesstore.New("ServiceName", mem.NewProvider(), false)
			require.NoError(t, err)
			actor1 := testutil.MustParseURL("https://actor1")
			it, err := provider.QueryReferences(spi.Follower,
				spi.NewCriteria(spi.WithObjectIRI(actor1), spi.WithType(vocab.TypeCreate)))
			require.EqualError(t, err, "cannot run query since the underlying storage provider "+
				"does not support querying with multiple tags")
			require.Nil(t, it)
		})
	})
}
func checkActivityQueryResultsInOrder(t *testing.T, it spi.ActivityIterator, expectedTotalItems int,
	expectedActivities ...*url.URL) {
	t.Helper()
	require.NotNil(t, it)
	for i := 0; i < len(expectedActivities); i++ {
		retrievedActivity, err := it.Next()
		require.NoError(t, err)
		require.NotNil(t, retrievedActivity)
		require.Equal(t, expectedActivities[i].String(), retrievedActivity.ID().URL().String())
	}
	totalItems, err := it.TotalItems()
	require.NoError(t, err)
	require.Equal(t, expectedTotalItems, totalItems)
	retrievedActivity, err := it.Next()
	require.Error(t, err)
	require.True(t, errors.Is(err, spi.ErrNotFound))
	require.Nil(t, retrievedActivity)
}