package builderimp import ( "testing" "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "git.sendico.io/tech/sxchnge/shared/service" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) func TestNewPipelineImp(t *testing.T) { pipeline := NewPipelineImp() assert.NotNil(t, pipeline) assert.IsType(t, &PipelineImp{}, pipeline) // Build should return empty pipeline initially built := pipeline.Build() assert.NotNil(t, built) assert.Len(t, built, 0) } func TestNewPipelineImpSeedsComposableStages(t *testing.T) { sortStage := bson.D{{Key: "$sort", Value: bson.D{{Key: "createdAt", Value: -1}}}} pipeline := NewPipelineImp(sortStage) result := pipeline.Count(&MockField{build: "total"}) assert.Same(t, pipeline, result) assert.Equal(t, mongo.Pipeline{ sortStage, bson.D{{Key: "$count", Value: "total"}}, }, pipeline.Build()) } func TestPipelineImp_Match(t *testing.T) { pipeline := NewPipelineImp() mockQuery := &MockQuery{ buildPipeline: bson.D{{Key: "$match", Value: bson.D{{Key: "field", Value: "value"}}}}, } result := pipeline.Match(mockQuery) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) assert.Equal(t, bson.D{{Key: "$match", Value: bson.D{{Key: "field", Value: "value"}}}}, built[0]) } func TestPipelineImp_Lookup(t *testing.T) { pipeline := NewPipelineImp() mockLocalField := &MockField{build: "localField"} mockForeignField := &MockField{build: "foreignField"} mockAsField := &MockField{build: "asField"} result := pipeline.Lookup(mservice.Site, mockLocalField, mockForeignField, mockAsField) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{ {Key: string(builder.MKFrom), Value: mservice.Site}, {Key: string(builder.MKLocalField), Value: "localField"}, {Key: string(builder.MKForeignField), Value: "foreignField"}, {Key: string(builder.MKAs), Value: "asField"}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_LookupWithPipeline_WithoutLet(t *testing.T) { pipeline := NewPipelineImp() mockNestedPipeline := &MockPipeline{ build: mongo.Pipeline{bson.D{{Key: "$match", Value: bson.D{{Key: "nested", Value: true}}}}}, } mockAsField := &MockField{build: "asField"} result := pipeline.LookupWithPipeline(mservice.Site, mockNestedPipeline, mockAsField, nil) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{ {Key: string(builder.MKFrom), Value: mservice.Site}, {Key: string(builder.MKPipeline), Value: mockNestedPipeline.build}, {Key: string(builder.MKAs), Value: "asField"}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_LookupWithPipeline_WithLet(t *testing.T) { pipeline := NewPipelineImp() mockNestedPipeline := &MockPipeline{ build: mongo.Pipeline{bson.D{{Key: "$match", Value: bson.D{{Key: "nested", Value: true}}}}}, } mockAsField := &MockField{build: "asField"} mockLetField := &MockField{build: "$_id"} letVars := map[string]builder.Field{ "projRef": mockLetField, } result := pipeline.LookupWithPipeline(mservice.Site, mockNestedPipeline, mockAsField, &letVars) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{ {Key: string(builder.MKFrom), Value: mservice.Site}, {Key: string(builder.MKPipeline), Value: mockNestedPipeline.build}, {Key: string(builder.MKAs), Value: "asField"}, {Key: string(builder.MKLet), Value: bson.D{{Key: "projRef", Value: "$_id"}}}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_LookupWithPipeline_WithEmptyLet(t *testing.T) { pipeline := NewPipelineImp() mockNestedPipeline := &MockPipeline{ build: mongo.Pipeline{bson.D{{Key: "$match", Value: bson.D{{Key: "nested", Value: true}}}}}, } mockAsField := &MockField{build: "asField"} emptyLetVars := map[string]builder.Field{} pipeline.LookupWithPipeline(mservice.Site, mockNestedPipeline, mockAsField, &emptyLetVars) built := pipeline.Build() assert.Len(t, built, 1) // Should not include let field when empty expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{ {Key: string(builder.MKFrom), Value: mservice.Site}, {Key: string(builder.MKPipeline), Value: mockNestedPipeline.build}, {Key: string(builder.MKAs), Value: "asField"}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Unwind_Simple(t *testing.T) { pipeline := NewPipelineImp() mockField := &MockField{build: "$array"} result := pipeline.Unwind(mockField) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Unwind), Value: "$array"}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Unwind_WithPreserveNullAndEmptyArrays(t *testing.T) { pipeline := NewPipelineImp() mockField := &MockField{build: "$array"} // Mock the UnwindOption function preserveOpt := func(opts *builder.UnwindOpts) { opts.PreserveNullAndEmptyArrays = true } pipeline.Unwind(mockField, preserveOpt) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Unwind), Value: bson.D{ {Key: string(builder.MKPath), Value: "$array"}, {Key: string(builder.MKPreserveNullAndEmptyArrays), Value: true}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Unwind_WithIncludeArrayIndex(t *testing.T) { pipeline := NewPipelineImp() mockField := &MockField{build: "$array"} // Mock the UnwindOption function indexOpt := func(opts *builder.UnwindOpts) { opts.IncludeArrayIndex = "arrayIndex" } pipeline.Unwind(mockField, indexOpt) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Unwind), Value: bson.D{ {Key: string(builder.MKPath), Value: "$array"}, {Key: string(builder.MKIncludeArrayIndex), Value: "arrayIndex"}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Unwind_WithBothOptions(t *testing.T) { pipeline := NewPipelineImp() mockField := &MockField{build: "$array"} // Mock the UnwindOption functions preserveOpt := func(opts *builder.UnwindOpts) { opts.PreserveNullAndEmptyArrays = true } indexOpt := func(opts *builder.UnwindOpts) { opts.IncludeArrayIndex = "arrayIndex" } pipeline.Unwind(mockField, preserveOpt, indexOpt) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Unwind), Value: bson.D{ {Key: string(builder.MKPath), Value: "$array"}, {Key: string(builder.MKPreserveNullAndEmptyArrays), Value: true}, {Key: string(builder.MKIncludeArrayIndex), Value: "arrayIndex"}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Count(t *testing.T) { pipeline := NewPipelineImp() mockField := &MockField{build: "totalCount"} result := pipeline.Count(mockField) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Count), Value: "totalCount"}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Group(t *testing.T) { pipeline := NewPipelineImp() mockAlias := &MockAlias{ build: bson.D{{Key: "_id", Value: "$field"}}, field: &MockField{build: "_id"}, } mockAccumulator := &MockGroupAccumulator{ build: bson.D{{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}}}, } result := pipeline.Group(mockAlias, mockAccumulator) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Group), Value: bson.D{ {Key: "_id", Value: "$field"}, {Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Group_MultipleAccumulators(t *testing.T) { pipeline := NewPipelineImp() mockAlias := &MockAlias{ build: bson.D{{Key: "_id", Value: "$field"}}, field: &MockField{build: "_id"}, } mockAccumulator1 := &MockGroupAccumulator{ build: bson.D{{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}}}, } mockAccumulator2 := &MockGroupAccumulator{ build: bson.D{{Key: "total", Value: bson.D{{Key: "$sum", Value: "$amount"}}}}, } pipeline.Group(mockAlias, mockAccumulator1, mockAccumulator2) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Group), Value: bson.D{ {Key: "_id", Value: "$field"}, {Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}}, {Key: "total", Value: bson.D{{Key: "$sum", Value: "$amount"}}}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Project(t *testing.T) { pipeline := NewPipelineImp() mockProjection := &MockProjection{ build: bson.D{{Key: "field1", Value: 1}}, } result := pipeline.Project(mockProjection) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Project), Value: bson.D{ {Key: "field1", Value: 1}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_Project_MultipleProjections(t *testing.T) { pipeline := NewPipelineImp() mockProjection1 := &MockProjection{ build: bson.D{{Key: "field1", Value: 1}}, } mockProjection2 := &MockProjection{ build: bson.D{{Key: "field2", Value: 0}}, } pipeline.Project(mockProjection1, mockProjection2) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.Project), Value: bson.D{ {Key: "field1", Value: 1}, {Key: "field2", Value: 0}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_ChainedOperations(t *testing.T) { pipeline := NewPipelineImp() // Create mocks mockQuery := &MockQuery{ buildPipeline: bson.D{{Key: "$match", Value: bson.D{{Key: "status", Value: "active"}}}}, } mockLocalField := &MockField{build: "userId"} mockForeignField := &MockField{build: "_id"} mockAsField := &MockField{build: "user"} mockUnwindField := &MockField{build: "$user"} mockProjection := &MockProjection{ build: bson.D{{Key: "name", Value: "$user.name"}}, } // Chain operations result := pipeline. Match(mockQuery). Lookup(mservice.Accounts, mockLocalField, mockForeignField, mockAsField). Unwind(mockUnwindField). Project(mockProjection) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 4) // Verify each stage assert.Equal(t, bson.D{{Key: "$match", Value: bson.D{{Key: "status", Value: "active"}}}}, built[0]) expectedLookup := bson.D{{Key: string(builder.Lookup), Value: bson.D{ {Key: string(builder.MKFrom), Value: mservice.Accounts}, {Key: string(builder.MKLocalField), Value: "userId"}, {Key: string(builder.MKForeignField), Value: "_id"}, {Key: string(builder.MKAs), Value: "user"}, }}} assert.Equal(t, expectedLookup, built[1]) assert.Equal(t, bson.D{{Key: string(builder.Unwind), Value: "$user"}}, built[2]) expectedProject := bson.D{{Key: string(builder.Project), Value: bson.D{ {Key: "name", Value: "$user.name"}, }}} assert.Equal(t, expectedProject, built[3]) } func TestNewUnwindOpts(t *testing.T) { t.Run("NoOptions", func(t *testing.T) { opts := NewUnwindOpts() assert.NotNil(t, opts) assert.False(t, opts.PreserveNullAndEmptyArrays) assert.Empty(t, opts.IncludeArrayIndex) }) t.Run("WithPreserveOption", func(t *testing.T) { preserveOpt := func(opts *builder.UnwindOpts) { opts.PreserveNullAndEmptyArrays = true } opts := NewUnwindOpts(preserveOpt) assert.True(t, opts.PreserveNullAndEmptyArrays) assert.Empty(t, opts.IncludeArrayIndex) }) t.Run("WithIndexOption", func(t *testing.T) { indexOpt := func(opts *builder.UnwindOpts) { opts.IncludeArrayIndex = "index" } opts := NewUnwindOpts(indexOpt) assert.False(t, opts.PreserveNullAndEmptyArrays) assert.Equal(t, "index", opts.IncludeArrayIndex) }) t.Run("WithBothOptions", func(t *testing.T) { preserveOpt := func(opts *builder.UnwindOpts) { opts.PreserveNullAndEmptyArrays = true } indexOpt := func(opts *builder.UnwindOpts) { opts.IncludeArrayIndex = "index" } opts := NewUnwindOpts(preserveOpt, indexOpt) assert.True(t, opts.PreserveNullAndEmptyArrays) assert.Equal(t, "index", opts.IncludeArrayIndex) }) } // Mock implementations for testing type MockQuery struct { buildPipeline bson.D } func (m *MockQuery) And(filters ...builder.Query) builder.Query { return m } func (m *MockQuery) Or(filters ...builder.Query) builder.Query { return m } func (m *MockQuery) Filter(field builder.Field, value any) builder.Query { return m } func (m *MockQuery) Expression(value builder.Expression) builder.Query { return m } func (m *MockQuery) Comparison(field builder.Field, operator builder.MongoOperation, value any) builder.Query { return m } func (m *MockQuery) RegEx(field builder.Field, pattern, options string) builder.Query { return m } func (m *MockQuery) In(field builder.Field, values ...any) builder.Query { return m } func (m *MockQuery) NotIn(field builder.Field, values ...any) builder.Query { return m } func (m *MockQuery) Sort(field builder.Field, ascending bool) builder.Query { return m } func (m *MockQuery) Limit(limit *int64) builder.Query { return m } func (m *MockQuery) Offset(offset *int64) builder.Query { return m } func (m *MockQuery) Archived(isArchived *bool) builder.Query { return m } func (m *MockQuery) BuildPipeline() bson.D { return m.buildPipeline } func (m *MockQuery) BuildQuery() bson.D { return bson.D{} } func (m *MockQuery) BuildOptions() *options.FindOptionsBuilder { return &options.FindOptionsBuilder{} } type MockField struct { build string } func (m *MockField) Dot(field string) builder.Field { return &MockField{build: m.build + "." + field} } func (m *MockField) CopyWith(field string) builder.Field { return &MockField{build: field} } func (m *MockField) Build() string { return m.build } type MockPipeline struct { build mongo.Pipeline } func (m *MockPipeline) Match(filter builder.Query) builder.Pipeline { return m } func (m *MockPipeline) Lookup(from mservice.Type, localField, foreignField, as builder.Field) builder.Pipeline { return m } func (m *MockPipeline) LookupWithPipeline(from mservice.Type, pipeline builder.Pipeline, as builder.Field, let *map[string]builder.Field) builder.Pipeline { return m } func (m *MockPipeline) Unwind(path builder.Field, opts ...UnwindOption) builder.Pipeline { return m } func (m *MockPipeline) Count(field builder.Field) builder.Pipeline { return m } func (m *MockPipeline) Group(groupBy builder.Alias, accumulators ...builder.GroupAccumulator) builder.Pipeline { return m } func (m *MockPipeline) Project(projections ...builder.Projection) builder.Pipeline { return m } func (m *MockPipeline) ReplaceRoot(newRoot builder.Expression) builder.Pipeline { return m } func (m *MockPipeline) Build() mongo.Pipeline { return m.build } type MockAlias struct { build bson.D field builder.Field } func (m *MockAlias) Field() builder.Field { return m.field } func (m *MockAlias) Build() bson.D { return m.build } type MockGroupAccumulator struct { build bson.D } func (m *MockGroupAccumulator) Build() bson.D { return m.build } type MockProjection struct { build bson.D } func (m *MockProjection) Build() bson.D { return m.build } func TestPipelineImp_ReplaceRoot(t *testing.T) { pipeline := NewPipelineImp() mockExpr := &MockExpression{build: "$newRoot"} result := pipeline.ReplaceRoot(mockExpr) // Should return self for chaining assert.Same(t, pipeline, result) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.ReplaceRoot), Value: bson.D{ {Key: string(builder.MKNewRoot), Value: "$newRoot"}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_ReplaceRoot_WithNestedField(t *testing.T) { pipeline := NewPipelineImp() mockExpr := &MockExpression{build: "$document.data"} pipeline.ReplaceRoot(mockExpr) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.ReplaceRoot), Value: bson.D{ {Key: string(builder.MKNewRoot), Value: "$document.data"}, }}} assert.Equal(t, expected, built[0]) } func TestPipelineImp_ReplaceRoot_WithExpression(t *testing.T) { pipeline := NewPipelineImp() // Mock a complex expression like { $mergeObjects: [...] } mockExpr := &MockExpression{build: bson.D{{Key: "$mergeObjects", Value: bson.A{"$field1", "$field2"}}}} pipeline.ReplaceRoot(mockExpr) built := pipeline.Build() assert.Len(t, built, 1) expected := bson.D{{Key: string(builder.ReplaceRoot), Value: bson.D{ {Key: string(builder.MKNewRoot), Value: bson.D{{Key: "$mergeObjects", Value: bson.A{"$field1", "$field2"}}}}, }}} assert.Equal(t, expected, built[0]) } type MockExpression struct { build any } func (m *MockExpression) Build() any { return m.build }