1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/readerioresult/readerioeither_test.go
Dr. Carsten Leue 1af6501cd8 fix: add bind variations
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-11 12:42:14 +01:00

770 lines
19 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 readerioresult
import (
"context"
"errors"
"fmt"
"strconv"
"testing"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/ioresult"
O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/reader"
RE "github.com/IBM/fp-go/v2/readereither"
RIO "github.com/IBM/fp-go/v2/readerio"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
type testContext struct {
value int
}
func TestMonadMap(t *testing.T) {
ctx := testContext{value: 10}
res := MonadMap(Of[testContext](5), func(x int) int { return x * 2 })
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestMonadMapTo(t *testing.T) {
ctx := testContext{value: 10}
res := MonadMapTo(Of[testContext](5), 42)
assert.Equal(t, result.Of(42), res(ctx)())
}
func TestMapTo(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(Of[testContext](5), MapTo[testContext, int](42))
assert.Equal(t, result.Of(42), res(ctx)())
}
func TestMonadChainFirst(t *testing.T) {
ctx := testContext{value: 10}
res := MonadChainFirst(
Of[testContext](5),
func(x int) ReaderIOResult[testContext, string] {
return Of[testContext](fmt.Sprintf("%d", x))
},
)
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestChainFirst(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](5),
ChainFirst(func(x int) ReaderIOResult[testContext, string] {
return Of[testContext](fmt.Sprintf("%d", x))
}),
)
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestMonadChainEitherK(t *testing.T) {
ctx := testContext{value: 10}
res := MonadChainEitherK(
Of[testContext](5),
func(x int) E.Either[error, int] {
return result.Of(x * 2)
},
)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestMonadChainFirstEitherK(t *testing.T) {
ctx := testContext{value: 10}
res := MonadChainFirstEitherK(
Of[testContext](5),
func(x int) E.Either[error, string] {
return result.Of(fmt.Sprintf("%d", x))
},
)
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestChainFirstEitherK(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](5),
ChainFirstEitherK[testContext](func(x int) E.Either[error, string] {
return result.Of(fmt.Sprintf("%d", x))
}),
)
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestMonadChainReaderK(t *testing.T) {
ctx := testContext{value: 10}
res := MonadChainReaderK(
Of[testContext](5),
func(x int) R.Reader[testContext, int] {
return func(c testContext) int { return x + c.value }
},
)
assert.Equal(t, result.Of(15), res(ctx)())
}
func TestMonadChainIOEitherK(t *testing.T) {
ctx := testContext{value: 10}
res := MonadChainIOEitherK(
Of[testContext](5),
func(x int) IOResult[int] {
return ioresult.Of(x * 2)
},
)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestChainIOEitherK(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](5),
ChainIOEitherK[testContext](func(x int) IOResult[int] {
return ioresult.Of(x * 2)
}),
)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestMonadChainIOK(t *testing.T) {
ctx := testContext{value: 10}
res := MonadChainIOK(
Of[testContext](5),
func(x int) IO[int] {
return func() int { return x * 2 }
},
)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestChainIOK(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](5),
ChainIOK[testContext](func(x int) IO[int] {
return func() int { return x * 2 }
}),
)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestMonadChainFirstIOK(t *testing.T) {
ctx := testContext{value: 10}
res := MonadChainFirstIOK(
Of[testContext](5),
func(x int) IO[string] {
return func() string { return fmt.Sprintf("%d", x) }
},
)
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestChainFirstIOK(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](5),
ChainFirstIOK[testContext](func(x int) IO[string] {
return func() string { return fmt.Sprintf("%d", x) }
}),
)
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestChainOptionK(t *testing.T) {
ctx := testContext{value: 10}
// Test with Some
resultSome := F.Pipe1(
Of[testContext](5),
ChainOptionK[testContext, int, int](func() error {
return errors.New("none")
})(func(x int) Option[int] {
return O.Some(x * 2)
}),
)
assert.Equal(t, result.Of(10), resultSome(ctx)())
// Test with None
resultNone := F.Pipe1(
Of[testContext](5),
ChainOptionK[testContext, int, int](func() error {
return errors.New("none")
})(func(x int) Option[int] {
return O.None[int]()
}),
)
assert.True(t, E.IsLeft(resultNone(ctx)()))
}
func TestMonadApSeq(t *testing.T) {
ctx := testContext{value: 10}
fab := Of[testContext](func(x int) int { return x * 2 })
fa := Of[testContext](5)
res := MonadApSeq(fab, fa)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestMonadApPar(t *testing.T) {
ctx := testContext{value: 10}
fab := Of[testContext](func(x int) int { return x * 2 })
fa := Of[testContext](5)
res := MonadApPar(fab, fa)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestChain(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](5),
Chain(func(x int) ReaderIOResult[testContext, int] {
return Of[testContext](x * 2)
}),
)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestThrowError(t *testing.T) {
ctx := testContext{value: 10}
result := ThrowError[testContext, int](errors.New("test error"))
assert.True(t, E.IsLeft(result(ctx)()))
}
func TestFlatten(t *testing.T) {
ctx := testContext{value: 10}
nested := Of[testContext](Of[testContext](5))
res := Flatten(nested)
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestFromEither(t *testing.T) {
ctx := testContext{value: 10}
res := FromEither[testContext](result.Of(5))
assert.Equal(t, result.Of(5), res(ctx)())
}
func TestRightReader(t *testing.T) {
ctx := testContext{value: 10}
rdr := func(c testContext) int { return c.value }
res := RightReader(rdr)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestLeftReader(t *testing.T) {
ctx := testContext{value: 10}
reader := func(c testContext) error { return errors.New("test") }
res := LeftReader[int](reader)
assert.True(t, E.IsLeft(res(ctx)()))
}
func TestRightIO(t *testing.T) {
ctx := testContext{value: 10}
ioVal := func() int { return 42 }
res := RightIO[testContext](ioVal)
assert.Equal(t, result.Of(42), res(ctx)())
}
func TestLeftIO(t *testing.T) {
ctx := testContext{value: 10}
ioVal := func() error { return errors.New("test") }
res := LeftIO[testContext, int](ioVal)
assert.True(t, E.IsLeft(res(ctx)()))
}
func TestFromIO(t *testing.T) {
ctx := testContext{value: 10}
ioVal := func() int { return 42 }
res := FromIO[testContext](ioVal)
assert.Equal(t, result.Of(42), res(ctx)())
}
func TestFromIOEither(t *testing.T) {
ctx := testContext{value: 10}
ioe := ioresult.Of(42)
res := FromIOEither[testContext](ioe)
assert.Equal(t, result.Of(42), res(ctx)())
}
func TestFromReaderEither(t *testing.T) {
ctx := testContext{value: 10}
re := RE.Of[testContext, error](42)
res := FromReaderEither(re)
assert.Equal(t, result.Of(42), res(ctx)())
}
func TestAsk(t *testing.T) {
ctx := testContext{value: 10}
res := Ask[testContext]()
assert.Equal(t, result.Of(ctx), res(ctx)())
}
func TestAsks(t *testing.T) {
ctx := testContext{value: 10}
res := Asks(func(c testContext) int { return c.value })
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestFromOption(t *testing.T) {
ctx := testContext{value: 10}
// Test with Some
resultSome := FromOption[testContext, int](func() error {
return errors.New("none")
})(O.Some(42))
assert.Equal(t, result.Of(42), resultSome(ctx)())
// Test with None
resultNone := FromOption[testContext, int](func() error {
return errors.New("none")
})(O.None[int]())
assert.True(t, E.IsLeft(resultNone(ctx)()))
}
func TestFromPredicate(t *testing.T) {
ctx := testContext{value: 10}
// Test predicate true
resultTrue := FromPredicate[testContext](
func(x int) bool { return x > 0 },
func(x int) error { return errors.New("negative") },
)(5)
assert.Equal(t, result.Of(5), resultTrue(ctx)())
// Test predicate false
resultFalse := FromPredicate[testContext](
func(x int) bool { return x > 0 },
func(x int) error { return errors.New("negative") },
)(-5)
assert.True(t, E.IsLeft(resultFalse(ctx)()))
}
func TestFold(t *testing.T) {
ctx := testContext{value: 10}
// Test Right case
resultRight := Fold(
func(e error) RIO.ReaderIO[testContext, string] {
return RIO.Of[testContext]("error: " + e.Error())
},
func(x int) RIO.ReaderIO[testContext, string] {
return RIO.Of[testContext](fmt.Sprintf("value: %d", x))
},
)(Of[testContext](42))
assert.Equal(t, "value: 42", resultRight(ctx)())
// Test Left case
resultLeft := Fold(
func(e error) RIO.ReaderIO[testContext, string] {
return RIO.Of[testContext]("error: " + e.Error())
},
func(x int) RIO.ReaderIO[testContext, string] {
return RIO.Of[testContext](fmt.Sprintf("value: %d", x))
},
)(Left[testContext, int](errors.New("test")))
assert.Equal(t, "error: test", resultLeft(ctx)())
}
func TestGetOrElse(t *testing.T) {
ctx := testContext{value: 10}
// Test Right case
resultRight := GetOrElse(func(e error) RIO.ReaderIO[testContext, int] {
return RIO.Of[testContext](0)
})(Of[testContext](42))
assert.Equal(t, 42, resultRight(ctx)())
// Test Left case
resultLeft := GetOrElse(func(e error) RIO.ReaderIO[testContext, int] {
return RIO.Of[testContext](0)
})(Left[testContext, int](errors.New("test")))
assert.Equal(t, 0, resultLeft(ctx)())
}
func TestMonadBiMap(t *testing.T) {
ctx := testContext{value: 10}
// Test Right case
resultRight := MonadBiMap(
Of[testContext](5),
error.Error,
strconv.Itoa,
)
assert.Equal(t, E.Of[string]("5"), resultRight(ctx)())
// Test Left case
resultLeft := MonadBiMap(
Left[testContext, int](errors.New("test")),
error.Error,
strconv.Itoa,
)
assert.Equal(t, E.Left[string]("test"), resultLeft(ctx)())
}
func TestBiMap(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](5),
BiMap[testContext](
error.Error,
strconv.Itoa,
),
)
assert.Equal(t, E.Of[string]("5"), res(ctx)())
}
func TestSwap(t *testing.T) {
ctx := testContext{value: 10}
// Test Right becomes Left
resultRight := Swap(Of[testContext](5))
res := resultRight(ctx)()
assert.True(t, E.IsLeft(res))
// Test Left becomes Right
resultLeft := Swap(Left[testContext, int](errors.New("test")))
assert.True(t, E.IsRight(resultLeft(ctx)()))
}
func TestDefer(t *testing.T) {
ctx := testContext{value: 10}
callCount := 0
res := Defer(func() ReaderIOResult[testContext, int] {
callCount++
return Of[testContext](42)
})
// First call
assert.Equal(t, result.Of(42), res(ctx)())
assert.Equal(t, 1, callCount)
// Second call
assert.Equal(t, result.Of(42), res(ctx)())
assert.Equal(t, 2, callCount)
}
func TestMonadAlt(t *testing.T) {
ctx := testContext{value: 10}
// Test first succeeds
resultFirst := MonadAlt(
Of[testContext](42),
func() ReaderIOResult[testContext, int] {
return Of[testContext](99)
},
)
assert.Equal(t, result.Of(42), resultFirst(ctx)())
// Test first fails, second succeeds
resultSecond := MonadAlt(
Left[testContext, int](errors.New("first")),
func() ReaderIOResult[testContext, int] {
return Of[testContext](99)
},
)
assert.Equal(t, result.Of(99), resultSecond(ctx)())
}
func TestAlt(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Left[testContext, int](errors.New("first")),
Alt(func() ReaderIOResult[testContext, int] {
return Of[testContext](99)
}),
)
assert.Equal(t, result.Of(99), res(ctx)())
}
func TestMemoize(t *testing.T) {
ctx := testContext{value: 10}
callCount := 0
res := Memoize(func(c testContext) IOResult[int] {
return func() E.Either[error, int] {
callCount++
return result.Of(c.value * 2)
}
})
// First call
assert.Equal(t, result.Of(20), res(ctx)())
assert.Equal(t, 1, callCount)
// Second call should use memoized value
assert.Equal(t, result.Of(20), res(ctx)())
assert.Equal(t, 1, callCount)
}
func TestMonadFlap(t *testing.T) {
ctx := testContext{value: 10}
fab := Of[testContext](func(x int) int { return x * 2 })
res := MonadFlap(fab, 5)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestFlap(t *testing.T) {
ctx := testContext{value: 10}
res := F.Pipe1(
Of[testContext](func(x int) int { return x * 2 }),
Flap[testContext, int](5),
)
assert.Equal(t, result.Of(10), res(ctx)())
}
func TestMonadMapLeft(t *testing.T) {
ctx := testContext{value: 10}
result := MonadMapLeft(
Left[testContext, int](errors.New("test")),
func(e error) string { return e.Error() + "!" },
)
res := result(ctx)()
assert.True(t, E.IsLeft(res))
// Verify the error was transformed
E.Fold(
func(s string) int {
assert.Equal(t, "test!", s)
return 0
},
func(i int) int { return i },
)(res)
}
func TestMapLeft(t *testing.T) {
ctx := testContext{value: 10}
result := F.Pipe1(
Left[testContext, int](errors.New("test")),
MapLeft[testContext, int](func(e error) string { return e.Error() + "!" }),
)
res := result(ctx)()
assert.True(t, E.IsLeft(res))
// Verify the error was transformed
E.Fold(
func(s string) int {
assert.Equal(t, "test!", s)
return 0
},
func(i int) int { return i },
)(res)
}
func TestLocal(t *testing.T) {
ctx2 := testContext{value: 20}
rdr := Asks(func(c testContext) int { return c.value })
res := Local[int](func(c testContext) testContext {
return testContext{value: c.value * 2}
})(rdr)
assert.Equal(t, result.Of(40), res(ctx2)())
}
func TestRightReaderIO(t *testing.T) {
ctx := testContext{value: 10}
rio := func(c testContext) IO[int] {
return func() int { return c.value * 2 }
}
res := RightReaderIO(rio)
assert.Equal(t, result.Of(20), res(ctx)())
}
func TestLeftReaderIO(t *testing.T) {
ctx := testContext{value: 10}
rio := func(c testContext) IO[error] {
return func() error { return errors.New("test") }
}
res := LeftReaderIO[int](rio)
assert.True(t, E.IsLeft(res(ctx)()))
}
func TestLet(t *testing.T) {
type State struct {
a int
b string
}
ctx := context.Background()
res := F.Pipe2(
Do[context.Context](State{}),
Let[context.Context](func(b string) func(State) State {
return func(s State) State { return State{a: s.a, b: b} }
}, func(s State) string { return "test" }),
Map[context.Context](func(s State) string { return s.b }),
)
assert.Equal(t, result.Of("test"), res(ctx)())
}
func TestLetTo(t *testing.T) {
type State struct {
a int
b string
}
ctx := context.Background()
res := F.Pipe2(
Do[context.Context](State{}),
LetTo[context.Context](func(b string) func(State) State {
return func(s State) State { return State{a: s.a, b: b} }
}, "constant"),
Map[context.Context](func(s State) string { return s.b }),
)
assert.Equal(t, result.Of("constant"), res(ctx)())
}
func TestBindTo(t *testing.T) {
type State struct {
value int
}
ctx := context.Background()
res := F.Pipe2(
Of[context.Context](42),
BindTo[context.Context](func(v int) State { return State{value: v} }),
Map[context.Context](func(s State) int { return s.value }),
)
assert.Equal(t, result.Of(42), res(ctx)())
}
func TestBracket(t *testing.T) {
ctx := testContext{value: 10}
released := false
res := Bracket(
Of[testContext](42),
func(x int) ReaderIOResult[testContext, string] {
return Of[testContext](fmt.Sprintf("%d", x))
},
func(x int, result E.Either[error, string]) ReaderIOResult[testContext, int] {
released = true
return Of[testContext](0)
},
)
assert.Equal(t, result.Of("42"), res(ctx)())
assert.True(t, released)
}
func TestWithResource(t *testing.T) {
ctx := testContext{value: 10}
released := false
res := WithResource[string](
Of[testContext](42),
func(x int) ReaderIOResult[testContext, int] {
released = true
return Of[testContext](0)
},
)(func(x int) ReaderIOResult[testContext, string] {
return Of[testContext](fmt.Sprintf("%d", x))
})
assert.Equal(t, result.Of("42"), res(ctx)())
assert.True(t, released)
}
func TestMonad(t *testing.T) {
m := Monad[testContext, int, string]()
assert.NotNil(t, m)
}
func TestTraverseArrayWithIndex(t *testing.T) {
ctx := testContext{value: 10}
res := TraverseArrayWithIndex(func(i int, x int) ReaderIOResult[testContext, int] {
return Of[testContext](x + i)
})([]int{1, 2, 3})
assert.Equal(t, result.Of([]int{1, 3, 5}), res(ctx)())
}
func TestTraverseRecord(t *testing.T) {
ctx := testContext{value: 10}
res := TraverseRecord[string](func(x int) ReaderIOResult[testContext, int] {
return Of[testContext](x * 2)
})(map[string]int{"a": 1, "b": 2})
expected := map[string]int{"a": 2, "b": 4}
assert.Equal(t, result.Of(expected), res(ctx)())
}
func TestTraverseRecordWithIndex(t *testing.T) {
ctx := testContext{value: 10}
res := TraverseRecordWithIndex(func(k string, x int) ReaderIOResult[testContext, string] {
return Of[testContext](fmt.Sprintf("%s:%d", k, x))
})(map[string]int{"a": 1, "b": 2})
assert.True(t, E.IsRight(res(ctx)()))
}
func TestSequenceRecord(t *testing.T) {
ctx := testContext{value: 10}
res := SequenceRecord(map[string]ReaderIOResult[testContext, int]{
"a": Of[testContext](1),
"b": Of[testContext](2),
})
expected := map[string]int{"a": 1, "b": 2}
assert.Equal(t, result.Of(expected), res(ctx)())
}
func TestSequenceT1(t *testing.T) {
ctx := testContext{value: 10}
result := SequenceT1(Of[testContext](42))
res := result(ctx)()
assert.True(t, E.IsRight(res))
}
func TestSequenceT3(t *testing.T) {
ctx := testContext{value: 10}
result := SequenceT3(
Of[testContext](1),
Of[testContext]("a"),
Of[testContext](true),
)
res := result(ctx)()
assert.True(t, E.IsRight(res))
}
func TestSequenceT4(t *testing.T) {
ctx := testContext{value: 10}
result := SequenceT4(
Of[testContext](1),
Of[testContext]("a"),
Of[testContext](true),
Of[testContext](3.14),
)
res := result(ctx)()
assert.True(t, E.IsRight(res))
}
func TestWithLock(t *testing.T) {
ctx := testContext{value: 10}
unlocked := false
res := F.Pipe1(
Of[testContext](42),
WithLock[testContext, int](func() context.CancelFunc {
return func() { unlocked = true }
}),
)
assert.Equal(t, result.Of(42), res(ctx)())
assert.True(t, unlocked)
}