1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/idiomatic/result/functions_test.go
Dr. Carsten Leue 8a2e9539b1 fix: add result
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-17 20:36:06 +01:00

455 lines
11 KiB
Go

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