service backend
All checks were successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful

This commit is contained in:
Stephan D
2025-11-07 18:35:26 +01:00
parent 20e8f9acc4
commit 62a6631b9a
537 changed files with 48453 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
# Indexable Implementation (Refactored)
## Overview
This package provides a refactored implementation of the `indexable.DB` interface that uses `mutil.GetObjects` for better consistency with the existing codebase. The implementation has been moved to the mongo folder and includes a factory for project indexable in the pkg/db folder.
## Structure
### 1. `api/pkg/db/internal/mongo/indexable/indexable.go`
- **`ReorderTemplate[T]`**: Generic template function that uses `mutil.GetObjects` for fetching objects
- **`IndexableDB`**: Base struct for creating concrete implementations
- **Type-safe implementation**: Uses Go generics with proper type constraints
### 2. `api/pkg/db/project_indexable.go`
- **`ProjectIndexableDB`**: Factory implementation for Project objects
- **`NewProjectIndexableDB`**: Constructor function
- **`ReorderTemplate`**: Duplicate of the mongo version for convenience
## Key Changes from Previous Implementation
### 1. **Uses `mutil.GetObjects`**
```go
// Old implementation (manual cursor handling)
err = repo.FindManyByFilter(ctx, filter, func(cursor *mongo.Cursor) error {
var obj T
if err := cursor.Decode(&obj); err != nil {
return err
}
objects = append(objects, obj)
return nil
})
// New implementation (using mutil.GetObjects)
objects, err := mutil.GetObjects[T](
ctx,
logger,
filterFunc().
And(
repository.IndexOpFilter(minIdx, builder.Gte),
repository.IndexOpFilter(maxIdx, builder.Lte),
),
nil, nil, nil, // limit, offset, isArchived
repo,
)
```
### 2. **Moved to Mongo Folder**
- Location: `api/pkg/db/internal/mongo/indexable/`
- Consistent with other mongo implementations
- Better organization within the codebase
### 3. **Added Factory in pkg/db**
- Location: `api/pkg/db/project_indexable.go`
- Provides easy access to project indexable functionality
- Includes logger parameter for better error handling
## Usage
### Using the Factory (Recommended)
```go
import "github.com/tech/sendico/pkg/db"
// Create a project indexable DB
projectDB := db.NewProjectIndexableDB(repo, logger, organizationRef)
// Reorder a project
err := projectDB.Reorder(ctx, projectID, newIndex)
if err != nil {
// Handle error
}
```
### Using the Template Directly
```go
import "github.com/tech/sendico/pkg/db/internal/mongo/indexable"
// Define helper functions
getIndexable := func(p *model.Project) *model.Indexable {
return &p.Indexable
}
updateIndexable := func(p *model.Project, newIndex int) {
p.Index = newIndex
}
createEmpty := func() *model.Project {
return &model.Project{}
}
filterFunc := func() builder.Query {
return repository.OrgFilter(organizationRef)
}
// Use the template
err := indexable.ReorderTemplate(
ctx,
logger,
repo,
objectRef,
newIndex,
filterFunc,
getIndexable,
updateIndexable,
createEmpty,
)
```
## Benefits of Refactoring
1. **Consistency**: Uses `mutil.GetObjects` like other parts of the codebase
2. **Better Error Handling**: Includes logger parameter for proper error logging
3. **Organization**: Moved to appropriate folder structure
4. **Factory Pattern**: Easy-to-use factory for common use cases
5. **Type Safety**: Maintains compile-time type checking
6. **Performance**: Leverages existing optimized `mutil.GetObjects` implementation
## Testing
### Mongo Implementation Tests
```bash
go test ./db/internal/mongo/indexable -v
```
### Factory Tests
```bash
go test ./db -v
```
## Integration
The refactored implementation is ready for integration with existing project reordering APIs. The factory pattern makes it easy to add reordering functionality to any service that needs to reorder projects within an organization.
## Migration from Old Implementation
If you were using the old implementation:
1. **Update imports**: Change from `api/pkg/db/internal/indexable` to `api/pkg/db`
2. **Use factory**: Replace manual template usage with `NewProjectIndexableDB`
3. **Add logger**: Include a logger parameter in your constructor calls
4. **Update tests**: Use the new test structure if needed
The API remains the same, so existing code should work with minimal changes.

View File

@@ -0,0 +1,174 @@
# Indexable Usage Guide
## Generic Implementation for Any Indexable Struct
The implementation is now **generic** and supports **any struct that embeds `model.Indexable`**!
- **Interface**: `api/pkg/db/indexable.go` - defines the contract
- **Implementation**: `api/pkg/db/internal/mongo/indexable/` - generic implementation
- **Factory**: `api/pkg/db/project_indexable.go` - convenient factory for projects
## Usage
### 1. Using the Generic Implementation Directly
```go
import "github.com/tech/sendico/pkg/db/internal/mongo/indexable"
// For any type that embeds model.Indexable, define helper functions:
createEmpty := func() *YourType {
return &YourType{}
}
getIndexable := func(obj *YourType) *model.Indexable {
return &obj.Indexable
}
// Create generic IndexableDB
indexableDB := indexable.NewIndexableDB(repo, logger, createEmpty, getIndexable)
// Use with single filter parameter
err := indexableDB.Reorder(ctx, objectID, newIndex, filter)
```
### 2. Using the Project Factory (Recommended for Projects)
```go
import "github.com/tech/sendico/pkg/db"
// Create project indexable DB (automatically applies org filter)
projectDB := db.NewProjectIndexableDB(repo, logger, organizationRef)
// Reorder project (org filter applied automatically)
err := projectDB.Reorder(ctx, projectID, newIndex, repository.Query())
// Reorder with additional filters (combined with org filter)
additionalFilter := repository.Query().Comparison(repository.Field("state"), builder.Eq, "active")
err := projectDB.Reorder(ctx, projectID, newIndex, additionalFilter)
```
## Examples for Different Types
### Project IndexableDB
```go
createEmpty := func() *model.Project {
return &model.Project{}
}
getIndexable := func(p *model.Project) *model.Indexable {
return &p.Indexable
}
projectDB := indexable.NewIndexableDB(repo, logger, createEmpty, getIndexable)
orgFilter := repository.OrgFilter(organizationRef)
projectDB.Reorder(ctx, projectID, 2, orgFilter)
```
### Status IndexableDB
```go
createEmpty := func() *model.Status {
return &model.Status{}
}
getIndexable := func(s *model.Status) *model.Indexable {
return &s.Indexable
}
statusDB := indexable.NewIndexableDB(repo, logger, createEmpty, getIndexable)
projectFilter := repository.Query().Comparison(repository.Field("projectRef"), builder.Eq, projectRef)
statusDB.Reorder(ctx, statusID, 1, projectFilter)
```
### Task IndexableDB
```go
createEmpty := func() *model.Task {
return &model.Task{}
}
getIndexable := func(t *model.Task) *model.Indexable {
return &t.Indexable
}
taskDB := indexable.NewIndexableDB(repo, logger, createEmpty, getIndexable)
statusFilter := repository.Query().Comparison(repository.Field("statusRef"), builder.Eq, statusRef)
taskDB.Reorder(ctx, taskID, 3, statusFilter)
```
### Priority IndexableDB
```go
createEmpty := func() *model.Priority {
return &model.Priority{}
}
getIndexable := func(p *model.Priority) *model.Indexable {
return &p.Indexable
}
priorityDB := indexable.NewIndexableDB(repo, logger, createEmpty, getIndexable)
orgFilter := repository.OrgFilter(organizationRef)
priorityDB.Reorder(ctx, priorityID, 0, orgFilter)
```
### Global Reordering (No Filter)
```go
createEmpty := func() *model.Project {
return &model.Project{}
}
getIndexable := func(p *model.Project) *model.Indexable {
return &p.Indexable
}
globalDB := indexable.NewIndexableDB(repo, logger, createEmpty, getIndexable)
// Reorders all items globally (empty filter)
globalDB.Reorder(ctx, objectID, 5, repository.Query())
```
## Key Features
### ✅ **Generic Support**
- Works with **any struct** that embeds `model.Indexable`
- Type-safe with compile-time checking
- No hardcoded types
### ✅ **Single Filter Parameter**
- **Simple**: Single `builder.Query` parameter instead of variadic `interface{}`
- **Flexible**: Can incorporate any combination of filters
- **Type-safe**: No runtime type assertions needed
### ✅ **Clean Architecture**
- Interface separated from implementation
- Generic implementation in internal package
- Easy-to-use factories for common types
## How It Works
### Generic Algorithm
1. **Get current index** using type-specific helper function
2. **If no change needed** → return early
3. **Apply filter** to scope affected items
4. **Shift affected items** using `PatchMany` with `$inc`
5. **Update target object** using `Patch` with `$set`
### Type-Safe Implementation
```go
type IndexableDB[T storable.Storable] struct {
repo repository.Repository
logger mlogger.Logger
createEmpty func() T
getIndexable func(T) *model.Indexable
}
// Single filter parameter - clean and simple
func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef primitive.ObjectID, newIndex int, filter builder.Query) error
```
## Benefits
**Generic** - Works with any Indexable struct
**Type Safe** - Compile-time type checking
**Simple** - Single filter parameter instead of variadic interface{}
**Efficient** - Uses patches, not full updates
**Clean** - Interface separated from implementation
That's it! **Generic, type-safe, and simple** reordering for any Indexable struct with a single filter parameter.

View File

@@ -0,0 +1,69 @@
package indexable
import (
"context"
"github.com/tech/sendico/pkg/db/repository"
"github.com/tech/sendico/pkg/db/repository/builder"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Example usage of the generic IndexableDB with different types
// Example 1: Using with Project
func ExampleProjectIndexableDB(repo repository.Repository, logger mlogger.Logger, organizationRef primitive.ObjectID) {
// Define helper functions for Project
createEmpty := func() *model.Project {
return &model.Project{}
}
getIndexable := func(p *model.Project) *model.Indexable {
return &p.Indexable
}
// Create generic IndexableDB for Project
projectDB := NewIndexableDB(repo, logger, createEmpty, getIndexable)
// Use with organization filter
orgFilter := repository.OrgFilter(organizationRef)
projectDB.Reorder(context.Background(), primitive.NewObjectID(), 2, orgFilter)
}
// Example 3: Using with Task
func ExampleTaskIndexableDB(repo repository.Repository, logger mlogger.Logger, statusRef primitive.ObjectID) {
// Define helper functions for Task
createEmpty := func() *model.Task {
return &model.Task{}
}
getIndexable := func(t *model.Task) *model.Indexable {
return &t.Indexable
}
// Create generic IndexableDB for Task
taskDB := NewIndexableDB(repo, logger, createEmpty, getIndexable)
// Use with status filter
statusFilter := repository.Query().Comparison(repository.Field("statusRef"), builder.Eq, statusRef)
taskDB.Reorder(context.Background(), primitive.NewObjectID(), 3, statusFilter)
}
// Example 5: Using without any filter (global reordering)
func ExampleGlobalIndexableDB(repo repository.Repository, logger mlogger.Logger) {
// Define helper functions for any Indexable type
createEmpty := func() *model.Project {
return &model.Project{}
}
getIndexable := func(p *model.Project) *model.Indexable {
return &p.Indexable
}
// Create generic IndexableDB without filters
globalDB := NewIndexableDB(repo, logger, createEmpty, getIndexable)
// Use without any filter - reorders all items globally
globalDB.Reorder(context.Background(), primitive.NewObjectID(), 5, repository.Query())
}

View File

@@ -0,0 +1,122 @@
package indexable
import (
"context"
"github.com/tech/sendico/pkg/db/repository"
"github.com/tech/sendico/pkg/db/repository/builder"
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.uber.org/zap"
)
// IndexableDB implements db.IndexableDB interface with generic support
type IndexableDB[T storable.Storable] struct {
repo repository.Repository
logger mlogger.Logger
createEmpty func() T
getIndexable func(T) *model.Indexable
}
// NewIndexableDB creates a new IndexableDB instance
func NewIndexableDB[T storable.Storable](
repo repository.Repository,
logger mlogger.Logger,
createEmpty func() T,
getIndexable func(T) *model.Indexable,
) *IndexableDB[T] {
return &IndexableDB[T]{
repo: repo,
logger: logger,
createEmpty: createEmpty,
getIndexable: getIndexable,
}
}
// Reorder implements the db.IndexableDB interface with single filter parameter
func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef primitive.ObjectID, newIndex int, filter builder.Query) error {
// Get current object to find its index
obj := db.createEmpty()
err := db.repo.Get(ctx, objectRef, obj)
if err != nil {
db.logger.Error("Failed to get object for reordering",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
zap.Int("new_index", newIndex))
return err
}
// Extract index from the object
indexable := db.getIndexable(obj)
currentIndex := indexable.Index
if currentIndex == newIndex {
db.logger.Debug("No reordering needed - same index",
zap.String("object_ref", objectRef.Hex()),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex))
return nil // No change needed
}
// Simple reordering logic
if currentIndex < newIndex {
// Moving down: shift items between currentIndex+1 and newIndex up by -1
patch := repository.Patch().Inc(repository.IndexField(), -1)
reorderFilter := filter.
And(repository.IndexOpFilter(currentIndex+1, builder.Gte)).
And(repository.IndexOpFilter(newIndex, builder.Lte))
updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch)
if err != nil {
db.logger.Error("Failed to shift objects during reordering (moving down)",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex),
zap.Int("updated_count", updatedCount))
return err
}
db.logger.Debug("Successfully shifted objects (moving down)",
zap.String("object_ref", objectRef.Hex()),
zap.Int("updated_count", updatedCount))
} else {
// Moving up: shift items between newIndex and currentIndex-1 down by +1
patch := repository.Patch().Inc(repository.IndexField(), 1)
reorderFilter := filter.
And(repository.IndexOpFilter(newIndex, builder.Gte)).
And(repository.IndexOpFilter(currentIndex-1, builder.Lte))
updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch)
if err != nil {
db.logger.Error("Failed to shift objects during reordering (moving up)",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex),
zap.Int("updated_count", updatedCount))
return err
}
db.logger.Debug("Successfully shifted objects (moving up)",
zap.String("object_ref", objectRef.Hex()),
zap.Int("updated_count", updatedCount))
}
// Update the target object to new index
patch := repository.Patch().Set(repository.IndexField(), newIndex)
err = db.repo.Patch(ctx, objectRef, patch)
if err != nil {
db.logger.Error("Failed to update target object index",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex))
return err
}
db.logger.Info("Successfully reordered object",
zap.String("object_ref", objectRef.Hex()),
zap.Int("old_index", currentIndex),
zap.Int("new_index", newIndex))
return nil
}

View File

@@ -0,0 +1,314 @@
//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
})
}