315 lines
8.7 KiB
Go
315 lines
8.7 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package indexable
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tech/sendico/pkg/db/repository"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"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/bson/primitive"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func setupTestDB(t *testing.T) (repository.Repository, func()) {
|
|
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.ForLog("Waiting for connections")),
|
|
)
|
|
require.NoError(t, err, "failed to start MongoDB container")
|
|
|
|
mongoURI, err := mongoContainer.ConnectionString(ctx)
|
|
require.NoError(t, err, "failed to get MongoDB connection string")
|
|
|
|
clientOptions := options.Client().ApplyURI(mongoURI)
|
|
client, err := mongo.Connect(ctx, clientOptions)
|
|
require.NoError(t, err, "failed to connect to MongoDB")
|
|
|
|
db := client.Database("testdb")
|
|
repo := repository.CreateMongoRepository(db, "projects")
|
|
|
|
cleanup := func() {
|
|
disconnect(ctx, t, client)
|
|
terminate(ctx, t, mongoContainer)
|
|
}
|
|
|
|
return repo, cleanup
|
|
}
|
|
|
|
func disconnect(ctx context.Context, t *testing.T, client *mongo.Client) {
|
|
if err := client.Disconnect(ctx); err != nil {
|
|
t.Logf("failed to disconnect from MongoDB: %v", err)
|
|
}
|
|
}
|
|
|
|
func terminate(ctx context.Context, t *testing.T, container testcontainers.Container) {
|
|
if err := container.Terminate(ctx); err != nil {
|
|
t.Logf("failed to terminate MongoDB container: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIndexableDB_Reorder(t *testing.T) {
|
|
repo, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
organizationRef := primitive.NewObjectID()
|
|
logger := zap.NewNop()
|
|
|
|
// Create test projects with different indices
|
|
projects := []*model.Project{
|
|
{
|
|
ProjectBase: model.ProjectBase{
|
|
PermissionBound: model.PermissionBound{
|
|
OrganizationBoundBase: model.OrganizationBoundBase{
|
|
OrganizationRef: organizationRef,
|
|
},
|
|
},
|
|
Describable: model.Describable{Name: "Project A"},
|
|
Indexable: model.Indexable{Index: 0},
|
|
Mnemonic: "A",
|
|
State: model.ProjectStateActive,
|
|
},
|
|
},
|
|
{
|
|
ProjectBase: model.ProjectBase{
|
|
PermissionBound: model.PermissionBound{
|
|
OrganizationBoundBase: model.OrganizationBoundBase{
|
|
OrganizationRef: organizationRef,
|
|
},
|
|
},
|
|
Describable: model.Describable{Name: "Project B"},
|
|
Indexable: model.Indexable{Index: 1},
|
|
Mnemonic: "B",
|
|
State: model.ProjectStateActive,
|
|
},
|
|
},
|
|
{
|
|
ProjectBase: model.ProjectBase{
|
|
PermissionBound: model.PermissionBound{
|
|
OrganizationBoundBase: model.OrganizationBoundBase{
|
|
OrganizationRef: organizationRef,
|
|
},
|
|
},
|
|
Describable: model.Describable{Name: "Project C"},
|
|
Indexable: model.Indexable{Index: 2},
|
|
Mnemonic: "C",
|
|
State: model.ProjectStateActive,
|
|
},
|
|
},
|
|
{
|
|
ProjectBase: model.ProjectBase{
|
|
PermissionBound: model.PermissionBound{
|
|
OrganizationBoundBase: model.OrganizationBoundBase{
|
|
OrganizationRef: organizationRef,
|
|
},
|
|
},
|
|
Describable: model.Describable{Name: "Project D"},
|
|
Indexable: model.Indexable{Index: 3},
|
|
Mnemonic: "D",
|
|
State: model.ProjectStateActive,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Insert projects into database
|
|
for _, project := range projects {
|
|
project.ID = primitive.NewObjectID()
|
|
err := repo.Insert(ctx, project, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Create helper functions for Project type
|
|
createEmpty := func() *model.Project {
|
|
return &model.Project{}
|
|
}
|
|
|
|
getIndexable := func(p *model.Project) *model.Indexable {
|
|
return &p.Indexable
|
|
}
|
|
|
|
indexableDB := NewIndexableDB(repo, logger, createEmpty, getIndexable)
|
|
|
|
t.Run("Reorder_NoChange", func(t *testing.T) {
|
|
// Test reordering to the same position (should be no-op)
|
|
err := indexableDB.Reorder(ctx, projects[1].ID, 1, repository.Query())
|
|
require.NoError(t, err)
|
|
|
|
// Verify indices haven't changed
|
|
var result model.Project
|
|
err = repo.Get(ctx, projects[0].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, result.Index)
|
|
|
|
err = repo.Get(ctx, projects[1].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.Index)
|
|
})
|
|
|
|
t.Run("Reorder_MoveDown", func(t *testing.T) {
|
|
// Move Project A (index 0) to index 2
|
|
err := indexableDB.Reorder(ctx, projects[0].ID, 2, repository.Query())
|
|
require.NoError(t, err)
|
|
|
|
// Verify the reordering:
|
|
// Project A should now be at index 2
|
|
// Project B should be at index 0
|
|
// Project C should be at index 1
|
|
// Project D should remain at index 3
|
|
|
|
var result model.Project
|
|
|
|
// Check Project A (moved to index 2)
|
|
err = repo.Get(ctx, projects[0].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, result.Index)
|
|
|
|
// Check Project B (shifted to index 0)
|
|
err = repo.Get(ctx, projects[1].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, result.Index)
|
|
|
|
// Check Project C (shifted to index 1)
|
|
err = repo.Get(ctx, projects[2].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.Index)
|
|
|
|
// Check Project D (unchanged)
|
|
err = repo.Get(ctx, projects[3].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, result.Index)
|
|
})
|
|
|
|
t.Run("Reorder_MoveUp", func(t *testing.T) {
|
|
// Reset indices for this test
|
|
for i, project := range projects {
|
|
project.Index = i
|
|
err := repo.Update(ctx, project)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Move Project C (index 2) to index 0
|
|
err := indexableDB.Reorder(ctx, projects[2].ID, 0, repository.Query())
|
|
require.NoError(t, err)
|
|
|
|
// Verify the reordering:
|
|
// Project C should now be at index 0
|
|
// Project A should be at index 1
|
|
// Project B should be at index 2
|
|
// Project D should remain at index 3
|
|
|
|
var result model.Project
|
|
|
|
// Check Project C (moved to index 0)
|
|
err = repo.Get(ctx, projects[2].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, result.Index)
|
|
|
|
// Check Project A (shifted to index 1)
|
|
err = repo.Get(ctx, projects[0].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.Index)
|
|
|
|
// Check Project B (shifted to index 2)
|
|
err = repo.Get(ctx, projects[1].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, result.Index)
|
|
|
|
// Check Project D (unchanged)
|
|
err = repo.Get(ctx, projects[3].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, result.Index)
|
|
})
|
|
|
|
t.Run("Reorder_WithFilter", func(t *testing.T) {
|
|
// Reset indices for this test
|
|
for i, project := range projects {
|
|
project.Index = i
|
|
err := repo.Update(ctx, project)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Test reordering with organization filter
|
|
orgFilter := repository.OrgFilter(organizationRef)
|
|
err := indexableDB.Reorder(ctx, projects[0].ID, 2, orgFilter)
|
|
require.NoError(t, err)
|
|
|
|
// Verify the reordering worked with filter
|
|
var result model.Project
|
|
err = repo.Get(ctx, projects[0].ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, result.Index)
|
|
})
|
|
}
|
|
|
|
func TestIndexableDB_EdgeCases(t *testing.T) {
|
|
repo, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
organizationRef := primitive.NewObjectID()
|
|
logger := zap.NewNop()
|
|
|
|
// Create a single project for edge case testing
|
|
project := &model.Project{
|
|
ProjectBase: model.ProjectBase{
|
|
PermissionBound: model.PermissionBound{
|
|
OrganizationBoundBase: model.OrganizationBoundBase{
|
|
OrganizationRef: organizationRef,
|
|
},
|
|
},
|
|
Describable: model.Describable{Name: "Test Project"},
|
|
Indexable: model.Indexable{Index: 0},
|
|
Mnemonic: "TEST",
|
|
State: model.ProjectStateActive,
|
|
},
|
|
}
|
|
project.ID = primitive.NewObjectID()
|
|
err := repo.Insert(ctx, project, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Create helper functions for Project type
|
|
createEmpty := func() *model.Project {
|
|
return &model.Project{}
|
|
}
|
|
|
|
getIndexable := func(p *model.Project) *model.Indexable {
|
|
return &p.Indexable
|
|
}
|
|
|
|
indexableDB := NewIndexableDB(repo, logger, createEmpty, getIndexable)
|
|
|
|
t.Run("Reorder_SingleItem", func(t *testing.T) {
|
|
// Test reordering a single item (should work but have no effect)
|
|
err := indexableDB.Reorder(ctx, project.ID, 0, repository.Query())
|
|
require.NoError(t, err)
|
|
|
|
var result model.Project
|
|
err = repo.Get(ctx, project.ID, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, result.Index)
|
|
})
|
|
|
|
t.Run("Reorder_InvalidObjectID", func(t *testing.T) {
|
|
// Test reordering with an invalid object ID
|
|
invalidID := primitive.NewObjectID()
|
|
err := indexableDB.Reorder(ctx, invalidID, 1, repository.Query())
|
|
require.Error(t, err) // Should fail because object doesn't exist
|
|
})
|
|
}
|