From 2239f27534e4e8a5332ff43e388f93e7f4e78575 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Fri, 4 Oct 2024 15:36:49 -0400 Subject: [PATCH] chore: Introduction of redis-backed daemon-mode tests --- framework/helpers/channels_test.go | 3 +- framework/ldtest/errors_test.go | 3 +- go.mod | 3 + go.sum | 8 + sdktests/custom_matchers_contexts_test.go | 3 +- sdktests/server_side_data_system_base.go | 116 +++++++++++ ...rver_side_data_system_persistence_store.go | 35 ++++ sdktests/server_side_data_system_read_only.go | 195 ++++++++++++++++++ sdktests/testapi_sdk_client.go | 9 +- sdktests/testapi_sdk_data_system.go | 46 +++++ sdktests/testsuite_entry_point.go | 1 + servicedef/sdk_config.go | 20 +- 12 files changed, 428 insertions(+), 14 deletions(-) create mode 100644 sdktests/server_side_data_system_base.go create mode 100644 sdktests/server_side_data_system_persistence_store.go create mode 100644 sdktests/server_side_data_system_read_only.go create mode 100644 sdktests/testapi_sdk_data_system.go diff --git a/framework/helpers/channels_test.go b/framework/helpers/channels_test.go index c6a2f46..cdf302d 100644 --- a/framework/helpers/channels_test.go +++ b/framework/helpers/channels_test.go @@ -4,8 +4,9 @@ import ( "testing" "time" - "github.com/launchdarkly/sdk-test-harness/v2/framework/opt" "github.com/stretchr/testify/assert" + + "github.com/launchdarkly/sdk-test-harness/v2/framework/opt" ) func TestNonBlockingSend(t *testing.T) { diff --git a/framework/ldtest/errors_test.go b/framework/ldtest/errors_test.go index bf38de2..456e38f 100644 --- a/framework/ldtest/errors_test.go +++ b/framework/ldtest/errors_test.go @@ -3,9 +3,10 @@ package ldtest import ( "testing" - "github.com/launchdarkly/sdk-test-harness/v2/framework/ldtest/internal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/launchdarkly/sdk-test-harness/v2/framework/ldtest/internal" ) func TestStacktrace(t *testing.T) { diff --git a/go.mod b/go.mod index d6d5660..0f76bbb 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,16 @@ require ( github.com/launchdarkly/go-sdk-common/v3 v3.1.0 github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.0 github.com/launchdarkly/go-test-helpers/v2 v2.3.2 + github.com/redis/go-redis/v9 v9.6.1 github.com/stretchr/testify v1.7.0 golang.org/x/exp v0.0.0-20220823124025-807a23277127 gopkg.in/yaml.v3 v3.0.0 ) require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/josharian/intern v1.0.0 // indirect github.com/launchdarkly/go-semver v1.0.2 // indirect github.com/mailru/easyjson v0.7.6 // indirect diff --git a/go.sum b/go.sum index 9f3747a..854ba58 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,12 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= @@ -32,6 +38,8 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/sdktests/custom_matchers_contexts_test.go b/sdktests/custom_matchers_contexts_test.go index e79b349..3164e90 100644 --- a/sdktests/custom_matchers_contexts_test.go +++ b/sdktests/custom_matchers_contexts_test.go @@ -4,9 +4,10 @@ import ( "encoding/json" "testing" + "github.com/stretchr/testify/require" + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" m "github.com/launchdarkly/go-test-helpers/v2/matchers" - "github.com/stretchr/testify/require" ) func TestJSONMatchesContext(t *testing.T) { diff --git a/sdktests/server_side_data_system_base.go b/sdktests/server_side_data_system_base.go new file mode 100644 index 0000000..f3fc151 --- /dev/null +++ b/sdktests/server_side_data_system_base.go @@ -0,0 +1,116 @@ +package sdktests + +import ( + "time" + + "github.com/redis/go-redis/v9" + "github.com/stretchr/testify/require" + + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/go-server-sdk-evaluation/v3/ldbuilders" + "github.com/launchdarkly/sdk-test-harness/v2/framework/ldtest" + "github.com/launchdarkly/sdk-test-harness/v2/servicedef" +) + +func doServerSideDataSystemTests(t *ldtest.T) { + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + + newServerSideDataSystemTests(t, RedisPersistenceStore{redis: rdb}).Run(t) +} + +type ServerSideDataSystemTests struct { + persistence PersistenceStore + initialFlags map[string]string +} + +func newServerSideDataSystemTests(t *ldtest.T, persistence PersistenceStore) *ServerSideDataSystemTests { + flagKeyBytes, err := + ldbuilders.NewFlagBuilder("flag-key").Version(1). + On(true).Variations(ldvalue.String("off"), ldvalue.String("match"), ldvalue.String("fallthrough")). + OffVariation(0). + FallthroughVariation(2). + Build().MarshalJSON() + require.NoError(t, err) + + initialFlags := map[string]string{"flag-key": string(flagKeyBytes)} + + uncachedFlagKeyBytes, err := + ldbuilders.NewFlagBuilder("uncached-flag-key").Version(1). + On(true).Variations(ldvalue.String("off"), ldvalue.String("match"), ldvalue.String("fallthrough")). + OffVariation(0). + FallthroughVariation(2). + Build().MarshalJSON() + require.NoError(t, err) + + initialFlags["uncached-flag-key"] = string(uncachedFlagKeyBytes) + + return &ServerSideDataSystemTests{ + persistence: persistence, + initialFlags: initialFlags, + } +} + +func (s *ServerSideDataSystemTests) Run(t *ldtest.T) { + t.Run("uses default prefix", s.usesDefaultPrefix) + t.Run("uses custom prefix", s.usesCustomPrefix) + + t.Run("read-only", s.doReadOnlyTests) +} + +func (s *ServerSideDataSystemTests) usesDefaultPrefix(t *ldtest.T) { + require.NoError(t, s.persistence.Reset()) + require.NoError(t, s.persistence.WriteData("launchdarkly:features", s.initialFlags)) + + dataSystem := NewDataSystem() + dataSystem.AddPersistence(servicedef.SDKConfigDataSystemPersistence{ + Store: servicedef.SDKConfigDataSystemPersistenceStore{ + Type: servicedef.Redis, + DSN: s.persistence.DSN(), + }, + Cache: servicedef.SDKConfigDataSystemPersistenceCache{ + Mode: servicedef.Off, + }, + }) + + client := NewSDKClient(t, dataSystem) + pollUntilFlagValueUpdated(t, client, "flag-key", ldcontext.New("user-key"), + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) +} + +func (s *ServerSideDataSystemTests) usesCustomPrefix(t *ldtest.T) { + require.NoError(t, s.persistence.Reset()) + customPrefix := "custom-prefix" + + dataSystem := NewDataSystem() + dataSystem.AddPersistence(servicedef.SDKConfigDataSystemPersistence{ + Store: servicedef.SDKConfigDataSystemPersistenceStore{ + Type: servicedef.Redis, + Prefix: customPrefix, + DSN: s.persistence.DSN(), + }, + Cache: servicedef.SDKConfigDataSystemPersistenceCache{ + Mode: servicedef.Off, + }, + }) + + client := NewSDKClient(t, dataSystem) + + require.Never( + t, + checkForUpdatedValue(t, client, "flag-key", ldcontext.New("user-key"), + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), + time.Millisecond*100, + time.Millisecond*20, + "flag value was updated, but it should not have been", + ) + + require.NoError(t, s.persistence.WriteData(customPrefix+":features", s.initialFlags)) + + pollUntilFlagValueUpdated(t, client, "flag-key", ldcontext.New("user-key"), + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) +} diff --git a/sdktests/server_side_data_system_persistence_store.go b/sdktests/server_side_data_system_persistence_store.go new file mode 100644 index 0000000..c0180c2 --- /dev/null +++ b/sdktests/server_side_data_system_persistence_store.go @@ -0,0 +1,35 @@ +package sdktests + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9" +) + +type PersistenceStore interface { + DSN() string + + WriteData(key string, data map[string]string) error + + Reset() error +} + +type RedisPersistenceStore struct { + redis *redis.Client +} + +func (r RedisPersistenceStore) DSN() string { + return fmt.Sprintf("redis://%s", r.redis.Options().Addr) +} + +func (r RedisPersistenceStore) Reset() error { + var ctx = context.Background() + return r.redis.FlushAll(ctx).Err() +} + +func (r RedisPersistenceStore) WriteData(key string, data map[string]string) error { + var ctx = context.Background() + _, err := r.redis.HSet(ctx, key, data).Result() + return err +} diff --git a/sdktests/server_side_data_system_read_only.go b/sdktests/server_side_data_system_read_only.go new file mode 100644 index 0000000..6154657 --- /dev/null +++ b/sdktests/server_side_data_system_read_only.go @@ -0,0 +1,195 @@ +package sdktests + +import ( + "time" + + "github.com/stretchr/testify/require" + + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldreason" + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + m "github.com/launchdarkly/go-test-helpers/v2/matchers" + h "github.com/launchdarkly/sdk-test-harness/v2/framework/helpers" + "github.com/launchdarkly/sdk-test-harness/v2/framework/ldtest" + o "github.com/launchdarkly/sdk-test-harness/v2/framework/opt" + "github.com/launchdarkly/sdk-test-harness/v2/servicedef" +) + +func (s *ServerSideDataSystemTests) doReadOnlyTests(t *ldtest.T) { + t.Run("daemon mode", s.daemonModeTests) +} + +func (s *ServerSideDataSystemTests) daemonModeTests(t *ldtest.T) { + t.Run("ignores database initialization flag", s.ignoresInitialization) + t.Run("can disable cache", s.canDisableCache) + t.Run("caches flag for duration", s.cachesFlagForDuration) + t.Run("caches flag forever", s.cachesFlagForever) +} + +func (s *ServerSideDataSystemTests) ignoresInitialization(t *ldtest.T) { + dataSystem := NewDataSystem() + dataSystem.AddPersistence(servicedef.SDKConfigDataSystemPersistence{ + Store: servicedef.SDKConfigDataSystemPersistenceStore{ + Type: servicedef.Redis, + DSN: s.persistence.DSN(), + }, + Cache: servicedef.SDKConfigDataSystemPersistenceCache{ + Mode: servicedef.Off, + }, + }) + context := ldcontext.New("user-key") + + require.NoError(t, s.persistence.Reset()) + client := NewSDKClient(t, dataSystem) + + h.RequireEventually(t, func() bool { + result := client.EvaluateFlag(t, servicedef.EvaluateFlagParams{ + FlagKey: "flag-key", + Context: o.Some(context), + ValueType: servicedef.ValueTypeAny, + DefaultValue: ldvalue.String("default"), + Detail: true, + }) + + return result.Reason.IsDefined() && + result.Reason.Value().GetErrorKind() == ldreason.EvalErrorFlagNotFound + }, time.Second, time.Millisecond*20, "flag was found before it should have been") + + require.NoError(t, s.persistence.WriteData("launchdarkly:features", s.initialFlags)) + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) +} + +func (s *ServerSideDataSystemTests) canDisableCache(t *ldtest.T) { + require.NoError(t, s.persistence.Reset()) + require.NoError(t, s.persistence.WriteData("launchdarkly:features", s.initialFlags)) + + dataSystem := NewDataSystem() + dataSystem.AddPersistence(servicedef.SDKConfigDataSystemPersistence{ + Store: servicedef.SDKConfigDataSystemPersistenceStore{ + Type: servicedef.Redis, + DSN: s.persistence.DSN(), + }, + Cache: servicedef.SDKConfigDataSystemPersistenceCache{ + Mode: servicedef.Off, + }, + }) + context := ldcontext.New("user-key") + + client := NewSDKClient(t, dataSystem) + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) + + // Completely reset the database so there are no valid flag definitions + require.NoError(t, s.persistence.Reset()) + + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("fallthrough"), ldvalue.String("default"), ldvalue.String("default")), + time.Second, time.Millisecond*20, "flag value was NOT updated after cache TTL") +} + +func (s *ServerSideDataSystemTests) cachesFlagForDuration(t *ldtest.T) { + dataSystem := NewDataSystem() + dataSystem.AddPersistence(servicedef.SDKConfigDataSystemPersistence{ + Store: servicedef.SDKConfigDataSystemPersistenceStore{ + Type: servicedef.Redis, + DSN: s.persistence.DSN(), + }, + Cache: servicedef.SDKConfigDataSystemPersistenceCache{ + Mode: servicedef.TTL, + TTL: o.Some(1), + }, + }) + context := ldcontext.New("user-key") + + t.Run("will cache found flag for TTL", func(t *ldtest.T) { + require.NoError(t, s.persistence.Reset()) + client := NewSDKClient(t, dataSystem) + + require.NoError(t, s.persistence.WriteData("launchdarkly:features", s.initialFlags)) + + pollUntilFlagValueUpdated(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")) + + // Completely reset the database so there are no valid flag definitions + require.NoError(t, s.persistence.Reset()) + + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("fallthrough"), ldvalue.String("default"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag value was updated before cache TTL") + + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("fallthrough"), ldvalue.String("default"), ldvalue.String("default")), + time.Second, time.Millisecond*20, "flag value was NOT updated after cache TTL") + }) + + t.Run("will cache missing flag for TTL", func(t *ldtest.T) { + require.NoError(t, s.persistence.Reset()) + client := NewSDKClient(t, dataSystem) + + result := client.EvaluateFlag(t, servicedef.EvaluateFlagParams{ + FlagKey: "flag-key", + Context: o.Some(context), + ValueType: servicedef.ValueTypeAny, + DefaultValue: ldvalue.String("default"), + Detail: true, + }) + + m.In(t).Assert(result.Value, m.Equal(ldvalue.String("default"))) + m.In(t).Assert(result.Reason.Value().GetErrorKind(), m.Equal(ldreason.EvalErrorFlagNotFound)) + + require.NoError(t, s.persistence.WriteData("launchdarkly:features", s.initialFlags)) + + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), + time.Microsecond*500, time.Millisecond*20, "flag value was updated before cache TTL") + + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), + time.Second, time.Millisecond*20, "flag value was NOT updated after cache TTL") + }) +} + +func (s *ServerSideDataSystemTests) cachesFlagForever(t *ldtest.T) { + dataSystem := NewDataSystem() + dataSystem.AddPersistence(servicedef.SDKConfigDataSystemPersistence{ + Store: servicedef.SDKConfigDataSystemPersistenceStore{ + Type: servicedef.Redis, + DSN: s.persistence.DSN(), + }, + Cache: servicedef.SDKConfigDataSystemPersistenceCache{ + Mode: servicedef.Infinite, + }, + }) + context := ldcontext.New("user-key") + + require.NoError(t, s.persistence.Reset()) + require.NoError(t, s.persistence.WriteData("launchdarkly:features", s.initialFlags)) + + client := NewSDKClient(t, dataSystem) + + h.RequireEventually(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag value was not changed") + + // Reset the store and verify that the flag value is still cached + require.NoError(t, s.persistence.Reset()) + + // Uncached key is gone, so we should NEVER see it evaluate as expected. + h.RequireNever(t, + checkForUpdatedValue(t, client, "uncached-flag-key", context, + ldvalue.String("default"), ldvalue.String("fallthrough"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "uncached-flag-key was not determined to be missing") + + // We are caching the old flag version forever, so this should also never revert to the default. + h.RequireNever(t, + checkForUpdatedValue(t, client, "flag-key", context, + ldvalue.String("fallthrough"), ldvalue.String("default"), ldvalue.String("default")), + time.Millisecond*500, time.Millisecond*20, "flag value was not changed") +} diff --git a/sdktests/testapi_sdk_client.go b/sdktests/testapi_sdk_client.go index 91fe64a..a882224 100644 --- a/sdktests/testapi_sdk_client.go +++ b/sdktests/testapi_sdk_client.go @@ -190,12 +190,19 @@ func TryNewSDKClient(t *ldtest.T, configurers ...SDKConfigurer) (*SDKClient, err } func validateSDKConfig(config servicedef.SDKConfigParams) error { - if !config.Streaming.IsDefined() && !config.Polling.IsDefined() && config.ServiceEndpoints.Value().Streaming == "" { + //nolint:godox + // TODO: I added this check about the datasystem here, but that isn't + // sufficient. We need better validation for the SDK config. + if !config.Streaming.IsDefined() && !config.Polling.IsDefined() && + !config.DataSystem.IsDefined() && config.ServiceEndpoints.Value().Streaming == "" { // Note that the default is streaming, so we don't necessarily need to set config.Streaming if there are // no other customized options and if we used serviceEndpoints.streaming to set the stream URI return errors.New( "neither streaming nor polling was enabled-- did you forget to include the SDKDataSource as a parameter?") } + //nolint:godox + // TODO: We are going to need something more like this for the datasystem + // and it's various synchronizers and initializers. if config.Streaming.IsDefined() && config.Streaming.Value().BaseURI == "" && (!config.ServiceEndpoints.IsDefined() || config.ServiceEndpoints.Value().Streaming == "") { return errors.New("streaming was enabled but base URI was not set") diff --git a/sdktests/testapi_sdk_data_system.go b/sdktests/testapi_sdk_data_system.go new file mode 100644 index 0000000..6d788df --- /dev/null +++ b/sdktests/testapi_sdk_data_system.go @@ -0,0 +1,46 @@ +package sdktests + +import ( + "errors" + + o "github.com/launchdarkly/sdk-test-harness/v2/framework/opt" + "github.com/launchdarkly/sdk-test-harness/v2/servicedef" +) + +type DataSystem struct { + synchronizers []servicedef.SDKConfigDataSystemDataSource + initializers []servicedef.SDKConfigDataSystemDataSource + persistence o.Maybe[servicedef.SDKConfigDataSystemPersistence] +} + +func NewDataSystem() *DataSystem { + return &DataSystem{} +} + +func (d *DataSystem) AddSynchronizer(synchronizer servicedef.SDKConfigDataSystemDataSource) { + d.synchronizers = append(d.synchronizers, synchronizer) +} + +func (d *DataSystem) AddInitializer(initializer servicedef.SDKConfigDataSystemDataSource) { + d.initializers = append(d.initializers, initializer) +} + +func (d *DataSystem) AddPersistence(persistence servicedef.SDKConfigDataSystemPersistence) { + d.persistence = o.Some(persistence) +} + +func (d DataSystem) Configure(target *servicedef.SDKConfigParams) error { + if len(d.synchronizers) == 0 && len(d.initializers) == 0 && !d.persistence.IsDefined() { + return errors.New("DataSystem must have at least one synchronizer, initializer, or persistence configuration") + } + + target.Streaming = o.None[servicedef.SDKConfigStreamingParams]() + target.Polling = o.None[servicedef.SDKConfigPollingParams]() + target.DataSystem = o.Some(servicedef.SDKConfigDataSystem{ + Synchronizers: d.synchronizers, + Initializers: d.initializers, + Persistence: d.persistence, + }) + + return nil +} diff --git a/sdktests/testsuite_entry_point.go b/sdktests/testsuite_entry_point.go index c0eac39..48a3955 100644 --- a/sdktests/testsuite_entry_point.go +++ b/sdktests/testsuite_entry_point.go @@ -90,6 +90,7 @@ func doAllServerSideTests(t *ldtest.T) { t.Run("secure mode hash", doServerSideSecureModeHashTests) t.Run("context type", doSDKContextTypeTests) t.Run("migrations", doServerSideMigrationTests) + t.Run("data system", doServerSideDataSystemTests) t.Run("hooks", doCommonHooksTests) t.Run("wrapper", doServerSideWrapperTests) } diff --git a/servicedef/sdk_config.go b/servicedef/sdk_config.go index 918a5ed..90bb3ff 100644 --- a/servicedef/sdk_config.go +++ b/servicedef/sdk_config.go @@ -107,13 +107,12 @@ type SDKConfigWrapper struct { } type SDKConfigDataSystem struct { - Synchronizers []SDKConfigDataSystemDataSource `json:"synchronizers,omitempty"` - Initializers []SDKConfigDataSystemDataSource `json:"initializers,omitempty"` - Persistence SDKConfigDataSystemPersistence `json:"persistence,omitempty"` + Synchronizers []SDKConfigDataSystemDataSource `json:"synchronizers,omitempty"` + Initializers []SDKConfigDataSystemDataSource `json:"initializers,omitempty"` + Persistence o.Maybe[SDKConfigDataSystemPersistence] `json:"persistence,omitempty"` } type SDKConfigDataSystemDataSource struct { - Type string `json:"type"` Streaming o.Maybe[SDKConfigStreamingParams] `json:"streaming,omitempty"` Polling o.Maybe[SDKConfigPollingParams] `json:"polling,omitempty"` } @@ -132,20 +131,21 @@ const ( ) type SDKConfigDataSystemPersistenceStore struct { - Type SDKConfigDataSystemPersistenceType `json:"type"` - DSN string `json:"dsn"` + Type SDKConfigDataSystemPersistenceType `json:"type"` + Prefix string `json:"prefix,omitempty"` + DSN string `json:"dsn"` } type SDKConfigDataSystemPersistenceMode string const ( - Off SDKConfigDataSystemPersistenceMode = "off" - TTL SDKConfigDataSystemPersistenceMode = "ttl" - Infinite SDKConfigDataSystemPersistenceMode = "infinite" + Off = SDKConfigDataSystemPersistenceMode("off") + TTL = SDKConfigDataSystemPersistenceMode("ttl") + Infinite = SDKConfigDataSystemPersistenceMode("infinite") ) type SDKConfigDataSystemPersistenceCache struct { - Mode string `json:"mode"` + Mode SDKConfigDataSystemPersistenceMode `json:"mode"` // This value is only valid when the Mode is set to TTL. It must be a positive integer. TTL o.Maybe[int] `json:"ttl,omitempty"`