// Copyright (c) 2023 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 di import ( "fmt" "testing" "time" A "github.com/IBM/fp-go/array" DIE "github.com/IBM/fp-go/di/erasure" E "github.com/IBM/fp-go/either" F "github.com/IBM/fp-go/function" IOE "github.com/IBM/fp-go/ioeither" O "github.com/IBM/fp-go/option" "github.com/stretchr/testify/assert" ) var ( INJ_KEY2 = MakeToken[string]("INJ_KEY2") INJ_KEY1 = MakeToken[string]("INJ_KEY1") INJ_KEY3 = MakeToken[string]("INJ_KEY3") ) func TestSimpleProvider(t *testing.T) { var staticCount int staticValue := func(value string) IOE.IOEither[error, string] { return func() E.Either[error, string] { staticCount++ return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) } } var dynamicCount int dynamicValue := func(value string) IOE.IOEither[error, string] { return func() E.Either[error, string] { dynamicCount++ return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) } } p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten")) p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue) inj := DIE.MakeInjector(A.From(p1, p2)) i1 := Resolve(INJ_KEY1) i2 := Resolve(INJ_KEY2) res := IOE.SequenceT4( i2(inj), i1(inj), i2(inj), i1(inj), ) r := res() assert.True(t, E.IsRight(r)) assert.Equal(t, 1, staticCount) assert.Equal(t, 1, dynamicCount) } func TestOptionalProvider(t *testing.T) { var staticCount int staticValue := func(value string) IOE.IOEither[error, string] { return func() E.Either[error, string] { staticCount++ return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) } } var dynamicCount int dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] { return func() E.Either[error, string] { dynamicCount++ return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) } } p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten")) p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue) inj := DIE.MakeInjector(A.From(p1, p2)) i1 := Resolve(INJ_KEY1) i2 := Resolve(INJ_KEY2) res := IOE.SequenceT4( i2(inj), i1(inj), i2(inj), i1(inj), ) r := res() assert.True(t, E.IsRight(r)) assert.Equal(t, 1, staticCount) assert.Equal(t, 1, dynamicCount) } func TestOptionalProviderMissingDependency(t *testing.T) { var dynamicCount int dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] { return func() E.Either[error, string] { dynamicCount++ return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) } } p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue) inj := DIE.MakeInjector(A.From(p2)) i2 := Resolve(INJ_KEY2) res := IOE.SequenceT2( i2(inj), i2(inj), ) r := res() assert.True(t, E.IsRight(r)) assert.Equal(t, 1, dynamicCount) } func TestProviderMissingDependency(t *testing.T) { var dynamicCount int dynamicValue := func(value string) IOE.IOEither[error, string] { return func() E.Either[error, string] { dynamicCount++ return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) } } p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue) inj := DIE.MakeInjector(A.From(p2)) i2 := Resolve(INJ_KEY2) res := IOE.SequenceT2( i2(inj), i2(inj), ) r := res() assert.True(t, E.IsLeft(r)) assert.Equal(t, 0, dynamicCount) } func TestEagerAndLazyProvider(t *testing.T) { var staticCount int staticValue := func(value string) IOE.IOEither[error, string] { return func() E.Either[error, string] { staticCount++ return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) } } var dynamicCount int dynamicValue := func(value string) IOE.IOEither[error, string] { return func() E.Either[error, string] { dynamicCount++ return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) } } var lazyEagerCount int lazyEager := func(laz IOE.IOEither[error, string], eager string) IOE.IOEither[error, string] { return F.Pipe1( laz, IOE.Chain(func(lazValue string) IOE.IOEither[error, string] { return func() E.Either[error, string] { lazyEagerCount++ return E.Of[error](fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now())) } }), ) } p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten")) p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue) p3 := MakeProvider2(INJ_KEY3, INJ_KEY2.IOEither(), INJ_KEY1.Identity(), lazyEager) inj := DIE.MakeInjector(A.From(p1, p2, p3)) i3 := Resolve(INJ_KEY3) r := i3(inj)() fmt.Println(r) assert.True(t, E.IsRight(r)) assert.Equal(t, 1, staticCount) assert.Equal(t, 1, dynamicCount) assert.Equal(t, 1, lazyEagerCount) } func TestItemProvider(t *testing.T) { // define a multi token injMulti := MakeMultiToken[string]("configs") // provide some values v1 := ConstProvider(injMulti.Item(), "Value1") v2 := ConstProvider(injMulti.Item(), "Value2") // mix in non-multi values p1 := ConstProvider(INJ_KEY1, "Value3") p2 := ConstProvider(INJ_KEY2, "Value4") // populate the injector inj := DIE.MakeInjector(A.From(p1, v1, p2, v2)) // access the multi value multi := Resolve(injMulti.Container()) multiInj := multi(inj) value := multiInj() assert.Equal(t, E.Of[error](A.From("Value1", "Value2")), value) } func TestEmptyItemProvider(t *testing.T) { // define a multi token injMulti := MakeMultiToken[string]("configs") // mix in non-multi values p1 := ConstProvider(INJ_KEY1, "Value3") p2 := ConstProvider(INJ_KEY2, "Value4") // populate the injector inj := DIE.MakeInjector(A.From(p1, p2)) // access the multi value multi := Resolve(injMulti.Container()) multiInj := multi(inj) value := multiInj() assert.Equal(t, E.Of[error](A.Empty[string]()), value) } func TestDependencyOnMultiProvider(t *testing.T) { // define a multi token injMulti := MakeMultiToken[string]("configs") // provide some values v1 := ConstProvider(injMulti.Item(), "Value1") v2 := ConstProvider(injMulti.Item(), "Value2") // mix in non-multi values p1 := ConstProvider(INJ_KEY1, "Value3") p2 := ConstProvider(INJ_KEY2, "Value4") fromMulti := func(val string, multi []string) IOE.IOEither[error, string] { return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi)) } p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti) // populate the injector inj := DIE.MakeInjector(A.From(p1, p2, v1, v2, p3)) r3 := Resolve(INJ_KEY3) v := r3(inj)() assert.Equal(t, E.Of[error]("Val: Value3, Multi: [Value1 Value2]"), v) } func TestTokenWithDefaultProvider(t *testing.T) { // token without a default injToken1 := MakeToken[string]("Token1") // token with a default injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) // dependency injToken3 := MakeToken[string]("Token3") p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] { return IOE.Of[error](fmt.Sprintf("Token: %s", data)) }) // populate the injector inj := DIE.MakeInjector(A.From(p3)) // resolving injToken3 should work and use the default provider for injToken2 r1 := Resolve(injToken1) r3 := Resolve(injToken3) // inj1 should not be available assert.True(t, E.IsLeft(r1(inj)())) // r3 should work assert.Equal(t, E.Of[error]("Token: Carsten"), r3(inj)()) } func TestTokenWithDefaultProviderAndOverride(t *testing.T) { // token with a default injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) // dependency injToken3 := MakeToken[string]("Token3") p2 := ConstProvider(injToken2, "Override") p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] { return IOE.Of[error](fmt.Sprintf("Token: %s", data)) }) // populate the injector inj := DIE.MakeInjector(A.From(p2, p3)) // resolving injToken3 should work and use the default provider for injToken2 r3 := Resolve(injToken3) // r3 should work assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)()) }