1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-25 22:21:49 +02:00
Files
fp-go/v2/readerresult/reader_test.go
Dr. Carsten Leue d116317cde fix: add readerresult
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-21 10:04:28 +01:00

325 lines
8.6 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 readerresult
import (
"errors"
"fmt"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
type MyContext string
const defaultContext MyContext = "default"
var (
testError = errors.New("test error")
)
func TestFromEither(t *testing.T) {
rr := FromEither[MyContext](result.Of(42))
assert.Equal(t, result.Of(42), rr(defaultContext))
rrErr := FromEither[MyContext](result.Left[int](testError))
assert.Equal(t, result.Left[int](testError), rrErr(defaultContext))
}
func TestFromResult(t *testing.T) {
rr := FromResult[MyContext](result.Of(42))
assert.Equal(t, result.Of(42), rr(defaultContext))
}
func TestRightReader(t *testing.T) {
r := func(ctx MyContext) int { return 42 }
rr := RightReader(r)
assert.Equal(t, result.Of(42), rr(defaultContext))
}
func TestLeftReader(t *testing.T) {
r := func(ctx MyContext) error { return testError }
rr := LeftReader[int](r)
assert.Equal(t, result.Left[int](testError), rr(defaultContext))
}
func TestLeft(t *testing.T) {
rr := Left[MyContext, int](testError)
assert.Equal(t, result.Left[int](testError), rr(defaultContext))
}
func TestRight(t *testing.T) {
rr := Right[MyContext](42)
assert.Equal(t, result.Of(42), rr(defaultContext))
}
func TestOf(t *testing.T) {
rr := Of[MyContext](42)
assert.Equal(t, result.Of(42), rr(defaultContext))
}
func TestFromReader(t *testing.T) {
r := func(ctx MyContext) string { return string(ctx) }
rr := FromReader(r)
assert.Equal(t, result.Of("default"), rr(defaultContext))
}
func TestMap(t *testing.T) {
g := F.Pipe1(
Of[MyContext](1),
Map[MyContext](utils.Double),
)
assert.Equal(t, result.Of(2), g(defaultContext))
// Test with error
gErr := F.Pipe1(
Left[MyContext, int](testError),
Map[MyContext](utils.Double),
)
assert.Equal(t, result.Left[int](testError), gErr(defaultContext))
}
func TestMonadMap(t *testing.T) {
rr := Of[MyContext](5)
doubled := MonadMap(rr, func(x int) int { return x * 2 })
assert.Equal(t, result.Of(10), doubled(defaultContext))
}
func TestChain(t *testing.T) {
addOne := func(x int) ReaderResult[MyContext, int] {
return Of[MyContext](x + 1)
}
g := F.Pipe1(
Of[MyContext](5),
Chain(addOne),
)
assert.Equal(t, result.Of(6), g(defaultContext))
// Test error propagation
gErr := F.Pipe1(
Left[MyContext, int](testError),
Chain(addOne),
)
assert.Equal(t, result.Left[int](testError), gErr(defaultContext))
}
func TestMonadChain(t *testing.T) {
addOne := func(x int) ReaderResult[MyContext, int] {
return Of[MyContext](x + 1)
}
rr := Of[MyContext](5)
res := MonadChain(rr, addOne)
assert.Equal(t, result.Of(6), res(defaultContext))
}
func TestAp(t *testing.T) {
g := F.Pipe1(
Of[MyContext](utils.Double),
Ap[int](Of[MyContext](1)),
)
assert.Equal(t, result.Of(2), g(defaultContext))
}
func TestMonadAp(t *testing.T) {
add := func(x int) func(int) int {
return func(y int) int { return x + y }
}
fabr := Of[MyContext](add(5))
fa := Of[MyContext](3)
res := MonadAp(fabr, fa)
assert.Equal(t, result.Of(8), res(defaultContext))
}
func TestFromPredicate(t *testing.T) {
isPositive := FromPredicate[MyContext](
func(x int) bool { return x > 0 },
func(x int) error { return fmt.Errorf("%d is not positive", x) },
)
assert.Equal(t, result.Of(5), isPositive(5)(defaultContext))
res := isPositive(-1)(defaultContext)
assert.True(t, result.IsLeft(res))
}
func TestFold(t *testing.T) {
handleError := func(err error) reader.Reader[MyContext, string] {
return func(ctx MyContext) string { return "Error: " + err.Error() }
}
handleSuccess := func(x int) reader.Reader[MyContext, string] {
return func(ctx MyContext) string { return fmt.Sprintf("Success: %d", x) }
}
fold := Fold(handleError, handleSuccess)
res1 := fold(Of[MyContext](42))(defaultContext)
assert.Equal(t, "Success: 42", res1)
res2 := fold(Left[MyContext, int](testError))(defaultContext)
assert.Equal(t, "Error: "+testError.Error(), res2)
}
func TestGetOrElse(t *testing.T) {
defaultVal := func(err error) reader.Reader[MyContext, int] {
return func(ctx MyContext) int { return 0 }
}
getOrElse := GetOrElse(defaultVal)
res1 := getOrElse(Of[MyContext](42))(defaultContext)
assert.Equal(t, 42, res1)
res2 := getOrElse(Left[MyContext, int](testError))(defaultContext)
assert.Equal(t, 0, res2)
}
func TestOrElse(t *testing.T) {
fallback := func(err error) ReaderResult[MyContext, int] {
return Of[MyContext](99)
}
orElse := OrElse(fallback)
res1 := F.Pipe1(Of[MyContext](42), orElse)(defaultContext)
assert.Equal(t, result.Of(42), res1)
res2 := F.Pipe1(Left[MyContext, int](testError), orElse)(defaultContext)
assert.Equal(t, result.Of(99), res2)
}
func TestOrLeft(t *testing.T) {
enrichErr := func(err error) reader.Reader[MyContext, error] {
return func(ctx MyContext) error {
return fmt.Errorf("enriched: %w", err)
}
}
orLeft := OrLeft[MyContext, int](enrichErr)
res1 := F.Pipe1(Of[MyContext](42), orLeft)(defaultContext)
assert.Equal(t, result.Of(42), res1)
res2 := F.Pipe1(Left[MyContext, int](testError), orLeft)(defaultContext)
assert.True(t, result.IsLeft(res2))
}
func TestAsk(t *testing.T) {
rr := Ask[MyContext]()
assert.Equal(t, result.Of(defaultContext), rr(defaultContext))
}
func TestAsks(t *testing.T) {
getLen := func(ctx MyContext) int { return len(string(ctx)) }
rr := Asks(getLen)
assert.Equal(t, result.Of(7), rr(defaultContext)) // "default" has 7 chars
}
func TestChainEitherK(t *testing.T) {
parseInt := func(s string) result.Result[int] {
if s == "42" {
return result.Of(42)
}
return result.Left[int](errors.New("parse error"))
}
chain := ChainEitherK[MyContext](parseInt)
res1 := F.Pipe1(Of[MyContext]("42"), chain)(defaultContext)
assert.Equal(t, result.Of(42), res1)
res2 := F.Pipe1(Of[MyContext]("invalid"), chain)(defaultContext)
assert.True(t, result.IsLeft(res2))
}
func TestChainOptionK(t *testing.T) {
findEven := func(x int) option.Option[int] {
if x%2 == 0 {
return option.Some(x)
}
return option.None[int]()
}
notFound := func() error { return errors.New("not even") }
chain := ChainOptionK[MyContext, int, int](notFound)(findEven)
res1 := F.Pipe1(Of[MyContext](4), chain)(defaultContext)
assert.Equal(t, result.Of(4), res1)
res2 := F.Pipe1(Of[MyContext](3), chain)(defaultContext)
assert.True(t, result.IsLeft(res2))
}
func TestFlatten(t *testing.T) {
g := F.Pipe1(
Of[MyContext](Of[MyContext]("a")),
Flatten[MyContext, string],
)
assert.Equal(t, result.Of("a"), g(defaultContext))
}
func TestBiMap(t *testing.T) {
enrichErr := func(e error) error { return fmt.Errorf("enriched: %w", e) }
double := func(x int) int { return x * 2 }
res1 := F.Pipe1(Of[MyContext](5), BiMap[MyContext](enrichErr, double))(defaultContext)
assert.Equal(t, result.Of(10), res1)
res2 := F.Pipe1(Left[MyContext, int](testError), BiMap[MyContext](enrichErr, double))(defaultContext)
assert.True(t, result.IsLeft(res2))
}
func TestLocal(t *testing.T) {
type OtherContext int
toMyContext := func(oc OtherContext) MyContext {
return MyContext(fmt.Sprintf("ctx-%d", oc))
}
rr := Asks(func(ctx MyContext) string { return string(ctx) })
adapted := Local[string](toMyContext)(rr)
res := adapted(OtherContext(42))
assert.Equal(t, result.Of("ctx-42"), res)
}
func TestRead(t *testing.T) {
rr := Of[MyContext](42)
read := Read[int](defaultContext)
res := read(rr)
assert.Equal(t, result.Of(42), res)
}
func TestFlap(t *testing.T) {
fabr := Of[MyContext](func(x int) int { return x * 2 })
flapped := MonadFlap(fabr, 5)
assert.Equal(t, result.Of(10), flapped(defaultContext))
}
func TestMapLeft(t *testing.T) {
enrichErr := func(e error) error { return fmt.Errorf("DB error: %w", e) }
res1 := F.Pipe1(Of[MyContext](42), MapLeft[MyContext, int](enrichErr))(defaultContext)
assert.Equal(t, result.Of(42), res1)
res2 := F.Pipe1(Left[MyContext, int](testError), MapLeft[MyContext, int](enrichErr))(defaultContext)
assert.True(t, result.IsLeft(res2))
}