// 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 readerio import ( "context" "testing" F "github.com/IBM/fp-go/v2/function" "github.com/IBM/fp-go/v2/internal/utils" G "github.com/IBM/fp-go/v2/io" N "github.com/IBM/fp-go/v2/number" "github.com/IBM/fp-go/v2/reader" "github.com/stretchr/testify/assert" ) func TestMonadMap(t *testing.T) { rio := Of(5) doubled := MonadMap(rio, N.Mul(2)) result := doubled(context.Background())() assert.Equal(t, 10, result) } func TestMap(t *testing.T) { g := F.Pipe1( Of(1), Map(utils.Double), ) assert.Equal(t, 2, g(context.Background())()) } func TestMonadMapTo(t *testing.T) { rio := Of(42) replaced := MonadMapTo(rio, "constant") result := replaced(context.Background())() assert.Equal(t, "constant", result) } func TestMapTo(t *testing.T) { result := F.Pipe1( Of(42), MapTo[int]("constant"), ) assert.Equal(t, "constant", result(context.Background())()) } func TestMonadChain(t *testing.T) { rio1 := Of(5) result := MonadChain(rio1, func(n int) ReaderIO[int] { return Of(n * 3) }) assert.Equal(t, 15, result(context.Background())()) } func TestChain(t *testing.T) { result := F.Pipe1( Of(5), Chain(func(n int) ReaderIO[int] { return Of(n * 3) }), ) assert.Equal(t, 15, result(context.Background())()) } func TestMonadChainFirst(t *testing.T) { sideEffect := 0 rio := Of(42) result := MonadChainFirst(rio, func(n int) ReaderIO[string] { sideEffect = n return Of("side effect") }) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestChainFirst(t *testing.T) { sideEffect := 0 result := F.Pipe1( Of(42), ChainFirst(func(n int) ReaderIO[string] { sideEffect = n return Of("side effect") }), ) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestMonadTap(t *testing.T) { sideEffect := 0 rio := Of(42) result := MonadTap(rio, func(n int) ReaderIO[func()] { sideEffect = n return Of(func() {}) }) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestTap(t *testing.T) { sideEffect := 0 result := F.Pipe1( Of(42), Tap(func(n int) ReaderIO[func()] { sideEffect = n return Of(func() {}) }), ) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestOf(t *testing.T) { rio := Of(100) result := rio(context.Background())() assert.Equal(t, 100, result) } func TestMonadAp(t *testing.T) { fabIO := Of(N.Mul(2)) faIO := Of(5) result := MonadAp(fabIO, faIO) assert.Equal(t, 10, result(context.Background())()) } func TestAp(t *testing.T) { g := F.Pipe1( Of(utils.Double), Ap[int](Of(1)), ) assert.Equal(t, 2, g(context.Background())()) } func TestMonadApSeq(t *testing.T) { fabIO := Of(N.Add(10)) faIO := Of(5) result := MonadApSeq(fabIO, faIO) assert.Equal(t, 15, result(context.Background())()) } func TestApSeq(t *testing.T) { g := F.Pipe1( Of(N.Add(10)), ApSeq[int](Of(5)), ) assert.Equal(t, 15, g(context.Background())()) } func TestMonadApPar(t *testing.T) { fabIO := Of(N.Add(10)) faIO := Of(5) result := MonadApPar(fabIO, faIO) assert.Equal(t, 15, result(context.Background())()) } func TestApPar(t *testing.T) { g := F.Pipe1( Of(N.Add(10)), ApPar[int](Of(5)), ) assert.Equal(t, 15, g(context.Background())()) } func TestAsk(t *testing.T) { rio := Ask() ctx := context.WithValue(context.Background(), "key", "value") result := rio(ctx)() assert.Equal(t, ctx, result) } func TestFromIO(t *testing.T) { ioAction := G.Of(42) rio := FromIO(ioAction) result := rio(context.Background())() assert.Equal(t, 42, result) } func TestFromReader(t *testing.T) { rdr := func(ctx context.Context) int { return 42 } rio := FromReader(rdr) result := rio(context.Background())() assert.Equal(t, 42, result) } func TestFromLazy(t *testing.T) { lazy := func() int { return 42 } rio := FromLazy(lazy) result := rio(context.Background())() assert.Equal(t, 42, result) } func TestMonadChainIOK(t *testing.T) { rio := Of(5) result := MonadChainIOK(rio, func(n int) G.IO[int] { return G.Of(n * 4) }) assert.Equal(t, 20, result(context.Background())()) } func TestChainIOK(t *testing.T) { result := F.Pipe1( Of(5), ChainIOK(func(n int) G.IO[int] { return G.Of(n * 4) }), ) assert.Equal(t, 20, result(context.Background())()) } func TestMonadChainFirstIOK(t *testing.T) { sideEffect := 0 rio := Of(42) result := MonadChainFirstIOK(rio, func(n int) G.IO[string] { sideEffect = n return G.Of("side effect") }) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestChainFirstIOK(t *testing.T) { sideEffect := 0 result := F.Pipe1( Of(42), ChainFirstIOK(func(n int) G.IO[string] { sideEffect = n return G.Of("side effect") }), ) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestMonadTapIOK(t *testing.T) { sideEffect := 0 rio := Of(42) result := MonadTapIOK(rio, func(n int) G.IO[func()] { sideEffect = n return G.Of(func() {}) }) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestTapIOK(t *testing.T) { sideEffect := 0 result := F.Pipe1( Of(42), TapIOK(func(n int) G.IO[func()] { sideEffect = n return G.Of(func() {}) }), ) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestDefer(t *testing.T) { counter := 0 rio := Defer(func() ReaderIO[int] { counter++ return Of(counter) }) result1 := rio(context.Background())() result2 := rio(context.Background())() assert.Equal(t, 1, result1) assert.Equal(t, 2, result2) } func TestMemoize(t *testing.T) { counter := 0 rio := Of(0) memoized := Memoize(MonadMap(rio, func(int) int { counter++ return counter })) result1 := memoized(context.Background())() result2 := memoized(context.Background())() assert.Equal(t, 1, result1) assert.Equal(t, 1, result2) // Same value, memoized } func TestFlatten(t *testing.T) { nested := Of(Of(42)) flattened := Flatten(nested) result := flattened(context.Background())() assert.Equal(t, 42, result) } func TestMonadFlap(t *testing.T) { fabIO := Of(N.Mul(3)) result := MonadFlap(fabIO, 7) assert.Equal(t, 21, result(context.Background())()) } func TestFlap(t *testing.T) { result := F.Pipe1( Of(N.Mul(3)), Flap[int](7), ) assert.Equal(t, 21, result(context.Background())()) } func TestMonadChainReaderK(t *testing.T) { rio := Of(5) result := MonadChainReaderK(rio, func(n int) reader.Reader[context.Context, int] { return func(ctx context.Context) int { return n * 2 } }) assert.Equal(t, 10, result(context.Background())()) } func TestChainReaderK(t *testing.T) { result := F.Pipe1( Of(5), ChainReaderK(func(n int) reader.Reader[context.Context, int] { return func(ctx context.Context) int { return n * 2 } }), ) assert.Equal(t, 10, result(context.Background())()) } func TestMonadChainFirstReaderK(t *testing.T) { sideEffect := 0 rio := Of(42) result := MonadChainFirstReaderK(rio, func(n int) reader.Reader[context.Context, string] { return func(ctx context.Context) string { sideEffect = n return "side effect" } }) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestChainFirstReaderK(t *testing.T) { sideEffect := 0 result := F.Pipe1( Of(42), ChainFirstReaderK(func(n int) reader.Reader[context.Context, string] { return func(ctx context.Context) string { sideEffect = n return "side effect" } }), ) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestMonadTapReaderK(t *testing.T) { sideEffect := 0 rio := Of(42) result := MonadTapReaderK(rio, func(n int) reader.Reader[context.Context, func()] { return func(ctx context.Context) func() { sideEffect = n return func() {} } }) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestTapReaderK(t *testing.T) { sideEffect := 0 result := F.Pipe1( Of(42), TapReaderK(func(n int) reader.Reader[context.Context, func()] { return func(ctx context.Context) func() { sideEffect = n return func() {} } }), ) value := result(context.Background())() assert.Equal(t, 42, value) assert.Equal(t, 42, sideEffect) } func TestRead(t *testing.T) { rio := Of(42) ctx := context.Background() ioAction := Read[int](ctx)(rio) result := ioAction() assert.Equal(t, 42, result) } func TestComplexPipeline(t *testing.T) { // Test a complex pipeline combining multiple operations result := F.Pipe3( Ask(), Map(func(ctx context.Context) int { return 5 }), Chain(func(n int) ReaderIO[int] { return Of(n * 2) }), Map(N.Add(10)), ) assert.Equal(t, 20, result(context.Background())()) // (5 * 2) + 10 = 20 } func TestFromIOWithChain(t *testing.T) { ioAction := G.Of(10) result := F.Pipe1( FromIO(ioAction), Chain(func(n int) ReaderIO[int] { return Of(n + 5) }), ) assert.Equal(t, 15, result(context.Background())()) } func TestTapWithLogging(t *testing.T) { // Simulate logging scenario logged := []int{} result := F.Pipe3( Of(42), Tap(func(n int) ReaderIO[func()] { logged = append(logged, n) return Of(func() {}) }), Map(N.Mul(2)), Tap(func(n int) ReaderIO[func()] { logged = append(logged, n) return Of(func() {}) }), ) value := result(context.Background())() assert.Equal(t, 84, value) assert.Equal(t, []int{42, 84}, logged) }