//go:build integration // +build integration package policiesdb_test import ( "context" "errors" "testing" "time" // Your internal packages "github.com/tech/sendico/pkg/db/internal/mongo/policiesdb" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/db/repository/builder" "github.com/tech/sendico/pkg/merrors" // Model package (contains PolicyDescription + Describable) "github.com/tech/sendico/pkg/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" // Testcontainers "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mongodb" "github.com/testcontainers/testcontainers-go/wait" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.uber.org/zap" ) // Helper to terminate container func terminate(t *testing.T, ctx context.Context, container *mongodb.MongoDBContainer) { err := container.Terminate(ctx) require.NoError(t, err, "failed to terminate MongoDB container") } // Helper to disconnect client func disconnect(t *testing.T, ctx context.Context, client *mongo.Client) { err := client.Disconnect(context.Background()) require.NoError(t, err, "failed to disconnect from MongoDB") } // Helper to drop the Policies collection func cleanupCollection(t *testing.T, ctx context.Context, db *mongo.Database) { // The actual collection name is typically the value returned by // (&model.PolicyDescription{}).Collection(), or something similar. // Make sure it matches what your code uses (often "policies" or "policyDescription"). err := db.Collection((&model.PolicyDescription{}).Collection()).Drop(ctx) require.NoError(t, err, "failed to drop collection between sub-tests") } func TestPoliciesDB(t *testing.T) { // Create context with reasonable timeout ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() // Start MongoDB test container mongoC, err := mongodb.Run(ctx, "mongo:latest", mongodb.WithUsername("root"), mongodb.WithPassword("password"), testcontainers.WithWaitStrategy(wait.ForLog("Waiting for connections")), ) require.NoError(t, err, "failed to start MongoDB container") defer terminate(t, ctx, mongoC) // Get connection URI mongoURI, err := mongoC.ConnectionString(ctx) require.NoError(t, err, "failed to get connection string") // Connect client clientOpts := options.Client().ApplyURI(mongoURI) client, err := mongo.Connect(ctx, clientOpts) require.NoError(t, err, "failed to connect to MongoDB") defer disconnect(t, ctx, client) // Create test DB db := client.Database("testdb") // Use a no-op logger (or real logger if you prefer) logger := zap.NewNop() // Create an instance of PoliciesDB pdb, err := policiesdb.Create(logger, db) require.NoError(t, err, "unexpected error creating PoliciesDB") // --------------------------------------------------------- // Each sub-test below starts by dropping the collection. // --------------------------------------------------------- t.Run("CreateAndGet", func(t *testing.T) { cleanupCollection(t, ctx, db) // ensure no leftover data desc := "Test policy description" policy := &model.PolicyDescription{ Describable: model.Describable{ Name: "TestPolicy", Description: &desc, }, } require.NoError(t, pdb.Create(ctx, policy)) result := &model.PolicyDescription{} err := pdb.Get(ctx, policy.ID, result) require.NoError(t, err) assert.Equal(t, policy.ID, result.ID) assert.Equal(t, "TestPolicy", result.Name) assert.NotNil(t, result.Description) assert.Equal(t, "Test policy description", *result.Description) }) t.Run("Get_NotFound", func(t *testing.T) { cleanupCollection(t, ctx, db) // Attempt to get a non-existent ID nonExistentID := primitive.NewObjectID() result := &model.PolicyDescription{} err := pdb.Get(ctx, nonExistentID, result) assert.Error(t, err) assert.True(t, errors.Is(err, merrors.ErrNoData)) }) t.Run("Update", func(t *testing.T) { cleanupCollection(t, ctx, db) originalDesc := "Original description" policy := &model.PolicyDescription{ Describable: model.Describable{ Name: "OriginalName", Description: &originalDesc, }, } require.NoError(t, pdb.Create(ctx, policy)) newDesc := "Updated description" policy.Name = "UpdatedName" policy.Description = &newDesc err := pdb.Update(ctx, policy) require.NoError(t, err) updated := &model.PolicyDescription{} err = pdb.Get(ctx, policy.ID, updated) require.NoError(t, err) assert.Equal(t, "UpdatedName", updated.Name) assert.NotNil(t, updated.Description) assert.Equal(t, "Updated description", *updated.Description) }) t.Run("Delete", func(t *testing.T) { cleanupCollection(t, ctx, db) desc := "To be deleted" policy := &model.PolicyDescription{ Describable: model.Describable{ Name: "WillDelete", Description: &desc, }, } require.NoError(t, pdb.Create(ctx, policy)) err := pdb.Delete(ctx, policy.ID) require.NoError(t, err) deleted := &model.PolicyDescription{} err = pdb.Get(ctx, policy.ID, deleted) assert.Error(t, err) assert.True(t, errors.Is(err, merrors.ErrNoData)) }) t.Run("DeleteMany", func(t *testing.T) { cleanupCollection(t, ctx, db) desc1 := "Will be deleted 1" desc2 := "Will be deleted 2" pol1 := &model.PolicyDescription{ Describable: model.Describable{ Name: "BatchDelete1", Description: &desc1, }, } pol2 := &model.PolicyDescription{ Describable: model.Describable{ Name: "BatchDelete2", Description: &desc2, }, } require.NoError(t, pdb.Create(ctx, pol1)) require.NoError(t, pdb.Create(ctx, pol2)) q := repository.Query().RegEx(repository.Field("description"), "^Will be deleted", "") err := pdb.DeleteMany(ctx, q) require.NoError(t, err) res1 := &model.PolicyDescription{} err1 := pdb.Get(ctx, pol1.ID, res1) assert.Error(t, err1) assert.True(t, errors.Is(err1, merrors.ErrNoData)) res2 := &model.PolicyDescription{} err2 := pdb.Get(ctx, pol2.ID, res2) assert.Error(t, err2) assert.True(t, errors.Is(err2, merrors.ErrNoData)) }) t.Run("FindOne", func(t *testing.T) { cleanupCollection(t, ctx, db) desc := "Unique find test" policy := &model.PolicyDescription{ Describable: model.Describable{ Name: "FindOneTest", Description: &desc, }, } require.NoError(t, pdb.Create(ctx, policy)) // Match by name == "FindOneTest" q := repository.Query().Comparison(repository.Field("name"), builder.Eq, "FindOneTest") found := &model.PolicyDescription{} err := pdb.FindOne(ctx, q, found) require.NoError(t, err) assert.Equal(t, policy.ID, found.ID) assert.Equal(t, "FindOneTest", found.Name) assert.NotNil(t, found.Description) assert.Equal(t, "Unique find test", *found.Description) }) t.Run("All", func(t *testing.T) { cleanupCollection(t, ctx, db) // Insert some policies (orgA, orgB, nil org) orgA := primitive.NewObjectID() orgB := primitive.NewObjectID() descA := "Org A policy" policyA := &model.PolicyDescription{ Describable: model.Describable{ Name: "PolicyA", Description: &descA, }, OrganizationRef: &orgA, // belongs to orgA } descB := "Org B policy" policyB := &model.PolicyDescription{ Describable: model.Describable{ Name: "PolicyB", Description: &descB, }, OrganizationRef: &orgB, // belongs to orgB } descNil := "No org policy" policyNil := &model.PolicyDescription{ Describable: model.Describable{ Name: "PolicyNil", Description: &descNil, }, // nil => built-in } require.NoError(t, pdb.Create(ctx, policyA)) require.NoError(t, pdb.Create(ctx, policyB)) require.NoError(t, pdb.Create(ctx, policyNil)) // Suppose the requirement is: "All" returns // - policies for the requested org // - plus built-in (nil) ones resultsA, err := pdb.All(ctx, orgA) require.NoError(t, err) require.Len(t, resultsA, 2) // orgA + built-in var idsA []primitive.ObjectID for _, r := range resultsA { idsA = append(idsA, r.ID) } assert.Contains(t, idsA, policyA.ID) assert.Contains(t, idsA, policyNil.ID) assert.NotContains(t, idsA, policyB.ID) resultsB, err := pdb.All(ctx, orgB) require.NoError(t, err) require.Len(t, resultsB, 2) // orgB + built-in var idsB []primitive.ObjectID for _, r := range resultsB { idsB = append(idsB, r.ID) } assert.Contains(t, idsB, policyB.ID) assert.Contains(t, idsB, policyNil.ID) assert.NotContains(t, idsB, policyA.ID) }) t.Run("Policies", func(t *testing.T) { cleanupCollection(t, ctx, db) desc1 := "PolicyOne" pol1 := &model.PolicyDescription{ Describable: model.Describable{ Name: "PolicyOne", Description: &desc1, }, } desc2 := "PolicyTwo" pol2 := &model.PolicyDescription{ Describable: model.Describable{ Name: "PolicyTwo", Description: &desc2, }, } desc3 := "PolicyThree" pol3 := &model.PolicyDescription{ Describable: model.Describable{ Name: "PolicyThree", Description: &desc3, }, } require.NoError(t, pdb.Create(ctx, pol1)) require.NoError(t, pdb.Create(ctx, pol2)) require.NoError(t, pdb.Create(ctx, pol3)) // 1) Request pol1, pol2 results12, err := pdb.Policies(ctx, []primitive.ObjectID{pol1.ID, pol2.ID}) require.NoError(t, err) require.Len(t, results12, 2) // IDs might be out of order, so we do a set-like check var set12 []primitive.ObjectID for _, r := range results12 { set12 = append(set12, r.ID) } assert.Contains(t, set12, pol1.ID) assert.Contains(t, set12, pol2.ID) // 2) Request pol1, pol3, plus a random ID fakeID := primitive.NewObjectID() results13Fake, err := pdb.Policies(ctx, []primitive.ObjectID{pol1.ID, pol3.ID, fakeID}) require.NoError(t, err) require.Len(t, results13Fake, 2) // pol1 + pol3 only var set13Fake []primitive.ObjectID for _, r := range results13Fake { set13Fake = append(set13Fake, r.ID) } assert.Contains(t, set13Fake, pol1.ID) assert.Contains(t, set13Fake, pol3.ID) // 3) Request with empty slice => expect no results resultsEmpty, err := pdb.Policies(ctx, []primitive.ObjectID{}) require.NoError(t, err) assert.Len(t, resultsEmpty, 0) }) }