diff --git a/pkgs/sdk/server/contract-tests/Representations.cs b/pkgs/sdk/server/contract-tests/Representations.cs index 9efeb5ce7..77e91f81c 100644 --- a/pkgs/sdk/server/contract-tests/Representations.cs +++ b/pkgs/sdk/server/contract-tests/Representations.cs @@ -208,12 +208,27 @@ public class SdkConfigPersistentCacheParams public class SdkConfigDataInitializerParams { public SdkConfigPollingParams Polling { get; set; } + public SdkConfigFileDataParams File { get; set; } } public class SdkConfigDataSynchronizerParams { public SdkConfigStreamingParams Streaming { get; set; } public SdkConfigPollingParams Polling { get; set; } + public SdkConfigFileDataParams File { get; set; } + public SdkConfigTestDataParams TestData { get; set; } + } + + public class SdkConfigFileDataParams + { + public string[] FilePaths { get; set; } + public bool? Persist { get; set; } + } + + public class SdkConfigTestDataParams + { + public Dictionary Flags { get; set; } + public bool? Persist { get; set; } } public class SdkConfigPollingParams @@ -244,6 +259,7 @@ public class CommandParams public RegisterFlagChangeListenerParams RegisterFlagChangeListener { get; set; } public RegisterFlagValueChangeListenerParams RegisterFlagValueChangeListener { get; set; } public UnregisterListenerParams UnregisterListener { get; set; } + public UpdateTestDataParams UpdateTestData { get; set; } } public class ContextComparisonPairParams @@ -304,6 +320,12 @@ public class UnregisterListenerParams public string ListenerId { get; set; } } + public class UpdateTestDataParams + { + public string FlagKey { get; set; } + public LdValue Value { get; set; } + } + public class ListenerNotification { public string ListenerId { get; set; } diff --git a/pkgs/sdk/server/contract-tests/SdkClientEntity.cs b/pkgs/sdk/server/contract-tests/SdkClientEntity.cs index 2007cca9d..861c7fe6f 100644 --- a/pkgs/sdk/server/contract-tests/SdkClientEntity.cs +++ b/pkgs/sdk/server/contract-tests/SdkClientEntity.cs @@ -23,6 +23,9 @@ public class SdkClientEntity private readonly Logger _log; private readonly Dictionary> _flagListeners = new Dictionary>(); + // The test data source of this client, if the configuration has a testData synchronizer. The + // harness sends at most one. + private TestData _testData; public SdkClientEntity( SdkConfigParams sdkParams, @@ -170,6 +173,21 @@ public bool DoCommand(CommandParams command, out object response) break; } + case "updateTestData": + { + if (_testData == null) + { + throw new InvalidOperationException("the client has no test data source"); + } + var p = command.UpdateTestData; + if (p?.FlagKey == null) + { + throw new ArgumentException("updateTestData requires a flag key"); + } + _testData.Update(_testData.Flag(p.FlagKey).ValueForAll(p.Value)); + break; + } + default: return false; } @@ -376,7 +394,7 @@ private ContextBuildResponse DoContextConvert(ContextConvertParams p) } } - private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapter logAdapter, string tag) + private Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapter logAdapter, string tag) { var builder = Configuration.Builder(sdkParams.Credential); @@ -617,6 +635,10 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt } initializers.Add(pollingBuilder); } + else if (initializer.File != null) + { + initializers.Add(CreateFileDataSource(initializer.File, false)); + } } if (initializers.Count > 0) { @@ -721,7 +743,45 @@ private static FileOverrideSourceBuilder BuildOverrideSource(SdkConfigOverridesP return source; } - private static IComponentConfigurer CreateSynchronizer( + private static FileDataSourceBuilder CreateFileDataSource(SdkConfigFileDataParams p, bool autoUpdate) + { + // A file data source with no files would load an empty data set, which hides LaunchDarkly data + // in memory or, when persisted, empties the store. + if (p.FilePaths == null || p.FilePaths.Length == 0 || p.FilePaths.Any(string.IsNullOrEmpty)) + { + throw new ArgumentException("a file data source requires at least one file path"); + } + var builder = FileData.DataSource().FilePaths(p.FilePaths).AutoUpdate(autoUpdate); + if (p.Persist.HasValue) + { + builder.ShouldPersist(p.Persist.Value); + } + return builder; + } + + private TestData TestDataFor(SdkConfigTestDataParams p) + { + // The updateTestData command addresses the client's test data source, so there is only one. + if (_testData != null) + { + throw new ArgumentException("the configuration has more than one testData synchronizer"); + } + _testData = TestData.DataSource(); + if (p.Persist.HasValue) + { + _testData.ShouldPersist(p.Persist.Value); + } + if (p.Flags != null) + { + foreach (var flag in p.Flags) + { + _testData.Update(_testData.Flag(flag.Key).ValueForAll(flag.Value)); + } + } + return _testData; + } + + private IComponentConfigurer CreateSynchronizer( SdkConfigDataSynchronizerParams synchronizer, string payloadFilter) { @@ -763,6 +823,15 @@ private static IComponentConfigurer CreateSynchronizer( } return streamingBuilder; } + else if (synchronizer.File != null) + { + // A file synchronizer reloads the files when they change. + return CreateFileDataSource(synchronizer.File, true); + } + else if (synchronizer.TestData != null) + { + return TestDataFor(synchronizer.TestData); + } return null; } diff --git a/pkgs/sdk/server/contract-tests/TestService.cs b/pkgs/sdk/server/contract-tests/TestService.cs index 9ce3be0b2..20d66e88f 100644 --- a/pkgs/sdk/server/contract-tests/TestService.cs +++ b/pkgs/sdk/server/contract-tests/TestService.cs @@ -52,7 +52,10 @@ public class Webapp "instance-id", "retry-conformance-fdv1-streaming", "retry-conformance-fdv1-polling", - "flag-overrides" + "flag-overrides", + "file-data-source", + "test-data-source", + "data-source-persist-opt-in" }; public readonly Handler Handler;