//go:build integration // +build integration package invitationdb import ( "context" "testing" "time" "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" "git.sendico.io/tech/sxchnge/bff/pkg/merrors" "git.sendico.io/tech/sxchnge/bff/pkg/model" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mongodb" "github.com/testcontainers/testcontainers-go/wait" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) func TestPendingInvitationEmailIndexBehavior(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() mongoContainer, err := mongodb.Run(ctx, "mongo:latest", mongodb.WithUsername("root"), mongodb.WithPassword("password"), testcontainers.WithWaitStrategy(wait.ForListeningPort("27017/tcp").WithStartupTimeout(2*time.Minute)), ) require.NoError(t, err) defer func() { termCtx, termCancel := context.WithTimeout(context.Background(), 30*time.Second) defer termCancel() _ = mongoContainer.Terminate(termCtx) }() mongoURI, err := mongoContainer.ConnectionString(ctx) require.NoError(t, err) client, err := mongo.Connect(options.Client().ApplyURI(mongoURI)) require.NoError(t, err) defer func() { _ = client.Disconnect(context.Background()) }() database := client.Database("test_invitation_pending_email_index") defer func() { _ = database.Drop(context.Background()) }() repo := repository.CreateMongoRepository(database, "invitations") require.NoError(t, repo.CreateIndex(pendingInvitationEmailIndexDefinition())) assertPendingEmailIndexDefinition(t, ctx, database.Collection("invitations")) orgRef := bson.NewObjectID() email := "invitee@example.com" require.NoError(t, repo.Insert(ctx, testInvitation(orgRef, email, model.InvitationCreated), nil)) err = repo.Insert(ctx, testInvitation(orgRef, email, model.InvitationSent), nil) require.ErrorIs(t, err, merrors.ErrDataConflict, "duplicate pending invitation must conflict") for _, status := range []model.InvitationStatus{model.InvitationRevoked, model.InvitationDeclined, model.InvitationAccepted} { statusEmail := string(status) + "@example.com" require.NoError(t, repo.Insert(ctx, testInvitation(orgRef, statusEmail, status), nil)) require.NoError(t, repo.Insert(ctx, testInvitation(orgRef, statusEmail, model.InvitationCreated), nil)) } otherOrgRef := bson.NewObjectID() require.NoError(t, repo.Insert(ctx, testInvitation(otherOrgRef, email, model.InvitationCreated), nil)) } func testInvitation(orgRef bson.ObjectID, email string, status model.InvitationStatus) *model.Invitation { inv := &model.Invitation{ OrganizationRef: orgRef, Status: status, ExpiresAt: time.Now().UTC().Add(time.Hour), } inv.SetID(bson.NewObjectID()) inv.SetOrganizationRef(orgRef) inv.Content.Email = email return inv } func assertPendingEmailIndexDefinition(t *testing.T, ctx context.Context, collection *mongo.Collection) { t.Helper() cursor, err := collection.Indexes().List(ctx) require.NoError(t, err) defer func() { _ = cursor.Close(ctx) }() for cursor.Next(ctx) { var index bson.M require.NoError(t, cursor.Decode(&index)) if index["name"] != uniquePendingInvitationEmailIndexName { continue } require.Equal(t, true, index["unique"]) require.Equal(t, bson.D{ {Key: "organizationRef", Value: int32(1)}, {Key: "description.email", Value: int32(1)}, }, index["key"]) require.Equal(t, bson.D{ {Key: "status", Value: bson.D{{Key: "$in", Value: bson.A{"created", "sent"}}}}, {Key: "description.email", Value: bson.D{{Key: "$type", Value: "string"}}}, }, index["partialFilterExpression"]) return } require.NoError(t, cursor.Err()) t.Fatalf("index %q was not created", uniquePendingInvitationEmailIndexName) }