mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
455 lines
11 KiB
Go
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)
|
|
})
|
|
}
|