mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-22 17:31:03 +01:00
461 lines
15 KiB
Go
461 lines
15 KiB
Go
package service_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/crawlab-team/crawlab/core/models/models"
|
|
"github.com/crawlab-team/crawlab/core/mongo"
|
|
|
|
"github.com/crawlab-team/crawlab/core/models/service"
|
|
"github.com/spf13/viper"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
type TestModel struct {
|
|
any `collection:"testmodels"`
|
|
Id primitive.ObjectID `bson:"_id,omitempty"`
|
|
models.BaseModel `bson:",inline"`
|
|
Name string `bson:"name"`
|
|
}
|
|
|
|
// TestModelBase represents a base model for testing embedded struct scenarios
|
|
type TestModelBase struct {
|
|
any `collection:"testbase"`
|
|
Id primitive.ObjectID `bson:"_id,omitempty"`
|
|
models.BaseModel `bson:",inline"`
|
|
BaseName string `bson:"base_name"`
|
|
}
|
|
|
|
// TestModelDTO represents a DTO that embeds TestModelBase (similar to SpiderDTO pattern)
|
|
type TestModelDTO struct {
|
|
TestModelBase `json:",inline" bson:",inline"`
|
|
ExtraField string `json:"extra_field,omitempty" bson:"extra_field,omitempty"`
|
|
}
|
|
|
|
// TestModelNestedDTO represents a deeply nested DTO structure
|
|
type TestModelNestedDTO struct {
|
|
TestModelDTO `json:",inline" bson:",inline"`
|
|
NestedField string `json:"nested_field,omitempty" bson:"nested_field,omitempty"`
|
|
}
|
|
|
|
// TestModelNonStandardPosition tests Priority 3: collection tag not on first field
|
|
type TestModelNonStandardPosition struct {
|
|
SomeField string `bson:"some_field"`
|
|
AnotherField string `bson:"another_field" collection:"non_standard_collection"`
|
|
models.BaseModel `bson:",inline"`
|
|
}
|
|
|
|
// TestModelEmbeddedInSecondPosition tests Priority 3: embedded struct with collection tag in non-first position
|
|
type TestModelWithCollectionTag struct {
|
|
any `collection:"embedded_collection"`
|
|
models.BaseModel `bson:",inline"`
|
|
EmbeddedName string `bson:"embedded_name"`
|
|
}
|
|
|
|
type TestModelEmbeddedInSecondPosition struct {
|
|
FirstField string `bson:"first_field"`
|
|
TestModelWithCollectionTag `bson:",inline"` // Embedded struct in second position
|
|
ThirdField string `bson:"third_field"`
|
|
}
|
|
|
|
func setupTestDB() {
|
|
viper.Set("mongo.db", "testdb")
|
|
}
|
|
|
|
func teardownTestDB() {
|
|
db := mongo.GetMongoDb("testdb")
|
|
err := db.Drop(context.Background())
|
|
if err != nil {
|
|
log.Errorf("dropping test db error: %v", err)
|
|
return
|
|
}
|
|
log.Infof("dropped test db")
|
|
}
|
|
|
|
func TestModelService(t *testing.T) {
|
|
setupTestDB()
|
|
defer teardownTestDB()
|
|
|
|
t.Run("GetById", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModel := TestModel{Name: "GetById Test"}
|
|
|
|
id, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := svc.GetById(id)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, testModel.Name, result.Name)
|
|
})
|
|
|
|
t.Run("GetOne", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModel := TestModel{Name: "GetOne Test"}
|
|
|
|
_, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := svc.GetOne(bson.M{"name": "GetOne Test"}, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, testModel.Name, result.Name)
|
|
})
|
|
|
|
t.Run("GetMany", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModels := []TestModel{
|
|
{Name: "GetMany Test 1"},
|
|
{Name: "GetMany Test 2"},
|
|
}
|
|
|
|
_, err := svc.InsertMany(testModels)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
results, err := svc.GetMany(bson.M{"name": bson.M{"$regex": "^GetMany Test"}}, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, 2, len(results))
|
|
})
|
|
|
|
t.Run("InsertOne", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModel := TestModel{Name: "InsertOne Test"}
|
|
|
|
id, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
assert.NotEqual(t, primitive.NilObjectID, id)
|
|
})
|
|
|
|
t.Run("InsertMany", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModels := []TestModel{
|
|
{Name: "InsertMany Test 1"},
|
|
{Name: "InsertMany Test 2"},
|
|
}
|
|
|
|
ids, err := svc.InsertMany(testModels)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, 2, len(ids))
|
|
})
|
|
|
|
t.Run("UpdateById", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModel := TestModel{Name: "UpdateById Test"}
|
|
|
|
id, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
update := bson.M{"$set": bson.M{"name": "UpdateById Test New Name"}}
|
|
err = svc.UpdateById(id, update)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := svc.GetById(id)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "UpdateById Test New Name", result.Name)
|
|
})
|
|
|
|
t.Run("UpdateOne", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModel := TestModel{Name: "UpdateOne Test"}
|
|
|
|
_, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
update := bson.M{"$set": bson.M{"name": "UpdateOne Test New Name"}}
|
|
err = svc.UpdateOne(bson.M{"name": "UpdateOne Test"}, update)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := svc.GetOne(bson.M{"name": "UpdateOne Test New Name"}, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "UpdateOne Test New Name", result.Name)
|
|
})
|
|
|
|
t.Run("UpdateMany", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModels := []TestModel{
|
|
{Name: "UpdateMany Test 1"},
|
|
{Name: "UpdateMany Test 2"},
|
|
}
|
|
|
|
_, err := svc.InsertMany(testModels)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
update := bson.M{"$set": bson.M{"name": "UpdateMany Test New Name"}}
|
|
err = svc.UpdateMany(bson.M{"name": bson.M{"$regex": "^UpdateMany Test"}}, update)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
results, err := svc.GetMany(bson.M{"name": "UpdateMany Test New Name"}, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, 2, len(results))
|
|
})
|
|
|
|
t.Run("DeleteById", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModel := TestModel{Name: "DeleteById Test"}
|
|
|
|
id, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
err = svc.DeleteById(id)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := svc.GetById(id)
|
|
assert.NotNil(t, err)
|
|
assert.Nil(t, result)
|
|
})
|
|
|
|
t.Run("DeleteOne", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModel := TestModel{Name: "DeleteOne Test"}
|
|
|
|
_, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
err = svc.DeleteOne(bson.M{"name": "DeleteOne Test"})
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := svc.GetOne(bson.M{"name": "DeleteOne Test"}, nil)
|
|
assert.NotNil(t, err)
|
|
assert.Nil(t, result)
|
|
})
|
|
|
|
t.Run("DeleteMany", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModels := []TestModel{
|
|
{Name: "DeleteMany Test 1"},
|
|
{Name: "DeleteMany Test 2"},
|
|
}
|
|
|
|
_, err := svc.InsertMany(testModels)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
err = svc.DeleteMany(bson.M{"name": bson.M{"$regex": "^DeleteMany Test"}})
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
results, err := svc.GetMany(bson.M{"name": bson.M{"$regex": "^DeleteMany Test"}}, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, 0, len(results))
|
|
})
|
|
|
|
t.Run("Count", func(t *testing.T) {
|
|
svc := service.NewModelService[TestModel]()
|
|
testModels := []TestModel{
|
|
{Name: "Count Test 1"},
|
|
{Name: "Count Test 2"},
|
|
}
|
|
|
|
_, err := svc.InsertMany(testModels)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
total, err := svc.Count(bson.M{"name": bson.M{"$regex": "^Count Test"}})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, 2, total)
|
|
})
|
|
}
|
|
|
|
func TestEmbeddedStructHandling(t *testing.T) {
|
|
setupTestDB()
|
|
defer teardownTestDB()
|
|
|
|
t.Run("GetCollectionName for embedded struct DTO", func(t *testing.T) {
|
|
// Test that GetCollectionName correctly handles embedded struct (DTO pattern)
|
|
collectionName := service.GetCollectionName[TestModelDTO]()
|
|
assert.Equal(t, "testbase", collectionName, "Should extract collection name from embedded struct")
|
|
})
|
|
|
|
t.Run("GetCollectionName for deeply nested embedded struct", func(t *testing.T) {
|
|
// Test deeply nested embedded struct
|
|
collectionName := service.GetCollectionName[TestModelNestedDTO]()
|
|
assert.Equal(t, "testbase", collectionName, "Should extract collection name from deeply nested embedded struct")
|
|
})
|
|
|
|
t.Run("GetCollectionName for regular struct", func(t *testing.T) {
|
|
// Test that regular struct (non-embedded) still works
|
|
collectionName := service.GetCollectionName[TestModel]()
|
|
assert.Equal(t, "testmodels", collectionName, "Should get collection name from regular struct")
|
|
|
|
collectionName = service.GetCollectionName[TestModelBase]()
|
|
assert.Equal(t, "testbase", collectionName, "Should get collection name from base struct")
|
|
})
|
|
|
|
t.Run("GetCollectionName follows Crawlab pattern", func(t *testing.T) {
|
|
// Test that the function correctly identifies the Crawlab pattern:
|
|
// First field is `any` with collection tag (no field name)
|
|
// This matches the actual pattern used in Spider, Node, Project, etc.
|
|
|
|
// TestModel follows: any `collection:"testmodels"`
|
|
collectionName := service.GetCollectionName[TestModel]()
|
|
assert.Equal(t, "testmodels", collectionName, "Should extract collection from first anonymous any field")
|
|
|
|
// TestModelBase follows: any `collection:"testbase"`
|
|
collectionName = service.GetCollectionName[TestModelBase]()
|
|
assert.Equal(t, "testbase", collectionName, "Should extract collection from first anonymous any field")
|
|
})
|
|
|
|
t.Run("ModelService with embedded struct DTO", func(t *testing.T) {
|
|
// Test that ModelService works correctly with embedded struct DTO
|
|
svc := service.NewModelService[TestModelDTO]()
|
|
assert.NotNil(t, svc, "Should create service for DTO")
|
|
|
|
// Verify the service uses the correct collection
|
|
col := svc.GetCol()
|
|
assert.NotNil(t, col, "Should have collection")
|
|
// The collection name should be extracted from the embedded struct
|
|
assert.Equal(t, "testbase", col.GetName(), "Should use collection name from embedded struct")
|
|
})
|
|
|
|
t.Run("CRUD operations with embedded struct DTO", func(t *testing.T) {
|
|
// Test actual CRUD operations with DTO
|
|
svc := service.NewModelService[TestModelDTO]()
|
|
|
|
testDTO := TestModelDTO{
|
|
TestModelBase: TestModelBase{BaseName: "Base Test"},
|
|
ExtraField: "Extra Test",
|
|
}
|
|
|
|
// Insert
|
|
id, err := svc.InsertOne(testDTO)
|
|
require.Nil(t, err)
|
|
assert.NotEqual(t, primitive.NilObjectID, id)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Get by ID
|
|
result, err := svc.GetById(id)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "Base Test", result.BaseName)
|
|
assert.Equal(t, "Extra Test", result.ExtraField)
|
|
|
|
// Update
|
|
update := bson.M{"$set": bson.M{"extra_field": "Updated Extra"}}
|
|
err = svc.UpdateById(id, update)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Verify update
|
|
updated, err := svc.GetById(id)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "Updated Extra", updated.ExtraField)
|
|
|
|
// Delete
|
|
err = svc.DeleteById(id)
|
|
require.Nil(t, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Verify deletion
|
|
deleted, err := svc.GetById(id)
|
|
assert.NotNil(t, err)
|
|
assert.Nil(t, deleted)
|
|
})
|
|
|
|
t.Run("GetCollection function with embedded struct", func(t *testing.T) {
|
|
// Test GetCollection function directly
|
|
col := service.GetCollection[TestModelDTO]()
|
|
assert.NotNil(t, col, "Should get collection for DTO")
|
|
assert.Equal(t, "testbase", col.GetName(), "Should use collection name from embedded struct")
|
|
|
|
// Test with regular struct
|
|
col = service.GetCollection[TestModel]()
|
|
assert.NotNil(t, col, "Should get collection for regular struct")
|
|
assert.Equal(t, "testmodels", col.GetName(), "Should use collection name from struct")
|
|
})
|
|
|
|
t.Run("Multiple DTO instances share same collection", func(t *testing.T) {
|
|
// Test that both base model and DTO use the same collection
|
|
baseSvc := service.NewModelService[TestModelBase]()
|
|
dtoSvc := service.NewModelService[TestModelDTO]()
|
|
|
|
baseCol := baseSvc.GetCol()
|
|
dtoCol := dtoSvc.GetCol()
|
|
|
|
assert.Equal(t, baseCol.GetName(), dtoCol.GetName(), "Base and DTO should use same collection")
|
|
assert.Equal(t, "testbase", baseCol.GetName(), "Should use correct collection name")
|
|
})
|
|
|
|
t.Run("Priority 3: Collection tag on non-first field", func(t *testing.T) {
|
|
// Test Priority 3: fallback to check remaining fields for collection tag
|
|
// TestModelNonStandardPosition has collection tag on second field
|
|
collectionName := service.GetCollectionName[TestModelNonStandardPosition]()
|
|
assert.Equal(t, "non_standard_collection", collectionName, "Should find collection tag on non-first field")
|
|
})
|
|
|
|
t.Run("Priority 3: Embedded struct in non-first position", func(t *testing.T) {
|
|
// Test Priority 3: embedded struct with collection tag in non-first position
|
|
// TestModelEmbeddedInSecondPosition has embedded struct as second field
|
|
collectionName := service.GetCollectionName[TestModelEmbeddedInSecondPosition]()
|
|
assert.Equal(t, "embedded_collection", collectionName, "Should find collection tag in embedded struct at non-first position")
|
|
})
|
|
|
|
t.Run("Priority 3: CRUD operations with non-standard position", func(t *testing.T) {
|
|
// Test that ModelService works with non-standard collection tag position
|
|
svc := service.NewModelService[TestModelNonStandardPosition]()
|
|
|
|
testModel := TestModelNonStandardPosition{
|
|
SomeField: "Test Field",
|
|
AnotherField: "Another Field",
|
|
}
|
|
|
|
// Insert
|
|
id, err := svc.InsertOne(testModel)
|
|
require.Nil(t, err)
|
|
assert.NotEqual(t, primitive.NilObjectID, id)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Get by ID
|
|
result, err := svc.GetById(id)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "Test Field", result.SomeField)
|
|
assert.Equal(t, "Another Field", result.AnotherField)
|
|
|
|
// Verify the service uses the correct collection
|
|
col := svc.GetCol()
|
|
assert.Equal(t, "non_standard_collection", col.GetName(), "Should use collection from non-first field")
|
|
|
|
// Delete
|
|
err = svc.DeleteById(id)
|
|
require.Nil(t, err)
|
|
})
|
|
|
|
t.Run("Priority order verification", func(t *testing.T) {
|
|
// This test verifies that priorities work as expected:
|
|
// Priority 1: First field collection tag
|
|
// Priority 2: First field embedded struct
|
|
// Priority 3: Other fields
|
|
|
|
// TestModel has collection tag on first field (Priority 1)
|
|
collectionName := service.GetCollectionName[TestModel]()
|
|
assert.Equal(t, "testmodels", collectionName, "Priority 1: First field collection tag")
|
|
|
|
// TestModelDTO has embedded struct as first field (Priority 2)
|
|
collectionName = service.GetCollectionName[TestModelDTO]()
|
|
assert.Equal(t, "testbase", collectionName, "Priority 2: First field embedded struct")
|
|
|
|
// TestModelNonStandardPosition has collection tag on second field (Priority 3)
|
|
collectionName = service.GetCollectionName[TestModelNonStandardPosition]()
|
|
assert.Equal(t, "non_standard_collection", collectionName, "Priority 3: Non-first field collection tag")
|
|
|
|
// TestModelEmbeddedInSecondPosition has embedded struct in second position (Priority 3)
|
|
collectionName = service.GetCollectionName[TestModelEmbeddedInSecondPosition]()
|
|
assert.Equal(t, "embedded_collection", collectionName, "Priority 3: Embedded struct in non-first position")
|
|
})
|
|
}
|