1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/readerioeither/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

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