Files
Stephan D 62a6631b9a
All checks were successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
service backend
2025-11-07 18:35:26 +01:00

354 lines
10 KiB
Go

//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)
})
}