// Copyright (c) 2023 - 2025 IBM Corp. // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package assert import ( "errors" "testing" "github.com/IBM/fp-go/v2/optics/prism" "github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/result" S "github.com/IBM/fp-go/v2/string" ) func TestEqual(t *testing.T) { t.Run("should pass when values are equal", func(t *testing.T) { result := Equal(42)(42)(t) if !result { t.Error("Expected Equal to pass for equal values") } }) t.Run("should fail when values are not equal", func(t *testing.T) { mockT := &testing.T{} result := Equal(42)(43)(mockT) if result { t.Error("Expected Equal to fail for different values") } }) t.Run("should work with strings", func(t *testing.T) { result := Equal("hello")("hello")(t) if !result { t.Error("Expected Equal to pass for equal strings") } }) } func TestNotEqual(t *testing.T) { t.Run("should pass when values are not equal", func(t *testing.T) { result := NotEqual(42)(43)(t) if !result { t.Error("Expected NotEqual to pass for different values") } }) t.Run("should fail when values are equal", func(t *testing.T) { mockT := &testing.T{} result := NotEqual(42)(42)(mockT) if result { t.Error("Expected NotEqual to fail for equal values") } }) } func TestArrayNotEmpty(t *testing.T) { t.Run("should pass for non-empty array", func(t *testing.T) { arr := []int{1, 2, 3} result := ArrayNotEmpty(arr)(t) if !result { t.Error("Expected ArrayNotEmpty to pass for non-empty array") } }) t.Run("should fail for empty array", func(t *testing.T) { mockT := &testing.T{} arr := []int{} result := ArrayNotEmpty(arr)(mockT) if result { t.Error("Expected ArrayNotEmpty to fail for empty array") } }) } func TestRecordNotEmpty(t *testing.T) { t.Run("should pass for non-empty map", func(t *testing.T) { mp := map[string]int{"a": 1, "b": 2} result := RecordNotEmpty(mp)(t) if !result { t.Error("Expected RecordNotEmpty to pass for non-empty map") } }) t.Run("should fail for empty map", func(t *testing.T) { mockT := &testing.T{} mp := map[string]int{} result := RecordNotEmpty(mp)(mockT) if result { t.Error("Expected RecordNotEmpty to fail for empty map") } }) } func TestArrayLength(t *testing.T) { t.Run("should pass when length matches", func(t *testing.T) { arr := []int{1, 2, 3} result := ArrayLength[int](3)(arr)(t) if !result { t.Error("Expected ArrayLength to pass when length matches") } }) t.Run("should fail when length doesn't match", func(t *testing.T) { mockT := &testing.T{} arr := []int{1, 2, 3} result := ArrayLength[int](5)(arr)(mockT) if result { t.Error("Expected ArrayLength to fail when length doesn't match") } }) t.Run("should work with empty array", func(t *testing.T) { arr := []string{} result := ArrayLength[string](0)(arr)(t) if !result { t.Error("Expected ArrayLength to pass for empty array with expected length 0") } }) } func TestRecordLength(t *testing.T) { t.Run("should pass when map length matches", func(t *testing.T) { mp := map[string]int{"a": 1, "b": 2} result := RecordLength[string, int](2)(mp)(t) if !result { t.Error("Expected RecordLength to pass when length matches") } }) t.Run("should fail when map length doesn't match", func(t *testing.T) { mockT := &testing.T{} mp := map[string]int{"a": 1} result := RecordLength[string, int](3)(mp)(mockT) if result { t.Error("Expected RecordLength to fail when length doesn't match") } }) } func TestStringLength(t *testing.T) { t.Run("should pass when string length matches", func(t *testing.T) { str := "hello" result := StringLength[string, int](5)(str)(t) if !result { t.Error("Expected StringLength to pass when length matches") } }) t.Run("should fail when string length doesn't match", func(t *testing.T) { mockT := &testing.T{} str := "hello" result := StringLength[string, int](10)(str)(mockT) if result { t.Error("Expected StringLength to fail when length doesn't match") } }) t.Run("should work with empty string", func(t *testing.T) { str := "" result := StringLength[string, int](0)(str)(t) if !result { t.Error("Expected StringLength to pass for empty string with expected length 0") } }) } func TestNoError(t *testing.T) { t.Run("should pass when error is nil", func(t *testing.T) { result := NoError(nil)(t) if !result { t.Error("Expected NoError to pass when error is nil") } }) t.Run("should fail when error is not nil", func(t *testing.T) { mockT := &testing.T{} err := errors.New("test error") result := NoError(err)(mockT) if result { t.Error("Expected NoError to fail when error is not nil") } }) } func TestError(t *testing.T) { t.Run("should pass when error is not nil", func(t *testing.T) { err := errors.New("test error") result := Error(err)(t) if !result { t.Error("Expected Error to pass when error is not nil") } }) t.Run("should fail when error is nil", func(t *testing.T) { mockT := &testing.T{} result := Error(nil)(mockT) if result { t.Error("Expected Error to fail when error is nil") } }) } func TestSuccess(t *testing.T) { t.Run("should pass for successful result", func(t *testing.T) { res := result.Of(42) result := Success(res)(t) if !result { t.Error("Expected Success to pass for successful result") } }) t.Run("should fail for error result", func(t *testing.T) { mockT := &testing.T{} res := result.Left[int](errors.New("test error")) result := Success(res)(mockT) if result { t.Error("Expected Success to fail for error result") } }) } func TestFailure(t *testing.T) { t.Run("should pass for error result", func(t *testing.T) { res := result.Left[int](errors.New("test error")) result := Failure(res)(t) if !result { t.Error("Expected Failure to pass for error result") } }) t.Run("should fail for successful result", func(t *testing.T) { mockT := &testing.T{} res := result.Of(42) result := Failure(res)(mockT) if result { t.Error("Expected Failure to fail for successful result") } }) } func TestArrayContains(t *testing.T) { t.Run("should pass when element is in array", func(t *testing.T) { arr := []int{1, 2, 3, 4, 5} result := ArrayContains(3)(arr)(t) if !result { t.Error("Expected ArrayContains to pass when element is in array") } }) t.Run("should fail when element is not in array", func(t *testing.T) { mockT := &testing.T{} arr := []int{1, 2, 3} result := ArrayContains(10)(arr)(mockT) if result { t.Error("Expected ArrayContains to fail when element is not in array") } }) t.Run("should work with strings", func(t *testing.T) { arr := []string{"apple", "banana", "cherry"} result := ArrayContains("banana")(arr)(t) if !result { t.Error("Expected ArrayContains to pass for string element") } }) } func TestContainsKey(t *testing.T) { t.Run("should pass when key exists in map", func(t *testing.T) { mp := map[string]int{"a": 1, "b": 2, "c": 3} result := ContainsKey[int]("b")(mp)(t) if !result { t.Error("Expected ContainsKey to pass when key exists") } }) t.Run("should fail when key doesn't exist in map", func(t *testing.T) { mockT := &testing.T{} mp := map[string]int{"a": 1, "b": 2} result := ContainsKey[int]("z")(mp)(mockT) if result { t.Error("Expected ContainsKey to fail when key doesn't exist") } }) } func TestNotContainsKey(t *testing.T) { t.Run("should pass when key doesn't exist in map", func(t *testing.T) { mp := map[string]int{"a": 1, "b": 2} result := NotContainsKey[int]("z")(mp)(t) if !result { t.Error("Expected NotContainsKey to pass when key doesn't exist") } }) t.Run("should fail when key exists in map", func(t *testing.T) { mockT := &testing.T{} mp := map[string]int{"a": 1, "b": 2} result := NotContainsKey[int]("a")(mp)(mockT) if result { t.Error("Expected NotContainsKey to fail when key exists") } }) } func TestThat(t *testing.T) { t.Run("should pass when predicate is true", func(t *testing.T) { isEven := func(n int) bool { return n%2 == 0 } result := That(isEven)(42)(t) if !result { t.Error("Expected That to pass when predicate is true") } }) t.Run("should fail when predicate is false", func(t *testing.T) { mockT := &testing.T{} isEven := func(n int) bool { return n%2 == 0 } result := That(isEven)(43)(mockT) if result { t.Error("Expected That to fail when predicate is false") } }) t.Run("should work with string predicates", func(t *testing.T) { startsWithH := func(s string) bool { return S.IsNonEmpty(s) && s[0] == 'h' } result := That(startsWithH)("hello")(t) if !result { t.Error("Expected That to pass for string predicate") } }) } func TestAllOf(t *testing.T) { t.Run("should pass when all assertions pass", func(t *testing.T) { assertions := AllOf([]Reader{ Equal(42)(42), Equal("hello")("hello"), ArrayNotEmpty([]int{1, 2, 3}), }) result := assertions(t) if !result { t.Error("Expected AllOf to pass when all assertions pass") } }) t.Run("should fail when any assertion fails", func(t *testing.T) { mockT := &testing.T{} assertions := AllOf([]Reader{ Equal(42)(42), Equal("hello")("goodbye"), ArrayNotEmpty([]int{1, 2, 3}), }) result := assertions(mockT) if result { t.Error("Expected AllOf to fail when any assertion fails") } }) t.Run("should work with empty array", func(t *testing.T) { assertions := AllOf([]Reader{}) result := assertions(t) if !result { t.Error("Expected AllOf to pass for empty array") } }) t.Run("should combine multiple array assertions", func(t *testing.T) { arr := []int{1, 2, 3, 4, 5} assertions := AllOf([]Reader{ ArrayNotEmpty(arr), ArrayLength[int](5)(arr), ArrayContains(3)(arr), }) result := assertions(t) if !result { t.Error("Expected AllOf to pass for multiple array assertions") } }) } func TestRunAll(t *testing.T) { t.Run("should run all named test cases", func(t *testing.T) { testcases := map[string]Reader{ "equality": Equal(42)(42), "string_check": Equal("test")("test"), "array_check": ArrayNotEmpty([]int{1, 2, 3}), } result := RunAll(testcases)(t) if !result { t.Error("Expected RunAll to pass when all test cases pass") } }) // Note: Testing failure behavior of RunAll is tricky because subtests // will actually fail in the test framework. The function works correctly // as demonstrated by the passing test above. t.Run("should work with empty test cases", func(t *testing.T) { testcases := map[string]Reader{} result := RunAll(testcases)(t) if !result { t.Error("Expected RunAll to pass for empty test cases") } }) } func TestEq(t *testing.T) { t.Run("should return true for equal values", func(t *testing.T) { if !Eq.Equals(42, 42) { t.Error("Expected Eq to return true for equal integers") } }) t.Run("should return false for different values", func(t *testing.T) { if Eq.Equals(42, 43) { t.Error("Expected Eq to return false for different integers") } }) t.Run("should work with strings", func(t *testing.T) { if !Eq.Equals("hello", "hello") { t.Error("Expected Eq to return true for equal strings") } if Eq.Equals("hello", "world") { t.Error("Expected Eq to return false for different strings") } }) t.Run("should work with slices", func(t *testing.T) { arr1 := []int{1, 2, 3} arr2 := []int{1, 2, 3} if !Eq.Equals(arr1, arr2) { t.Error("Expected Eq to return true for equal slices") } }) } func TestLocal(t *testing.T) { type User struct { Name string Age int } t.Run("should focus assertion on a property", func(t *testing.T) { // Create an assertion that checks if age is positive ageIsPositive := That(func(age int) bool { return age > 0 }) // Focus this assertion on the Age field of User userAgeIsPositive := Local(func(u User) int { return u.Age })(ageIsPositive) // Test with a user who has a positive age user := User{Name: "Alice", Age: 30} result := userAgeIsPositive(user)(t) if !result { t.Error("Expected focused assertion to pass for positive age") } }) t.Run("should fail when focused property doesn't match", func(t *testing.T) { mockT := &testing.T{} ageIsPositive := That(func(age int) bool { return age > 0 }) userAgeIsPositive := Local(func(u User) int { return u.Age })(ageIsPositive) // Test with a user who has zero age user := User{Name: "Bob", Age: 0} result := userAgeIsPositive(user)(mockT) if result { t.Error("Expected focused assertion to fail for zero age") } }) t.Run("should compose with other assertions", func(t *testing.T) { // Create multiple focused assertions nameNotEmpty := Local(func(u User) string { return u.Name })( That(S.IsNonEmpty), ) ageInRange := Local(func(u User) int { return u.Age })( That(func(age int) bool { return age >= 18 && age <= 100 }), ) user := User{Name: "Charlie", Age: 25} assertions := AllOf([]Reader{ nameNotEmpty(user), ageInRange(user), }) result := assertions(t) if !result { t.Error("Expected composed focused assertions to pass") } }) t.Run("should work with Equal assertion", func(t *testing.T) { // Focus Equal assertion on Name field nameIsAlice := Local(func(u User) string { return u.Name })(Equal("Alice")) user := User{Name: "Alice", Age: 30} result := nameIsAlice(user)(t) if !result { t.Error("Expected focused Equal assertion to pass") } }) } func TestLocalL(t *testing.T) { // Note: LocalL requires lens package which provides lens operations. // This test demonstrates the concept, but actual usage would require // proper lens definitions. t.Run("conceptual test for LocalL", func(t *testing.T) { // LocalL is similar to Local but uses lenses for focusing. // It would be used like: // validEmail := That(func(email string) bool { return strings.Contains(email, "@") }) // validPersonEmail := LocalL(emailLens)(validEmail) // // The actual implementation would require lens definitions from the lens package. // This test serves as documentation for the intended usage. }) } func TestFromOptional(t *testing.T) { type DatabaseConfig struct { Host string Port int } type Config struct { Database *DatabaseConfig } // Create an Optional that focuses on the Database field dbOptional := Optional[Config, *DatabaseConfig]{ GetOption: func(c Config) option.Option[*DatabaseConfig] { if c.Database != nil { return option.Of(c.Database) } return option.None[*DatabaseConfig]() }, Set: func(db *DatabaseConfig) func(Config) Config { return func(c Config) Config { c.Database = db return c } }, } t.Run("should pass when optional value is present", func(t *testing.T) { config := Config{Database: &DatabaseConfig{Host: "localhost", Port: 5432}} hasDatabaseConfig := FromOptional(dbOptional) result := hasDatabaseConfig(config)(t) if !result { t.Error("Expected FromOptional to pass when optional value is present") } }) t.Run("should fail when optional value is absent", func(t *testing.T) { mockT := &testing.T{} emptyConfig := Config{Database: nil} hasDatabaseConfig := FromOptional(dbOptional) result := hasDatabaseConfig(emptyConfig)(mockT) if result { t.Error("Expected FromOptional to fail when optional value is absent") } }) t.Run("should work with nested optionals", func(t *testing.T) { type AdvancedSettings struct { Cache bool } type Settings struct { Advanced *AdvancedSettings } advancedOptional := Optional[Settings, *AdvancedSettings]{ GetOption: func(s Settings) option.Option[*AdvancedSettings] { if s.Advanced != nil { return option.Of(s.Advanced) } return option.None[*AdvancedSettings]() }, Set: func(adv *AdvancedSettings) func(Settings) Settings { return func(s Settings) Settings { s.Advanced = adv return s } }, } settings := Settings{Advanced: &AdvancedSettings{Cache: true}} hasAdvanced := FromOptional(advancedOptional) result := hasAdvanced(settings)(t) if !result { t.Error("Expected FromOptional to pass for nested optional") } }) } // Helper types for Prism testing type PrismTestResult interface { isPrismTestResult() } type PrismTestSuccess struct { Value int } type PrismTestFailure struct { Error string } func (PrismTestSuccess) isPrismTestResult() {} func (PrismTestFailure) isPrismTestResult() {} func TestFromPrism(t *testing.T) { // Create a Prism that focuses on Success variant using prism.MakePrism successPrism := prism.MakePrism( func(r PrismTestResult) option.Option[int] { if s, ok := r.(PrismTestSuccess); ok { return option.Of(s.Value) } return option.None[int]() }, func(v int) PrismTestResult { return PrismTestSuccess{Value: v} }, ) // Create a Prism that focuses on Failure variant failurePrism := prism.MakePrism( func(r PrismTestResult) option.Option[string] { if f, ok := r.(PrismTestFailure); ok { return option.Of(f.Error) } return option.None[string]() }, func(err string) PrismTestResult { return PrismTestFailure{Error: err} }, ) t.Run("should pass when prism successfully extracts", func(t *testing.T) { result := PrismTestSuccess{Value: 42} isSuccess := FromPrism(successPrism) testResult := isSuccess(result)(t) if !testResult { t.Error("Expected FromPrism to pass when prism extracts successfully") } }) t.Run("should fail when prism cannot extract", func(t *testing.T) { mockT := &testing.T{} result := PrismTestFailure{Error: "something went wrong"} isSuccess := FromPrism(successPrism) testResult := isSuccess(result)(mockT) if testResult { t.Error("Expected FromPrism to fail when prism cannot extract") } }) t.Run("should work with failure prism", func(t *testing.T) { result := PrismTestFailure{Error: "test error"} isFailure := FromPrism(failurePrism) testResult := isFailure(result)(t) if !testResult { t.Error("Expected FromPrism to pass for failure prism on failure result") } }) t.Run("should fail with failure prism on success result", func(t *testing.T) { mockT := &testing.T{} result := PrismTestSuccess{Value: 100} isFailure := FromPrism(failurePrism) testResult := isFailure(result)(mockT) if testResult { t.Error("Expected FromPrism to fail for failure prism on success result") } }) }