Multiple implementations of the same back-end application. The aim is to provide quick, side-by-side comparisons of different technologies (languages, frameworks, libraries) while preserving consistent business logic across all implementations.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

904 lines
27 KiB

package unit
import (
"testing"
"time"
"autostore/internal/domain/specifications"
)
// Test data constants
const (
TEST_STRING = "test-value"
TEST_INT = 42
TEST_FLOAT = 3.14
TEST_DATE = "2023-01-15 10:30:00"
TEST_DATE_2 = "2023-02-15 10:30:00"
INVALID_DATE = "invalid-date"
)
// TestObject is a simple struct for testing specifications
type TestObject struct {
name string
age int
price float64
status string
score int
role string
department string
active bool
optionalField interface{}
dateField string
createdAt time.Time
expirationDate time.Time
}
// NewTestObject creates a new TestObject with the given field values
func NewTestObject(fields map[string]interface{}) *TestObject {
obj := &TestObject{}
if val, ok := fields["name"]; ok {
obj.name = val.(string)
}
if val, ok := fields["age"]; ok {
obj.age = val.(int)
}
if val, ok := fields["price"]; ok {
obj.price = val.(float64)
}
if val, ok := fields["status"]; ok {
obj.status = val.(string)
}
if val, ok := fields["score"]; ok {
obj.score = val.(int)
}
if val, ok := fields["role"]; ok {
obj.role = val.(string)
}
if val, ok := fields["department"]; ok {
obj.department = val.(string)
}
if val, ok := fields["active"]; ok {
obj.active = val.(bool)
}
if val, ok := fields["optionalField"]; ok {
obj.optionalField = val
}
if val, ok := fields["dateField"]; ok {
obj.dateField = val.(string)
}
if val, ok := fields["createdAt"]; ok {
switch v := val.(type) {
case time.Time:
obj.createdAt = v
case string:
obj.createdAt, _ = time.Parse(dateFormat, v)
}
}
if val, ok := fields["expirationDate"]; ok {
switch v := val.(type) {
case time.Time:
obj.expirationDate = v
case string:
obj.expirationDate, _ = time.Parse(dateFormat, v)
}
}
return obj
}
// Getter methods for TestObject
func (o *TestObject) GetName() string {
return o.name
}
func (o *TestObject) GetAge() int {
return o.age
}
func (o *TestObject) GetPrice() float64 {
return o.price
}
func (o *TestObject) GetStatus() string {
return o.status
}
func (o *TestObject) GetScore() int {
return o.score
}
func (o *TestObject) GetRole() string {
return o.role
}
func (o *TestObject) GetDepartment() string {
return o.department
}
func (o *TestObject) GetActive() bool {
return o.active
}
func (o *TestObject) GetOptionalField() interface{} {
return o.optionalField
}
func (o *TestObject) GetDateField() string {
return o.dateField
}
func (o *TestObject) GetCreatedAt() time.Time {
return o.createdAt
}
func (o *TestObject) GetExpirationDate() time.Time {
return o.expirationDate
}
// EQ Operator Tests
func TestWhenUsingEqWithStringThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("name", TEST_STRING))
matchingObject := NewTestObject(map[string]interface{}{"name": TEST_STRING})
nonMatchingObject := NewTestObject(map[string]interface{}{"name": "different"})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingEqWithIntegerThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("age", TEST_INT))
matchingObject := NewTestObject(map[string]interface{}{"age": TEST_INT})
nonMatchingObject := NewTestObject(map[string]interface{}{"age": 100})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingEqWithFloatThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("price", TEST_FLOAT))
matchingObject := NewTestObject(map[string]interface{}{"price": TEST_FLOAT})
nonMatchingObject := NewTestObject(map[string]interface{}{"price": 1.0})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
// NEQ Operator Tests
func TestWhenUsingNeqThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Neq("status", "inactive"))
matchingObject := NewTestObject(map[string]interface{}{"status": "active"})
nonMatchingObject := NewTestObject(map[string]interface{}{"status": "inactive"})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
// Comparison Operators Tests
func TestWhenUsingGtThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Gt("score", 80))
matchingObject := NewTestObject(map[string]interface{}{"score": 90})
nonMatchingObject := NewTestObject(map[string]interface{}{"score": 70})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingGteThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Gte("score", 80))
matchingObject1 := NewTestObject(map[string]interface{}{"score": 80})
matchingObject2 := NewTestObject(map[string]interface{}{"score": 90})
nonMatchingObject := NewTestObject(map[string]interface{}{"score": 70})
// When
matchResult1 := spec.IsSatisfiedBy(matchingObject1)
matchResult2 := spec.IsSatisfiedBy(matchingObject2)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult1 {
t.Error("Expected matching object 1 to satisfy the specification")
}
if !matchResult2 {
t.Error("Expected matching object 2 to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingLtThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Lt("score", 80))
matchingObject := NewTestObject(map[string]interface{}{"score": 70})
nonMatchingObject := NewTestObject(map[string]interface{}{"score": 90})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingLteThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Lte("score", 80))
matchingObject1 := NewTestObject(map[string]interface{}{"score": 80})
matchingObject2 := NewTestObject(map[string]interface{}{"score": 70})
nonMatchingObject := NewTestObject(map[string]interface{}{"score": 90})
// When
matchResult1 := spec.IsSatisfiedBy(matchingObject1)
matchResult2 := spec.IsSatisfiedBy(matchingObject2)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult1 {
t.Error("Expected matching object 1 to satisfy the specification")
}
if !matchResult2 {
t.Error("Expected matching object 2 to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
// IN Operator Tests
func TestWhenUsingInThenMatchesCorrectly(t *testing.T) {
// Given
validValues := []interface{}{"admin", "moderator", "editor"}
spec := specifications.NewSimpleSpecification[*TestObject](specifications.In("role", validValues))
matchingObject := NewTestObject(map[string]interface{}{"role": "admin"})
nonMatchingObject := NewTestObject(map[string]interface{}{"role": "user"})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingInWithEmptyArrayThenNeverMatches(t *testing.T) {
// Given
validValues := []interface{}{}
spec := specifications.NewSimpleSpecification[*TestObject](specifications.In("role", validValues))
testObject := NewTestObject(map[string]interface{}{"role": "admin"})
// When
result := spec.IsSatisfiedBy(testObject)
// Then
if result {
t.Error("Expected object to not satisfy the specification with empty array")
}
}
// NOT IN Operator Tests
func TestWhenUsingNotInThenMatchesCorrectly(t *testing.T) {
// Given
invalidValues := []interface{}{"banned", "suspended"}
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Nin("status", invalidValues))
matchingObject := NewTestObject(map[string]interface{}{"status": "active"})
nonMatchingObject := NewTestObject(map[string]interface{}{"status": "banned"})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
// DateTime Tests
func TestWhenUsingDateTimeComparisonWithStringsThenMatchesCorrectly(t *testing.T) {
// Given
testDate2, _ := time.Parse(dateFormat, TEST_DATE_2)
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Lt("createdAt", testDate2))
matchingObject := NewTestObject(map[string]interface{}{"createdAt": TEST_DATE})
nonMatchingObject := NewTestObject(map[string]interface{}{"createdAt": TEST_DATE_2})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingDateTimeComparisonWithTimeObjectsThenMatchesCorrectly(t *testing.T) {
// Given
testDate, _ := time.Parse(dateFormat, TEST_DATE)
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Lte("expirationDate", testDate))
matchingDate, _ := time.Parse(dateFormat, TEST_DATE)
nonMatchingDate, _ := time.Parse(dateFormat, TEST_DATE_2)
matchingObject := NewTestObject(map[string]interface{}{"expirationDate": matchingDate})
nonMatchingObject := NewTestObject(map[string]interface{}{"expirationDate": nonMatchingDate})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
// AND Group Tests
func TestWhenUsingAndGroupThenMatchesOnlyWhenAllConditionsMet(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.And(
specifications.Eq("status", "active"),
specifications.Gte("score", 80),
specifications.In("role", []interface{}{"admin", "moderator"}),
))
matchingObject := NewTestObject(map[string]interface{}{
"status": "active",
"score": 85,
"role": "admin",
})
nonMatchingObject1 := NewTestObject(map[string]interface{}{
"status": "inactive",
"score": 85,
"role": "admin",
})
nonMatchingObject2 := NewTestObject(map[string]interface{}{
"status": "active",
"score": 70,
"role": "admin",
})
nonMatchingObject3 := NewTestObject(map[string]interface{}{
"status": "active",
"score": 85,
"role": "user",
})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult1 := spec.IsSatisfiedBy(nonMatchingObject1)
noMatchResult2 := spec.IsSatisfiedBy(nonMatchingObject2)
noMatchResult3 := spec.IsSatisfiedBy(nonMatchingObject3)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult1 {
t.Error("Expected non-matching object 1 to not satisfy the specification")
}
if noMatchResult2 {
t.Error("Expected non-matching object 2 to not satisfy the specification")
}
if noMatchResult3 {
t.Error("Expected non-matching object 3 to not satisfy the specification")
}
}
// OR Group Tests
func TestWhenUsingOrGroupThenMatchesWhenAnyConditionMet(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Or(
specifications.Eq("role", "admin"),
specifications.Gte("score", 90),
specifications.In("department", []interface{}{"IT", "HR"}),
))
matchingObject1 := NewTestObject(map[string]interface{}{
"role": "admin",
"score": 70,
"department": "Finance",
})
matchingObject2 := NewTestObject(map[string]interface{}{
"role": "user",
"score": 95,
"department": "Finance",
})
matchingObject3 := NewTestObject(map[string]interface{}{
"role": "user",
"score": 70,
"department": "IT",
})
nonMatchingObject := NewTestObject(map[string]interface{}{
"role": "user",
"score": 70,
"department": "Finance",
})
// When
matchResult1 := spec.IsSatisfiedBy(matchingObject1)
matchResult2 := spec.IsSatisfiedBy(matchingObject2)
matchResult3 := spec.IsSatisfiedBy(matchingObject3)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult1 {
t.Error("Expected matching object 1 to satisfy the specification")
}
if !matchResult2 {
t.Error("Expected matching object 2 to satisfy the specification")
}
if !matchResult3 {
t.Error("Expected matching object 3 to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
// NOT Group Tests
func TestWhenUsingNotGroupThenInvertsCondition(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Not(specifications.Eq("status", "banned")))
matchingObject := NewTestObject(map[string]interface{}{"status": "active"})
nonMatchingObject := NewTestObject(map[string]interface{}{"status": "banned"})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
// Complex Nested Groups Tests
func TestWhenUsingNestedAndOrGroupsThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.And(
specifications.Eq("status", "active"),
specifications.Or(
specifications.Gte("score", 80),
specifications.In("role", []interface{}{"admin", "moderator"}),
),
))
matchingObject1 := NewTestObject(map[string]interface{}{
"status": "active",
"score": 85,
"role": "user",
})
matchingObject2 := NewTestObject(map[string]interface{}{
"status": "active",
"score": 70,
"role": "admin",
})
nonMatchingObject := NewTestObject(map[string]interface{}{
"status": "inactive",
"score": 85,
"role": "user",
})
// When
matchResult1 := spec.IsSatisfiedBy(matchingObject1)
matchResult2 := spec.IsSatisfiedBy(matchingObject2)
noMatchResult := spec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult1 {
t.Error("Expected matching object 1 to satisfy the specification")
}
if !matchResult2 {
t.Error("Expected matching object 2 to satisfy the specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the specification")
}
}
func TestWhenUsingTripleNestedGroupsThenMatchesCorrectly(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.And(
specifications.Eq("active", true),
specifications.Not(specifications.Or(
specifications.Eq("role", "banned"),
specifications.Eq("status", "suspended"),
)),
))
matchingObject := NewTestObject(map[string]interface{}{
"active": true,
"role": "user",
"status": "active",
})
nonMatchingObject1 := NewTestObject(map[string]interface{}{
"active": false,
"role": "user",
"status": "active",
})
nonMatchingObject2 := NewTestObject(map[string]interface{}{
"active": true,
"role": "banned",
"status": "active",
})
// When
matchResult := spec.IsSatisfiedBy(matchingObject)
noMatchResult1 := spec.IsSatisfiedBy(nonMatchingObject1)
noMatchResult2 := spec.IsSatisfiedBy(nonMatchingObject2)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the specification")
}
if noMatchResult1 {
t.Error("Expected non-matching object 1 to not satisfy the specification")
}
if noMatchResult2 {
t.Error("Expected non-matching object 2 to not satisfy the specification")
}
}
// Edge Case Tests
func TestWhenFieldDoesNotExistThenReturnsFalse(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("nonExistentField", "value"))
testObject := NewTestObject(map[string]interface{}{"existingField": "value"})
// When
result := spec.IsSatisfiedBy(testObject)
// Then
if result {
t.Error("Expected object to not satisfy the specification when field doesn't exist")
}
}
func TestWhenFieldIsNilThenReturnsFalse(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("optionalField", "value"))
testObject := NewTestObject(map[string]interface{}{"optionalField": nil})
// When
result := spec.IsSatisfiedBy(testObject)
// Then
if result {
t.Error("Expected object to not satisfy the specification when field is nil")
}
}
func TestWhenUsingInvalidDateStringThenFallsBackToRegularComparison(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("dateField", INVALID_DATE))
testObject := NewTestObject(map[string]interface{}{"dateField": INVALID_DATE})
// When
result := spec.IsSatisfiedBy(testObject)
// Then
if !result {
t.Error("Expected object to satisfy the specification with invalid date string")
}
}
// Spec Helper Method Tests
func TestWhenUsingSpecHelpersThenCreatesCorrectSpecification(t *testing.T) {
// Given
eqSpec := specifications.Eq("field", "value")
neqSpec := specifications.Neq("field", "value")
gtSpec := specifications.Gt("field", 10)
gteSpec := specifications.Gte("field", 10)
ltSpec := specifications.Lt("field", 10)
lteSpec := specifications.Lte("field", 10)
inSpec := specifications.In("field", []interface{}{"a", "b"})
ninSpec := specifications.Nin("field", []interface{}{"a", "b"})
// When & Then
if eqSpec.Condition == nil || eqSpec.Condition.Operator != specifications.OP_EQ || eqSpec.Condition.Value != "value" {
t.Error("EQ spec not created correctly")
}
if neqSpec.Condition == nil || neqSpec.Condition.Operator != specifications.OP_NEQ || neqSpec.Condition.Value != "value" {
t.Error("NEQ spec not created correctly")
}
if gtSpec.Condition == nil || gtSpec.Condition.Operator != specifications.OP_GT || gtSpec.Condition.Value != 10 {
t.Error("GT spec not created correctly")
}
if gteSpec.Condition == nil || gteSpec.Condition.Operator != specifications.OP_GTE || gteSpec.Condition.Value != 10 {
t.Error("GTE spec not created correctly")
}
if ltSpec.Condition == nil || ltSpec.Condition.Operator != specifications.OP_LT || ltSpec.Condition.Value != 10 {
t.Error("LT spec not created correctly")
}
if lteSpec.Condition == nil || lteSpec.Condition.Operator != specifications.OP_LTE || lteSpec.Condition.Value != 10 {
t.Error("LTE spec not created correctly")
}
if inSpec.Condition == nil || inSpec.Condition.Operator != specifications.OP_IN {
t.Error("IN spec not created correctly")
}
if ninSpec.Condition == nil || ninSpec.Condition.Operator != specifications.OP_NIN {
t.Error("NIN spec not created correctly")
}
}
func TestWhenUsingLogicalGroupHelpersThenCreatesCorrectSpecification(t *testing.T) {
// Given
andSpec := specifications.And(specifications.Eq("a", 1), specifications.Eq("b", 2))
orSpec := specifications.Or(specifications.Eq("a", 1), specifications.Eq("b", 2))
notSpec := specifications.Not(specifications.Eq("a", 1))
// When & Then
if andSpec.LogicalGroup == nil || andSpec.LogicalGroup.Operator != specifications.GROUP_AND || len(andSpec.LogicalGroup.Conditions) != 2 {
t.Error("AND spec not created correctly")
}
if orSpec.LogicalGroup == nil || orSpec.LogicalGroup.Operator != specifications.GROUP_OR || len(orSpec.LogicalGroup.Conditions) != 2 {
t.Error("OR spec not created correctly")
}
if notSpec.LogicalGroup == nil || notSpec.LogicalGroup.Operator != specifications.GROUP_NOT || notSpec.LogicalGroup.Spec == nil {
t.Error("NOT spec not created correctly")
}
}
func TestGetSpecReturnsOriginalSpecification(t *testing.T) {
// Given
originalSpec := specifications.Eq("field", "value")
specification := specifications.NewSimpleSpecification[*TestObject](originalSpec)
// When
retrievedSpec := specification.GetSpec()
// Then
if retrievedSpec != originalSpec {
t.Error("Expected retrieved spec to be the same as original spec")
}
}
func TestGetConditionsReturnsAllConditions(t *testing.T) {
// Given
spec := specifications.NewSimpleSpecification[*TestObject](specifications.And(
specifications.Eq("status", "active"),
specifications.Gte("score", 80),
))
// When
conditions := spec.GetConditions()
// Then
if len(conditions) != 2 {
t.Error("Expected 2 conditions")
}
// Check that both conditions are present
foundStatus := false
foundScore := false
for _, cond := range conditions {
if cond.Field == "status" && cond.Operator == specifications.OP_EQ && cond.Value == "active" {
foundStatus = true
}
if cond.Field == "score" && cond.Operator == specifications.OP_GTE && cond.Value == 80 {
foundScore = true
}
}
if !foundStatus {
t.Error("Expected status condition to be found")
}
if !foundScore {
t.Error("Expected score condition to be found")
}
}
// Composite Specification Tests
func TestCompositeSpecificationAndOperation(t *testing.T) {
// Given
leftSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("status", "active"))
rightSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Gte("score", 80))
compositeSpec := leftSpec.And(rightSpec)
matchingObject := NewTestObject(map[string]interface{}{
"status": "active",
"score": 85,
})
nonMatchingObject := NewTestObject(map[string]interface{}{
"status": "inactive",
"score": 85,
})
// When
matchResult := compositeSpec.IsSatisfiedBy(matchingObject)
noMatchResult := compositeSpec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the composite specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the composite specification")
}
}
func TestCompositeSpecificationOrOperation(t *testing.T) {
// Given
leftSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("role", "admin"))
rightSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Gte("score", 90))
compositeSpec := leftSpec.Or(rightSpec)
matchingObject1 := NewTestObject(map[string]interface{}{
"role": "admin",
"score": 70,
})
matchingObject2 := NewTestObject(map[string]interface{}{
"role": "user",
"score": 95,
})
nonMatchingObject := NewTestObject(map[string]interface{}{
"role": "user",
"score": 70,
})
// When
matchResult1 := compositeSpec.IsSatisfiedBy(matchingObject1)
matchResult2 := compositeSpec.IsSatisfiedBy(matchingObject2)
noMatchResult := compositeSpec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult1 {
t.Error("Expected matching object 1 to satisfy the composite specification")
}
if !matchResult2 {
t.Error("Expected matching object 2 to satisfy the composite specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the composite specification")
}
}
func TestCompositeSpecificationNotOperation(t *testing.T) {
// Given
baseSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("status", "banned"))
compositeSpec := baseSpec.Not()
matchingObject := NewTestObject(map[string]interface{}{"status": "active"})
nonMatchingObject := NewTestObject(map[string]interface{}{"status": "banned"})
// When
matchResult := compositeSpec.IsSatisfiedBy(matchingObject)
noMatchResult := compositeSpec.IsSatisfiedBy(nonMatchingObject)
// Then
if !matchResult {
t.Error("Expected matching object to satisfy the NOT specification")
}
if noMatchResult {
t.Error("Expected non-matching object to not satisfy the NOT specification")
}
}
func TestCompositeSpecificationGetConditions(t *testing.T) {
// Given
leftSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("status", "active"))
rightSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Gte("score", 80))
compositeSpec := leftSpec.And(rightSpec)
// When
conditions := compositeSpec.GetConditions()
// Then
if len(conditions) != 2 {
t.Error("Expected 2 conditions from composite specification")
}
// Check that both conditions are present
foundStatus := false
foundScore := false
for _, cond := range conditions {
if cond.Field == "status" && cond.Operator == specifications.OP_EQ && cond.Value == "active" {
foundStatus = true
}
if cond.Field == "score" && cond.Operator == specifications.OP_GTE && cond.Value == 80 {
foundScore = true
}
}
if !foundStatus {
t.Error("Expected status condition to be found")
}
if !foundScore {
t.Error("Expected score condition to be found")
}
}
func TestCompositeSpecificationGetSpecReturnsNil(t *testing.T) {
// Given
leftSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Eq("status", "active"))
rightSpec := specifications.NewSimpleSpecification[*TestObject](specifications.Gte("score", 80))
compositeSpec := leftSpec.And(rightSpec)
// When
spec := compositeSpec.GetSpec()
// Then
if spec != nil {
t.Error("Expected composite specification to return nil for GetSpec")
}
}