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