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
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") |
|
} |
|
}
|
|
|