// 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 result import ( "errors" "fmt" "testing" N "github.com/IBM/fp-go/v2/number" "github.com/stretchr/testify/assert" ) // TestBiMap tests mapping over both error and value channels func TestBiMap(t *testing.T) { wrapError := func(e error) error { return fmt.Errorf("wrapped: %w", e) } double := N.Mul(2) t.Run("BiMap on Right", func(t *testing.T) { val, err := BiMap[int, int](wrapError, double)(Right(21)) AssertEq(Right(42))(val, err)(t) }) t.Run("BiMap on Left", func(t *testing.T) { originalErr := errors.New("original") val, err := BiMap[int, int](wrapError, double)(Left[int](originalErr)) assert.Error(t, err) assert.Contains(t, err.Error(), "wrapped") assert.Contains(t, err.Error(), "original") assert.Equal(t, 0, val) }) } // TestMapTo tests mapping to a constant value func TestMapTo(t *testing.T) { t.Run("MapTo on Right", func(t *testing.T) { val, err := MapTo[int, string]("constant")(Right(42)) AssertEq(Right("constant"))(val, err)(t) }) t.Run("MapTo on Left", func(t *testing.T) { originalErr := errors.New("error") val, err := MapTo[int, string]("constant")(Left[int](originalErr)) assert.Error(t, err) assert.Equal(t, originalErr, err) // MapTo still applies the constant value even for Left assert.Equal(t, "constant", val) }) } // TestChainTo tests chaining to a constant value func TestChainTo(t *testing.T) { t.Run("ChainTo Right to Right", func(t *testing.T) { val, err := ChainTo[int, string]("success", nil)(Right(42)) AssertEq(Right("success"))(val, err)(t) }) t.Run("ChainTo Right to Left", func(t *testing.T) { targetErr := errors.New("target error") val, err := ChainTo[int, string]("", targetErr)(Right(42)) assert.Error(t, err) assert.Equal(t, targetErr, err) assert.Equal(t, "", val) }) t.Run("ChainTo Left", func(t *testing.T) { sourceErr := errors.New("source error") val, err := ChainTo[int, string]("success", nil)(Left[int](sourceErr)) assert.Error(t, err) assert.Equal(t, sourceErr, err) assert.Equal(t, "", val) }) } // TestReduce tests the reduce/fold operation func TestReduce(t *testing.T) { sum := func(acc, val int) int { return acc + val } t.Run("Reduce on Right", func(t *testing.T) { result := Reduce(sum, 10)(Right(32)) assert.Equal(t, 42, result) }) t.Run("Reduce on Left", func(t *testing.T) { result := Reduce(sum, 10)(Left[int](errors.New("error"))) assert.Equal(t, 10, result) // Returns initial value }) } // TestFromPredicate tests creating Result from a predicate func TestFromPredicate(t *testing.T) { isPositive := FromPredicate( func(x int) bool { return x > 0 }, func(x int) error { return fmt.Errorf("%d is not positive", x) }, ) t.Run("Predicate passes", func(t *testing.T) { val, err := isPositive(42) AssertEq(Right(42))(val, err)(t) }) t.Run("Predicate fails", func(t *testing.T) { val, err := isPositive(-5) assert.Error(t, err) assert.Contains(t, err.Error(), "not positive") // FromPredicate returns zero value on Left assert.Equal(t, 0, val) }) t.Run("Predicate with zero", func(t *testing.T) { val, err := isPositive(0) assert.Error(t, err) assert.Equal(t, 0, val) }) } // TestFromNillable tests creating Result from nullable pointers func TestFromNillable(t *testing.T) { nilErr := errors.New("value is nil") fromPtr := FromNillable[int](nilErr) t.Run("Non-nil pointer", func(t *testing.T) { value := 42 val, err := fromPtr(&value) assert.NoError(t, err) assert.NotNil(t, val) assert.Equal(t, 42, *val) }) t.Run("Nil pointer", func(t *testing.T) { val, err := fromPtr(nil) assert.Error(t, err) assert.Equal(t, nilErr, err) assert.Nil(t, val) }) } // TestToType tests type conversion func TestToType(t *testing.T) { toInt := ToType[int](func(v any) error { return fmt.Errorf("cannot convert %T to int", v) }) t.Run("Correct type", func(t *testing.T) { val, err := toInt(42) AssertEq(Right(42))(val, err)(t) }) t.Run("Wrong type", func(t *testing.T) { val, err := toInt("string") assert.Error(t, err) assert.Contains(t, err.Error(), "cannot convert") assert.Equal(t, 0, val) }) t.Run("Nil value", func(t *testing.T) { val, err := toInt(nil) assert.Error(t, err) assert.Equal(t, 0, val) }) } // TestMemoize tests that Memoize returns the value unchanged func TestMemoize(t *testing.T) { t.Run("Memoize Right", func(t *testing.T) { val, err := Memoize(Right(42)) AssertEq(Right(42))(val, err)(t) }) t.Run("Memoize Left", func(t *testing.T) { originalErr := errors.New("error") val, err := Memoize(Left[int](originalErr)) assert.Error(t, err) assert.Equal(t, originalErr, err) assert.Equal(t, 0, val) }) } // TestFlap tests reverse application func TestFlap(t *testing.T) { t.Run("Flap with Right function", func(t *testing.T) { double := N.Mul(2) val, err := Flap[int, int](21)(Right(double)) AssertEq(Right(42))(val, err)(t) }) t.Run("Flap with Left function", func(t *testing.T) { fnErr := errors.New("function error") val, err := Flap[int, int](21)(Left[func(int) int](fnErr)) assert.Error(t, err) assert.Equal(t, fnErr, err) assert.Equal(t, 0, val) }) } // TestToError tests extracting error from Result func TestToError(t *testing.T) { t.Run("ToError from Right", func(t *testing.T) { err := ToError(Right(42)) assert.NoError(t, err) }) t.Run("ToError from Left", func(t *testing.T) { originalErr := errors.New("error") err := ToError(Left[int](originalErr)) assert.Error(t, err) assert.Equal(t, originalErr, err) }) } // TestLet tests the Let operation for do-notation func TestLet(t *testing.T) { type State struct { value int } t.Run("Let with Right", func(t *testing.T) { val, err := Pipe2( State{value: 10}, Right, Let( func(v int) func(State) State { return func(s State) State { return State{value: s.value + v} } }, func(s State) int { return 32 }, ), ) assert.NoError(t, err) assert.Equal(t, 42, val.value) }) t.Run("Let with Left", func(t *testing.T) { originalErr := errors.New("error") val, err := Pipe2( originalErr, Left[State], Let( func(v int) func(State) State { return func(s State) State { return State{value: v} } }, func(s State) int { return 42 }, ), ) assert.Error(t, err) assert.Equal(t, originalErr, err) assert.Equal(t, State{}, val) }) } // TestLetTo tests the LetTo operation func TestLetTo(t *testing.T) { type State struct { name string } t.Run("LetTo with Right", func(t *testing.T) { val, err := Pipe2( State{}, Right, LetTo( func(n string) func(State) State { return func(s State) State { return State{name: n} } }, "Alice", ), ) assert.NoError(t, err) assert.Equal(t, "Alice", val.name) }) t.Run("LetTo with Left", func(t *testing.T) { originalErr := errors.New("error") val, err := Pipe2( originalErr, Left[State], LetTo( func(n string) func(State) State { return func(s State) State { return State{name: n} } }, "Bob", ), ) assert.Error(t, err) assert.Equal(t, State{}, val) }) } // TestBindTo tests the BindTo operation func TestBindTo(t *testing.T) { type State struct { value int } t.Run("BindTo with Right", func(t *testing.T) { val, err := Pipe2( 42, Right, BindTo(func(v int) State { return State{value: v} }), ) assert.NoError(t, err) assert.Equal(t, 42, val.value) }) t.Run("BindTo with Left", func(t *testing.T) { originalErr := errors.New("error") val, err := Pipe2( originalErr, Left[int], BindTo(func(v int) State { return State{value: v} }), ) assert.Error(t, err) assert.Equal(t, State{}, val) }) } // TestMapLeft tests mapping over the error channel func TestMapLeft(t *testing.T) { wrapError := func(e error) error { return fmt.Errorf("wrapped: %w", e) } t.Run("MapLeft on Right", func(t *testing.T) { val, err := MapLeft[int](wrapError)(Right(42)) AssertEq(Right(42))(val, err)(t) }) t.Run("MapLeft on Left", func(t *testing.T) { originalErr := errors.New("original") _, err := MapLeft[int](wrapError)(Left[int](originalErr)) assert.Error(t, err) assert.Contains(t, err.Error(), "wrapped") assert.Contains(t, err.Error(), "original") }) } // TestOrElse tests recovery from error func TestOrElse(t *testing.T) { recover := OrElse(func(e error) (int, error) { return Right(0) // default value }) t.Run("OrElse on Right", func(t *testing.T) { val, err := recover(Right(42)) AssertEq(Right(42))(val, err)(t) }) t.Run("OrElse on Left recovers", func(t *testing.T) { val, err := recover(Left[int](errors.New("error"))) AssertEq(Right(0))(val, err)(t) }) } // TestDo tests the Do operation func TestDo(t *testing.T) { type State struct { x int y int } result, err := Do(State{}) assert.NoError(t, err) assert.Equal(t, State{}, result) } // TestOf tests the Of/pure operation func TestOf(t *testing.T) { val, err := Of(42) AssertEq(Right(42))(val, err)(t) } // TestToString tests string representation func TestToString(t *testing.T) { t.Run("ToString Right", func(t *testing.T) { str := ToString(Right(42)) assert.Equal(t, "Right[int](42)", str) }) t.Run("ToString Left", func(t *testing.T) { str := ToString(Left[int](errors.New("error"))) assert.Contains(t, str, "Left(") assert.Contains(t, str, "error") }) } // TestToOption tests conversion to Option func TestToOption(t *testing.T) { t.Run("ToOption from Right", func(t *testing.T) { val, ok := ToOption(Right(42)) assert.True(t, ok) assert.Equal(t, 42, val) }) t.Run("ToOption from Left", func(t *testing.T) { val, ok := ToOption(Left[int](errors.New("error"))) assert.False(t, ok) assert.Equal(t, 0, val) }) } // TestFromError tests creating Result from error-returning function func TestFromError(t *testing.T) { validate := func(x int) error { if x < 0 { return errors.New("negative") } return nil } toResult := FromError(validate) t.Run("FromError with valid value", func(t *testing.T) { val, err := toResult(42) AssertEq(Right(42))(val, err)(t) }) t.Run("FromError with invalid value", func(t *testing.T) { val, err := toResult(-5) assert.Error(t, err) assert.Equal(t, "negative", err.Error()) assert.Equal(t, -5, val) }) } // TestGetOrElse tests extracting value with default func TestGetOrElse(t *testing.T) { defaultValue := func(error) int { return 0 } t.Run("GetOrElse on Right", func(t *testing.T) { val := GetOrElse(defaultValue)(Right(42)) assert.Equal(t, 42, val) }) t.Run("GetOrElse on Left", func(t *testing.T) { val := GetOrElse(defaultValue)(Left[int](errors.New("error"))) assert.Equal(t, 0, val) }) }