1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/statereaderioeither/statereaderioeither_test.go

663 lines
17 KiB
Go
Raw Normal View History

// Copyright (c) 2024 - 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 statereaderioeither
import (
"errors"
"fmt"
"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"
N "github.com/IBM/fp-go/v2/number"
P "github.com/IBM/fp-go/v2/pair"
RE "github.com/IBM/fp-go/v2/readereither"
RIOE "github.com/IBM/fp-go/v2/readerioeither"
"github.com/stretchr/testify/assert"
)
type testState struct {
counter int
}
type testContext struct {
multiplier int
}
func TestOf(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := Of[testState, testContext, error](42)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Fold(
func(err error) bool {
t.Fatalf("Expected Right but got Left: %v", err)
return false
},
func(p P.Pair[testState, int]) bool {
assert.Equal(t, 42, P.Tail(p))
assert.Equal(t, 0, P.Head(p).counter) // State unchanged
return true
},
)(res)
}
func TestRight(t *testing.T) {
state := testState{counter: 5}
ctx := testContext{multiplier: 3}
result := Right[testState, testContext, error](100)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 100, P.Tail(p))
assert.Equal(t, 5, P.Head(p).counter)
return p
})(res)
}
func TestLeft(t *testing.T) {
state := testState{counter: 10}
ctx := testContext{multiplier: 2}
testErr := errors.New("test error")
result := Left[testState, testContext, int](testErr)
res := result(state)(ctx)()
assert.True(t, E.IsLeft(res))
}
func TestMonadMap(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := MonadMap(
Of[testState, testContext, error](21),
N.Mul(2),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestMap(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := F.Pipe1(
Of[testState, testContext, error](21),
Map[testState, testContext, error](N.Mul(2)),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestMonadChain(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := MonadChain(
Of[testState, testContext, error](5),
func(x int) StateReaderIOEither[testState, testContext, error, string] {
return Of[testState, testContext, error](fmt.Sprintf("value: %d", x))
},
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "value: 5", P.Tail(p))
return p
})(res)
}
func TestChain(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := F.Pipe1(
Of[testState, testContext, error](5),
Chain(func(x int) StateReaderIOEither[testState, testContext, error, string] {
return Of[testState, testContext, error](fmt.Sprintf("value: %d", x))
}),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "value: 5", P.Tail(p))
return p
})(res)
}
func TestMonadAp(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
fab := Of[testState, testContext, error](N.Mul(2))
fa := Of[testState, testContext, error](21)
result := MonadAp(fab, fa)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestAp(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
fa := Of[testState, testContext, error](21)
result := F.Pipe1(
Of[testState, testContext, error](N.Mul(2)),
Ap[int](fa),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestFromReaderIOEither(t *testing.T) {
state := testState{counter: 5}
ctx := testContext{multiplier: 2}
rioe := RIOE.Of[testContext, error](42)
result := FromReaderIOEither[testState](rioe)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
assert.Equal(t, 5, P.Head(p).counter) // State unchanged
return p
})(res)
}
func TestFromReaderEither(t *testing.T) {
state := testState{counter: 7}
ctx := testContext{multiplier: 3}
re := RE.Of[testContext, error](100)
result := FromReaderEither[testState](re)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 100, P.Tail(p))
assert.Equal(t, 7, P.Head(p).counter)
return p
})(res)
}
func TestFromIOEither(t *testing.T) {
state := testState{counter: 3}
ctx := testContext{multiplier: 4}
ioe := IOE.Right[error](55)
result := FromIOEither[testState, testContext](ioe)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 55, P.Tail(p))
assert.Equal(t, 3, P.Head(p).counter)
return p
})(res)
}
func TestFromState(t *testing.T) {
initialState := testState{counter: 10}
ctx := testContext{multiplier: 2}
// State computation that increments counter and returns it
stateComp := func(s testState) P.Pair[testState, int] {
newState := testState{counter: s.counter + 1}
return P.MakePair(newState, newState.counter)
}
result := FromState[testContext, error](stateComp)
res := result(initialState)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 11, P.Tail(p)) // Incremented value
assert.Equal(t, 11, P.Head(p).counter) // State updated
return p
})(res)
}
func TestFromIO(t *testing.T) {
state := testState{counter: 8}
ctx := testContext{multiplier: 2}
ioVal := func() int { return 99 }
result := FromIO[testState, testContext, error](ioVal)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 99, P.Tail(p))
assert.Equal(t, 8, P.Head(p).counter)
return p
})(res)
}
func TestFromReader(t *testing.T) {
state := testState{counter: 6}
ctx := testContext{multiplier: 5}
reader := func(c testContext) int { return c.multiplier * 10 }
result := FromReader[testState, error](reader)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 50, P.Tail(p))
assert.Equal(t, 6, P.Head(p).counter)
return p
})(res)
}
func TestFromEither(t *testing.T) {
state := testState{counter: 12}
ctx := testContext{multiplier: 3}
// Test Right case
resultRight := FromEither[testState, testContext](E.Right[error](42))
resRight := resultRight(state)(ctx)()
assert.True(t, E.IsRight(resRight))
// Test Left case
resultLeft := FromEither[testState, testContext](E.Left[int](errors.New("error")))
resLeft := resultLeft(state)(ctx)()
assert.True(t, E.IsLeft(resLeft))
}
func TestLocal(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
// Create a computation that uses the context
comp := Asks(func(c testContext) StateReaderIOEither[testState, testContext, error, int] {
return Of[testState, testContext, error](c.multiplier * 10)
})
// Modify context before running computation
result := Local[testState, error, int, int](
func(c testContext) testContext {
return testContext{multiplier: c.multiplier * 2}
},
)(comp)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 40, P.Tail(p)) // (2 * 2) * 10
return p
})(res)
}
func TestAsks(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 7}
result := Asks(func(c testContext) StateReaderIOEither[testState, testContext, error, int] {
return Of[testState, testContext, error](c.multiplier * 5)
})
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 35, P.Tail(p))
return p
})(res)
}
func TestFromEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
validate := func(x int) E.Either[error, int] {
if x > 0 {
return E.Right[error](x * 2)
}
return E.Left[int](errors.New("negative"))
}
kleisli := FromEitherK[testState, testContext](validate)
// Test with valid input
resultValid := kleisli(5)
resValid := resultValid(state)(ctx)()
assert.True(t, E.IsRight(resValid))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 10, P.Tail(p))
return p
})(resValid)
// Test with invalid input
resultInvalid := kleisli(-5)
resInvalid := resultInvalid(state)(ctx)()
assert.True(t, E.IsLeft(resInvalid))
}
func TestFromIOK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
ioFunc := func(x int) io.IO[int] {
return func() int { return x * 3 }
}
kleisli := FromIOK[testState, testContext, error](ioFunc)
result := kleisli(7)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 21, P.Tail(p))
return p
})(res)
}
func TestFromIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
ioeFunc := func(x int) IOE.IOEither[error, int] {
if x > 0 {
return IOE.Right[error](x * 4)
}
return IOE.Left[int](errors.New("invalid"))
}
kleisli := FromIOEitherK[testState, testContext](ioeFunc)
result := kleisli(3)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 12, P.Tail(p))
return p
})(res)
}
func TestFromReaderIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
rioeFunc := func(x int) RIOE.ReaderIOEither[testContext, error, int] {
return func(c testContext) IOE.IOEither[error, int] {
return IOE.Right[error](x * c.multiplier)
}
}
kleisli := FromReaderIOEitherK[testState](rioeFunc)
result := kleisli(5)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 10, P.Tail(p))
return p
})(res)
}
func TestChainEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
validate := func(x int) E.Either[error, string] {
if x > 0 {
return E.Right[error](fmt.Sprintf("valid: %d", x))
}
return E.Left[string](errors.New("invalid"))
}
result := F.Pipe1(
Of[testState, testContext, error](42),
ChainEitherK[testState, testContext](validate),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "valid: 42", P.Tail(p))
return p
})(res)
}
func TestChainIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
ioeFunc := func(x int) IOE.IOEither[error, string] {
return IOE.Right[error](fmt.Sprintf("result: %d", x))
}
result := F.Pipe1(
Of[testState, testContext, error](100),
ChainIOEitherK[testState, testContext](ioeFunc),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "result: 100", P.Tail(p))
return p
})(res)
}
func TestChainReaderIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 3}
rioeFunc := func(x int) RIOE.ReaderIOEither[testContext, error, int] {
return func(c testContext) IOE.IOEither[error, int] {
return IOE.Right[error](x * c.multiplier)
}
}
result := F.Pipe1(
Of[testState, testContext, error](5),
ChainReaderIOEitherK[testState](rioeFunc),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 15, P.Tail(p))
return p
})(res)
}
func TestDo(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
type Result struct {
value int
}
result := Do[testState, testContext, error](Result{})
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, Result]) P.Pair[testState, Result] {
assert.Equal(t, 0, P.Tail(p).value)
return p
})(res)
}
func TestBindTo(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
type Result struct {
value int
}
result := F.Pipe1(
Of[testState, testContext, error](42),
BindTo[testState, testContext, error](func(v int) Result {
return Result{value: v}
}),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, Result]) P.Pair[testState, Result] {
assert.Equal(t, 42, P.Tail(p).value)
return p
})(res)
}
func TestStatefulComputation(t *testing.T) {
initialState := testState{counter: 0}
ctx := testContext{multiplier: 10}
// Create a computation that modifies state
incrementAndGet := func(s testState) P.Pair[testState, int] {
newState := testState{counter: s.counter + 1}
return P.MakePair(newState, newState.counter)
}
// Chain multiple stateful operations
result := F.Pipe2(
FromState[testContext, error](incrementAndGet),
Chain(func(v1 int) StateReaderIOEither[testState, testContext, error, int] {
return FromState[testContext, error](incrementAndGet)
}),
Chain(func(v2 int) StateReaderIOEither[testState, testContext, error, int] {
return FromState[testContext, error](incrementAndGet)
}),
)
res := result(initialState)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 3, P.Tail(p)) // Last incremented value
assert.Equal(t, 3, P.Head(p).counter) // State updated three times
return p
})(res)
}
func TestErrorPropagation(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
testErr := errors.New("test error")
// Chain operations where the second one fails
result := F.Pipe1(
Of[testState, testContext, error](42),
Chain(func(x int) StateReaderIOEither[testState, testContext, error, int] {
return Left[testState, testContext, int](testErr)
}),
)
res := result(state)(ctx)()
assert.True(t, E.IsLeft(res))
}
func TestPointed(t *testing.T) {
p := Pointed[testState, testContext, error, int]()
assert.NotNil(t, p)
result := p.Of(42)
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
}
func TestFunctor(t *testing.T) {
f := Functor[testState, testContext, error, int, string]()
assert.NotNil(t, f)
mapper := f.Map(func(x int) string { return fmt.Sprintf("%d", x) })
result := mapper(Of[testState, testContext, error](42))
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "42", P.Tail(p))
return p
})(res)
}
func TestApplicative(t *testing.T) {
a := Applicative[testState, testContext, error, int, string]()
assert.NotNil(t, a)
fab := Of[testState, testContext, error](func(x int) string { return fmt.Sprintf("%d", x) })
fa := Of[testState, testContext, error](42)
result := a.Ap(fa)(fab)
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "42", P.Tail(p))
return p
})(res)
}
func TestMonad(t *testing.T) {
m := Monad[testState, testContext, error, int, string]()
assert.NotNil(t, m)
fa := m.Of(42)
result := m.Chain(func(x int) StateReaderIOEither[testState, testContext, error, string] {
return Of[testState, testContext, error](fmt.Sprintf("%d", x))
})(fa)
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "42", P.Tail(p))
return p
})(res)
}