diff --git a/README.md b/README.md index a6487cf8..61a87c52 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,14 @@ OpenFeature.Instance.SetProvider(new NoOpProvider()); var client = OpenFeature.Instance.GetClient(); // Evaluation the `my-feature` feature flag var isEnabled = await client.GetBooleanValue("my-feature", false); + +// Evaluating with a context. +var evaluationContext = EvaluationContext.Builder() + .Set("my-key", "my-value") + .Build(); + +// Evaluation the `my-conditional` feature flag +var isEnabled = await client.GetBooleanValue("my-conditional", false, evaluationContext); ``` ### Provider diff --git a/build/Common.props b/build/Common.props index aae4ba90..49ac8adf 100644 --- a/build/Common.props +++ b/build/Common.props @@ -21,4 +21,8 @@ [2.0,6.0) [1.0.0,2.0) + + + + diff --git a/src/OpenFeatureSDK/Hook.cs b/src/OpenFeatureSDK/Hook.cs index 5a478e20..12949f0a 100644 --- a/src/OpenFeatureSDK/Hook.cs +++ b/src/OpenFeatureSDK/Hook.cs @@ -31,7 +31,7 @@ public abstract class Hook public virtual Task Before(HookContext context, IReadOnlyDictionary hints = null) { - return Task.FromResult(new EvaluationContext()); + return Task.FromResult(EvaluationContext.Empty); } /// diff --git a/src/OpenFeatureSDK/Model/EvaluationContext.cs b/src/OpenFeatureSDK/Model/EvaluationContext.cs index c50561e6..3d10e81b 100644 --- a/src/OpenFeatureSDK/Model/EvaluationContext.cs +++ b/src/OpenFeatureSDK/Model/EvaluationContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; namespace OpenFeatureSDK.Model { @@ -8,9 +9,31 @@ namespace OpenFeatureSDK.Model /// to the feature flag evaluation context. /// /// Evaluation context - public class EvaluationContext + public sealed class EvaluationContext { - private readonly Structure _structure = new Structure(); + private readonly Structure _structure; + + /// + /// Internal constructor used by the builder. + /// + /// The content of the context. + internal EvaluationContext(Structure content) + { + this._structure = content; + } + + /// + /// Private constructor for making an empty . + /// + private EvaluationContext() + { + this._structure = Structure.Empty; + } + + /// + /// An empty evaluation context. + /// + public static EvaluationContext Empty { get; } = new EvaluationContext(); /// /// Gets the Value at the specified key @@ -35,15 +58,6 @@ public class EvaluationContext /// public bool ContainsKey(string key) => this._structure.ContainsKey(key); - /// - /// Removes the Value at the specified key - /// - /// The key of the value to be removed - /// - /// Thrown when the key is - /// - public void Remove(string key) => this._structure.Remove(key); - /// /// Gets the value associated with the specified key /// @@ -59,153 +73,9 @@ public class EvaluationContext /// Gets all values as a Dictionary /// /// New representation of this Structure - public IDictionary AsDictionary() - { - return new Dictionary(this._structure.AsDictionary()); - } - - /// - /// Add a new bool Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, bool value) + public IImmutableDictionary AsDictionary() { - this._structure.Add(key, value); - return this; - } - - /// - /// Add a new string Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, string value) - { - this._structure.Add(key, value); - return this; - } - - /// - /// Add a new int Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, int value) - { - this._structure.Add(key, value); - return this; - } - - /// - /// Add a new double Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, double value) - { - this._structure.Add(key, value); - return this; - } - - /// - /// Add a new DateTime Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, DateTime value) - { - this._structure.Add(key, value); - return this; - } - - /// - /// Add a new Structure Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, Structure value) - { - this._structure.Add(key, value); - return this; - } - - /// - /// Add a new List Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, List value) - { - this._structure.Add(key, value); - return this; - } - - /// - /// Add a new Value to the evaluation context - /// - /// The key of the value to be added - /// The value to be added - /// This - /// - /// Thrown when the key is - /// - /// - /// Thrown when an element with the same key is already contained in the context - /// - public EvaluationContext Add(string key, Value value) - { - this._structure.Add(key, value); - return this; + return this._structure.AsDictionary(); } /// @@ -214,32 +84,21 @@ public EvaluationContext Add(string key, Value value) public int Count => this._structure.Count; /// - /// Merges provided evaluation context into this one. - /// Any duplicate keys will be overwritten. + /// Return an enumerator for all values /// - /// - public void Merge(EvaluationContext other) + /// An enumerator for all values + public IEnumerator> GetEnumerator() { - foreach (var key in other._structure.Keys) - { - if (this._structure.ContainsKey(key)) - { - this._structure[key] = other._structure[key]; - } - else - { - this._structure.Add(key, other._structure[key]); - } - } + return this._structure.GetEnumerator(); } /// - /// Return an enumerator for all values + /// Get a builder which can build an . /// - /// An enumerator for all values - public IEnumerator> GetEnumerator() + /// The builder + public static EvaluationContextBuilder Builder() { - return this._structure.GetEnumerator(); + return new EvaluationContextBuilder(); } } } diff --git a/src/OpenFeatureSDK/Model/EvaluationContextBuilder.cs b/src/OpenFeatureSDK/Model/EvaluationContextBuilder.cs new file mode 100644 index 00000000..f5c88025 --- /dev/null +++ b/src/OpenFeatureSDK/Model/EvaluationContextBuilder.cs @@ -0,0 +1,145 @@ +using System; + +namespace OpenFeatureSDK.Model +{ + /// + /// A builder which allows the specification of attributes for an . + /// + /// A object is intended for use by a single thread and should not be used + /// from multiple threads. Once an has been created it is immutable and safe for use + /// from multiple threads. + /// + /// + public sealed class EvaluationContextBuilder + { + private readonly StructureBuilder _attributes = Structure.Builder(); + + /// + /// Internal to only allow direct creation by . + /// + internal EvaluationContextBuilder() { } + + /// + /// Set the key to the given . + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, Value value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Set the key to the given string. + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, string value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Set the key to the given int. + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, int value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Set the key to the given double. + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, double value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Set the key to the given long. + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, long value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Set the key to the given bool. + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, bool value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Set the key to the given . + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, Structure value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Set the key to the given DateTime. + /// + /// The key for the value + /// The value to set + /// This builder + public EvaluationContextBuilder Set(string key, DateTime value) + { + this._attributes.Set(key, value); + return this; + } + + /// + /// Incorporate an existing context into the builder. + /// + /// Any existing keys in the builder will be replaced by keys in the context. + /// + /// + /// The context to add merge + /// This builder + public EvaluationContextBuilder Merge(EvaluationContext context) + { + foreach (var kvp in context) + { + this.Set(kvp.Key, kvp.Value); + } + + return this; + } + + /// + /// Build an immutable . + /// + /// An immutable + public EvaluationContext Build() + { + return new EvaluationContext(this._attributes.Build()); + } + } +} diff --git a/src/OpenFeatureSDK/Model/HookContext.cs b/src/OpenFeatureSDK/Model/HookContext.cs index d97b8333..329ca180 100644 --- a/src/OpenFeatureSDK/Model/HookContext.cs +++ b/src/OpenFeatureSDK/Model/HookContext.cs @@ -65,5 +65,17 @@ public HookContext(string flagKey, this.ProviderMetadata = providerMetadata ?? throw new ArgumentNullException(nameof(providerMetadata)); this.EvaluationContext = evaluationContext ?? throw new ArgumentNullException(nameof(evaluationContext)); } + + internal HookContext WithNewEvaluationContext(EvaluationContext context) + { + return new HookContext( + this.FlagKey, + this.DefaultValue, + this.FlagValueType, + this.ClientMetadata, + this.ProviderMetadata, + context + ); + } } } diff --git a/src/OpenFeatureSDK/Model/Structure.cs b/src/OpenFeatureSDK/Model/Structure.cs index eec68240..c2d0ba8d 100644 --- a/src/OpenFeatureSDK/Model/Structure.cs +++ b/src/OpenFeatureSDK/Model/Structure.cs @@ -1,6 +1,6 @@ -using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; namespace OpenFeatureSDK.Model @@ -8,25 +8,38 @@ namespace OpenFeatureSDK.Model /// /// Structure represents a map of Values /// - public class Structure : IEnumerable> + public sealed class Structure : IEnumerable> { - private readonly Dictionary _attributes; + private readonly ImmutableDictionary _attributes; /// - /// Creates a new structure with an empty set of attributes + /// Internal constructor for use by the builder. /// - public Structure() + internal Structure(ImmutableDictionary attributes) { - this._attributes = new Dictionary(); + this._attributes = attributes; } + /// + /// Private constructor for creating an empty . + /// + private Structure() + { + this._attributes = ImmutableDictionary.Empty; + } + + /// + /// An empty structure. + /// + public static Structure Empty { get; } = new Structure(); + /// /// Creates a new structure with the supplied attributes /// /// public Structure(IDictionary attributes) { - this._attributes = new Dictionary(attributes); + this._attributes = ImmutableDictionary.CreateRange(attributes); } /// @@ -43,13 +56,6 @@ public Structure(IDictionary attributes) /// indicating the presence of the key. public bool ContainsKey(string key) => this._attributes.ContainsKey(key); - /// - /// Removes the Value at the specified key - /// - /// The key of the value to be retrieved - /// indicating the presence of the key. - public bool Remove(string key) => this._attributes.Remove(key); - /// /// Gets the value associated with the specified key by mutating the supplied value. /// @@ -62,9 +68,9 @@ public Structure(IDictionary attributes) /// Gets all values as a Dictionary /// /// New representation of this Structure - public IDictionary AsDictionary() + public IImmutableDictionary AsDictionary() { - return new Dictionary(this._attributes); + return this._attributes; } /// @@ -74,114 +80,17 @@ public IDictionary AsDictionary() public Value this[string key] { get => this._attributes[key]; - set => this._attributes[key] = value; - } - - /// - /// Return a collection containing all the keys in this structure - /// - public ICollection Keys => this._attributes.Keys; - - /// - /// Return a collection containing all the values in this structure - /// - public ICollection Values => this._attributes.Values; - - /// - /// Add a new bool Value to the structure - /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, bool value) - { - this._attributes.Add(key, new Value(value)); - return this; - } - - /// - /// Add a new string Value to the structure - /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, string value) - { - this._attributes.Add(key, new Value(value)); - return this; - } - - /// - /// Add a new int Value to the structure - /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, int value) - { - this._attributes.Add(key, new Value(value)); - return this; } /// - /// Add a new double Value to the structure + /// Return a list containing all the keys in this structure /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, double value) - { - this._attributes.Add(key, new Value(value)); - return this; - } + public IImmutableList Keys => this._attributes.Keys.ToImmutableList(); /// - /// Add a new DateTime Value to the structure + /// Return an enumerable containing all the values in this structure /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, DateTime value) - { - this._attributes.Add(key, new Value(value)); - return this; - } - - /// - /// Add a new Structure Value to the structure - /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, Structure value) - { - this._attributes.Add(key, new Value(value)); - return this; - } - - /// - /// Add a new List Value to the structure - /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, IList value) - { - this._attributes.Add(key, new Value(value)); - return this; - } - - /// - /// Add a new Value to the structure - /// - /// The key of the value to be retrieved - /// The value to be added - /// This - public Structure Add(string key, Value value) - { - this._attributes.Add(key, new Value(value)); - return this; - } + public IImmutableList Values => this._attributes.Values.ToImmutableList(); /// /// Return a count of all values @@ -197,6 +106,15 @@ public IEnumerator> GetEnumerator() return this._attributes.GetEnumerator(); } + /// + /// Get a builder which can build a . + /// + /// The builder + public static StructureBuilder Builder() + { + return new StructureBuilder(); + } + [ExcludeFromCodeCoverage] IEnumerator IEnumerable.GetEnumerator() { diff --git a/src/OpenFeatureSDK/Model/StructureBuilder.cs b/src/OpenFeatureSDK/Model/StructureBuilder.cs new file mode 100644 index 00000000..5fba1422 --- /dev/null +++ b/src/OpenFeatureSDK/Model/StructureBuilder.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace OpenFeatureSDK.Model +{ + /// + /// A builder which allows the specification of attributes for a . + /// + /// A object is intended for use by a single thread and should not be used from + /// multiple threads. Once a has been created it is immutable and safe for use from + /// multiple threads. + /// + /// + public sealed class StructureBuilder + { + private readonly ImmutableDictionary.Builder _attributes = + ImmutableDictionary.CreateBuilder(); + + /// + /// Internal to only allow direct creation by . + /// + internal StructureBuilder() { } + + /// + /// Set the key to the given . + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, Value value) + { + // Remove the attribute. Will not throw an exception if not present. + this._attributes.Remove(key); + this._attributes.Add(key, value); + return this; + } + + /// + /// Set the key to the given string. + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, string value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Set the key to the given int. + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, int value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Set the key to the given double. + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, double value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Set the key to the given long. + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, long value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Set the key to the given bool. + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, bool value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Set the key to the given . + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, Structure value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Set the key to the given DateTime. + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, DateTime value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Set the key to the given list. + /// + /// The key for the value + /// The value to set + /// This builder + public StructureBuilder Set(string key, IList value) + { + this.Set(key, new Value(value)); + return this; + } + + /// + /// Build an immutable / + /// + /// The built + public Structure Build() + { + return new Structure(this._attributes.ToImmutable()); + } + } +} diff --git a/src/OpenFeatureSDK/Model/Value.cs b/src/OpenFeatureSDK/Model/Value.cs index 308c698a..10ce3c7f 100644 --- a/src/OpenFeatureSDK/Model/Value.cs +++ b/src/OpenFeatureSDK/Model/Value.cs @@ -1,6 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; namespace OpenFeatureSDK.Model { @@ -8,7 +8,7 @@ namespace OpenFeatureSDK.Model /// Values serve as a return type for provider objects. Providers may deal in JSON, protobuf, XML or some other data-interchange format. /// This intermediate representation provides a good medium of exchange. /// - public class Value + public sealed class Value { private readonly object _innerValue; @@ -23,6 +23,10 @@ public class Value /// The object to set as the inner value public Value(Object value) { + if (value is IList list) + { + value = list.ToImmutableList(); + } // integer is a special case, convert those. this._innerValue = value is int ? Convert.ToDouble(value) : value; if (!(this.IsNull @@ -77,8 +81,8 @@ public Value(Object value) /// /// Creates a Value with the inner set to list type /// - /// List type - public Value(IList value) => this._innerValue = value; + /// List type + public Value(IList value) => this._innerValue = value.ToImmutableList(); /// /// Creates a Value with the inner set to DateTime type @@ -120,7 +124,7 @@ public Value(Object value) /// Determines if inner value is list /// /// True if value is list - public bool IsList => this._innerValue is IList; + public bool IsList => this._innerValue is IImmutableList; /// /// Determines if inner value is DateTime @@ -174,7 +178,7 @@ public Value(Object value) /// Value will be null if it isn't a List /// /// Value as List - public IList AsList => this.IsList ? (IList)this._innerValue : null; + public IImmutableList AsList => this.IsList ? (IImmutableList)this._innerValue : null; /// /// Returns the underlying DateTime value diff --git a/src/OpenFeatureSDK/OpenFeature.cs b/src/OpenFeatureSDK/OpenFeature.cs index 8ea95048..42b23dcf 100644 --- a/src/OpenFeatureSDK/OpenFeature.cs +++ b/src/OpenFeatureSDK/OpenFeature.cs @@ -11,7 +11,7 @@ namespace OpenFeatureSDK /// public sealed class OpenFeature { - private EvaluationContext _evaluationContext = new EvaluationContext(); + private EvaluationContext _evaluationContext = EvaluationContext.Empty; private FeatureProvider _featureProvider = new NoOpFeatureProvider(); private readonly List _hooks = new List(); @@ -82,7 +82,7 @@ public FeatureClient GetClient(string name = null, string version = null, ILogge /// Sets the global /// /// - public void SetContext(EvaluationContext context) => this._evaluationContext = context ?? new EvaluationContext(); + public void SetContext(EvaluationContext context) => this._evaluationContext = context ?? EvaluationContext.Empty; /// /// Gets the global diff --git a/src/OpenFeatureSDK/OpenFeatureClient.cs b/src/OpenFeatureSDK/OpenFeatureClient.cs index a105644a..3d4120b7 100644 --- a/src/OpenFeatureSDK/OpenFeatureClient.cs +++ b/src/OpenFeatureSDK/OpenFeatureClient.cs @@ -47,7 +47,7 @@ public FeatureClient(FeatureProvider featureProvider, string name, string versio this._featureProvider = featureProvider ?? throw new ArgumentNullException(nameof(featureProvider)); this._metadata = new ClientMetadata(name, version); this._logger = logger ?? new Logger(new NullLoggerFactory()); - this._evaluationContext = context ?? new EvaluationContext(); + this._evaluationContext = context ?? EvaluationContext.Empty; } /// @@ -213,13 +213,15 @@ private async Task> EvaluateFlag( // New up a evaluation context if one was not provided. if (context == null) { - context = new EvaluationContext(); + context = EvaluationContext.Empty; } // merge api, client, and invocation context. var evaluationContext = OpenFeature.Instance.GetContext(); - evaluationContext.Merge(this.GetContext()); - evaluationContext.Merge(context); + var evaluationContextBuilder = EvaluationContext.Builder(); + evaluationContextBuilder.Merge(evaluationContext); + evaluationContextBuilder.Merge(this.GetContext()); + evaluationContextBuilder.Merge(context); var allHooks = new List() .Concat(OpenFeature.Instance.GetHooks()) @@ -240,16 +242,16 @@ private async Task> EvaluateFlag( defaultValue, flagValueType, this._metadata, OpenFeature.Instance.GetProviderMetadata(), - evaluationContext + evaluationContextBuilder.Build() ); FlagEvaluationDetails evaluation; try { - await this.TriggerBeforeHooks(allHooks, hookContext, options); + var contextFromHooks = await this.TriggerBeforeHooks(allHooks, hookContext, options); evaluation = - (await resolveValueDelegate.Invoke(flagKey, defaultValue, hookContext.EvaluationContext)) + (await resolveValueDelegate.Invoke(flagKey, defaultValue, contextFromHooks.EvaluationContext)) .ToFlagEvaluationDetails(); await this.TriggerAfterHooks(allHooksReversed, hookContext, evaluation, options); @@ -277,15 +279,19 @@ private async Task> EvaluateFlag( return evaluation; } - private async Task TriggerBeforeHooks(IReadOnlyList hooks, HookContext context, + private async Task> TriggerBeforeHooks(IReadOnlyList hooks, HookContext context, FlagEvaluationOptions options) { + var evalContextBuilder = EvaluationContext.Builder(); + evalContextBuilder.Merge(context.EvaluationContext); + foreach (var hook in hooks) { var resp = await hook.Before(context, options?.HookHints); if (resp != null) { - context.EvaluationContext.Merge(resp); + evalContextBuilder.Merge(resp); + context = context.WithNewEvaluationContext(evalContextBuilder.Build()); } else { @@ -293,6 +299,8 @@ private async Task TriggerBeforeHooks(IReadOnlyList hooks, HookContext< hook.GetType().Name); } } + + return context.WithNewEvaluationContext(evalContextBuilder.Build()); } private async Task TriggerAfterHooks(IReadOnlyList hooks, HookContext context, diff --git a/test/OpenFeatureSDK.Tests/OpenFeatureClientTests.cs b/test/OpenFeatureSDK.Tests/OpenFeatureClientTests.cs index 45a19200..6babbdf5 100644 --- a/test/OpenFeatureSDK.Tests/OpenFeatureClientTests.cs +++ b/test/OpenFeatureSDK.Tests/OpenFeatureClientTests.cs @@ -7,7 +7,6 @@ using Moq; using OpenFeatureSDK.Constant; using OpenFeatureSDK.Error; -using OpenFeatureSDK.Extension; using OpenFeatureSDK.Model; using OpenFeatureSDK.Tests.Internal; using Xunit; @@ -75,24 +74,24 @@ public async Task OpenFeatureClient_Should_Allow_Flag_Evaluation() var client = OpenFeature.Instance.GetClient(clientName, clientVersion); (await client.GetBooleanValue(flagName, defaultBoolValue)).Should().Be(defaultBoolValue); - (await client.GetBooleanValue(flagName, defaultBoolValue, new EvaluationContext())).Should().Be(defaultBoolValue); - (await client.GetBooleanValue(flagName, defaultBoolValue, new EvaluationContext(), emptyFlagOptions)).Should().Be(defaultBoolValue); + (await client.GetBooleanValue(flagName, defaultBoolValue, EvaluationContext.Empty)).Should().Be(defaultBoolValue); + (await client.GetBooleanValue(flagName, defaultBoolValue, EvaluationContext.Empty, emptyFlagOptions)).Should().Be(defaultBoolValue); (await client.GetIntegerValue(flagName, defaultIntegerValue)).Should().Be(defaultIntegerValue); - (await client.GetIntegerValue(flagName, defaultIntegerValue, new EvaluationContext())).Should().Be(defaultIntegerValue); - (await client.GetIntegerValue(flagName, defaultIntegerValue, new EvaluationContext(), emptyFlagOptions)).Should().Be(defaultIntegerValue); + (await client.GetIntegerValue(flagName, defaultIntegerValue, EvaluationContext.Empty)).Should().Be(defaultIntegerValue); + (await client.GetIntegerValue(flagName, defaultIntegerValue, EvaluationContext.Empty, emptyFlagOptions)).Should().Be(defaultIntegerValue); (await client.GetDoubleValue(flagName, defaultDoubleValue)).Should().Be(defaultDoubleValue); - (await client.GetDoubleValue(flagName, defaultDoubleValue, new EvaluationContext())).Should().Be(defaultDoubleValue); - (await client.GetDoubleValue(flagName, defaultDoubleValue, new EvaluationContext(), emptyFlagOptions)).Should().Be(defaultDoubleValue); + (await client.GetDoubleValue(flagName, defaultDoubleValue, EvaluationContext.Empty)).Should().Be(defaultDoubleValue); + (await client.GetDoubleValue(flagName, defaultDoubleValue, EvaluationContext.Empty, emptyFlagOptions)).Should().Be(defaultDoubleValue); (await client.GetStringValue(flagName, defaultStringValue)).Should().Be(defaultStringValue); - (await client.GetStringValue(flagName, defaultStringValue, new EvaluationContext())).Should().Be(defaultStringValue); - (await client.GetStringValue(flagName, defaultStringValue, new EvaluationContext(), emptyFlagOptions)).Should().Be(defaultStringValue); + (await client.GetStringValue(flagName, defaultStringValue, EvaluationContext.Empty)).Should().Be(defaultStringValue); + (await client.GetStringValue(flagName, defaultStringValue, EvaluationContext.Empty, emptyFlagOptions)).Should().Be(defaultStringValue); (await client.GetObjectValue(flagName, defaultStructureValue)).Should().BeEquivalentTo(defaultStructureValue); - (await client.GetObjectValue(flagName, defaultStructureValue, new EvaluationContext())).Should().BeEquivalentTo(defaultStructureValue); - (await client.GetObjectValue(flagName, defaultStructureValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(defaultStructureValue); + (await client.GetObjectValue(flagName, defaultStructureValue, EvaluationContext.Empty)).Should().BeEquivalentTo(defaultStructureValue); + (await client.GetObjectValue(flagName, defaultStructureValue, EvaluationContext.Empty, emptyFlagOptions)).Should().BeEquivalentTo(defaultStructureValue); } [Fact] @@ -122,28 +121,28 @@ public async Task OpenFeatureClient_Should_Allow_Details_Flag_Evaluation() var boolFlagEvaluationDetails = new FlagEvaluationDetails(flagName, defaultBoolValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant); (await client.GetBooleanDetails(flagName, defaultBoolValue)).Should().BeEquivalentTo(boolFlagEvaluationDetails); - (await client.GetBooleanDetails(flagName, defaultBoolValue, new EvaluationContext())).Should().BeEquivalentTo(boolFlagEvaluationDetails); - (await client.GetBooleanDetails(flagName, defaultBoolValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(boolFlagEvaluationDetails); + (await client.GetBooleanDetails(flagName, defaultBoolValue, EvaluationContext.Empty)).Should().BeEquivalentTo(boolFlagEvaluationDetails); + (await client.GetBooleanDetails(flagName, defaultBoolValue, EvaluationContext.Empty, emptyFlagOptions)).Should().BeEquivalentTo(boolFlagEvaluationDetails); var integerFlagEvaluationDetails = new FlagEvaluationDetails(flagName, defaultIntegerValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant); (await client.GetIntegerDetails(flagName, defaultIntegerValue)).Should().BeEquivalentTo(integerFlagEvaluationDetails); - (await client.GetIntegerDetails(flagName, defaultIntegerValue, new EvaluationContext())).Should().BeEquivalentTo(integerFlagEvaluationDetails); - (await client.GetIntegerDetails(flagName, defaultIntegerValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(integerFlagEvaluationDetails); + (await client.GetIntegerDetails(flagName, defaultIntegerValue, EvaluationContext.Empty)).Should().BeEquivalentTo(integerFlagEvaluationDetails); + (await client.GetIntegerDetails(flagName, defaultIntegerValue, EvaluationContext.Empty, emptyFlagOptions)).Should().BeEquivalentTo(integerFlagEvaluationDetails); var doubleFlagEvaluationDetails = new FlagEvaluationDetails(flagName, defaultDoubleValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant); (await client.GetDoubleDetails(flagName, defaultDoubleValue)).Should().BeEquivalentTo(doubleFlagEvaluationDetails); - (await client.GetDoubleDetails(flagName, defaultDoubleValue, new EvaluationContext())).Should().BeEquivalentTo(doubleFlagEvaluationDetails); - (await client.GetDoubleDetails(flagName, defaultDoubleValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(doubleFlagEvaluationDetails); + (await client.GetDoubleDetails(flagName, defaultDoubleValue, EvaluationContext.Empty)).Should().BeEquivalentTo(doubleFlagEvaluationDetails); + (await client.GetDoubleDetails(flagName, defaultDoubleValue, EvaluationContext.Empty, emptyFlagOptions)).Should().BeEquivalentTo(doubleFlagEvaluationDetails); var stringFlagEvaluationDetails = new FlagEvaluationDetails(flagName, defaultStringValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant); (await client.GetStringDetails(flagName, defaultStringValue)).Should().BeEquivalentTo(stringFlagEvaluationDetails); - (await client.GetStringDetails(flagName, defaultStringValue, new EvaluationContext())).Should().BeEquivalentTo(stringFlagEvaluationDetails); - (await client.GetStringDetails(flagName, defaultStringValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(stringFlagEvaluationDetails); + (await client.GetStringDetails(flagName, defaultStringValue, EvaluationContext.Empty)).Should().BeEquivalentTo(stringFlagEvaluationDetails); + (await client.GetStringDetails(flagName, defaultStringValue, EvaluationContext.Empty, emptyFlagOptions)).Should().BeEquivalentTo(stringFlagEvaluationDetails); var structureFlagEvaluationDetails = new FlagEvaluationDetails(flagName, defaultStructureValue, ErrorType.None, NoOpProvider.ReasonNoOp, NoOpProvider.Variant); (await client.GetObjectDetails(flagName, defaultStructureValue)).Should().BeEquivalentTo(structureFlagEvaluationDetails); - (await client.GetObjectDetails(flagName, defaultStructureValue, new EvaluationContext())).Should().BeEquivalentTo(structureFlagEvaluationDetails); - (await client.GetObjectDetails(flagName, defaultStructureValue, new EvaluationContext(), emptyFlagOptions)).Should().BeEquivalentTo(structureFlagEvaluationDetails); + (await client.GetObjectDetails(flagName, defaultStructureValue, EvaluationContext.Empty)).Should().BeEquivalentTo(structureFlagEvaluationDetails); + (await client.GetObjectDetails(flagName, defaultStructureValue, EvaluationContext.Empty, emptyFlagOptions)).Should().BeEquivalentTo(structureFlagEvaluationDetails); } [Fact] @@ -393,7 +392,7 @@ public void Should_Get_And_Set_Context() var KEY = "key"; var VAL = 1; FeatureClient client = OpenFeature.Instance.GetClient(); - client.SetContext(new EvaluationContext().Add(KEY, VAL)); + client.SetContext(new EvaluationContextBuilder().Set(KEY, VAL).Build()); Assert.Equal(VAL, client.GetContext().GetValue(KEY).AsInteger); } } diff --git a/test/OpenFeatureSDK.Tests/OpenFeatureEvaluationContextTests.cs b/test/OpenFeatureSDK.Tests/OpenFeatureEvaluationContextTests.cs index 8c743568..2dc90710 100644 --- a/test/OpenFeatureSDK.Tests/OpenFeatureEvaluationContextTests.cs +++ b/test/OpenFeatureSDK.Tests/OpenFeatureEvaluationContextTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using AutoFixture; using FluentAssertions; using OpenFeatureSDK.Model; @@ -13,12 +12,12 @@ public class OpenFeatureEvaluationContextTests [Fact] public void Should_Merge_Two_Contexts() { - var context1 = new EvaluationContext() - .Add("key1", "value1"); - var context2 = new EvaluationContext() - .Add("key2", "value2"); + var contextBuilder1 = new EvaluationContextBuilder() + .Set("key1", "value1"); + var contextBuilder2 = new EvaluationContextBuilder() + .Set("key2", "value2"); - context1.Merge(context2); + var context1 = contextBuilder1.Merge(contextBuilder2.Build()).Build(); Assert.Equal(2, context1.Count); Assert.Equal("value1", context1.GetValue("key1").AsString); @@ -29,21 +28,18 @@ public void Should_Merge_Two_Contexts() [Specification("3.2.2", "Duplicate values being overwritten.")] public void Should_Merge_TwoContexts_And_Override_Duplicates_With_RightHand_Context() { - var context1 = new EvaluationContext(); - var context2 = new EvaluationContext(); + var contextBuilder1 = new EvaluationContextBuilder(); + var contextBuilder2 = new EvaluationContextBuilder(); - context1.Add("key1", "value1"); - context2.Add("key1", "overriden_value"); - context2.Add("key2", "value2"); + contextBuilder1.Set("key1", "value1"); + contextBuilder2.Set("key1", "overriden_value"); + contextBuilder2.Set("key2", "value2"); - context1.Merge(context2); + var context1 = contextBuilder1.Merge(contextBuilder2.Build()).Build(); Assert.Equal(2, context1.Count); Assert.Equal("overriden_value", context1.GetValue("key1").AsString); Assert.Equal("value2", context1.GetValue("key2").AsString); - - context1.Remove("key1"); - Assert.Throws(() => context1.GetValue("key1")); } [Fact] @@ -54,13 +50,15 @@ public void EvaluationContext_Should_All_Types() var fixture = new Fixture(); var now = fixture.Create(); var structure = fixture.Create(); - var context = new EvaluationContext() - .Add("key1", "value") - .Add("key2", 1) - .Add("key3", true) - .Add("key4", now) - .Add("key5", structure) - .Add("key6", 1.0); + var contextBuilder = new EvaluationContextBuilder() + .Set("key1", "value") + .Set("key2", 1) + .Set("key3", true) + .Set("key4", now) + .Set("key5", structure) + .Set("key6", 1.0); + + var context = contextBuilder.Build(); var value1 = context.GetValue("key1"); value1.IsString.Should().BeTrue(); @@ -89,24 +87,24 @@ public void EvaluationContext_Should_All_Types() [Fact] [Specification("3.1.4", "The evaluation context fields MUST have an unique key.")] - public void When_Duplicate_Key_Throw_Unique_Constraint() + public void When_Duplicate_Key_Set_It_Replaces_Value() { - var context = new EvaluationContext().Add("key", "value"); - var exception = Assert.Throws(() => - context.Add("key", "overriden_value")); - exception.Message.Should().StartWith("An item with the same key has already been added."); + var contextBuilder = new EvaluationContextBuilder().Set("key", "value"); + contextBuilder.Set("key", "overriden_value"); + Assert.Equal("overriden_value", contextBuilder.Build().GetValue("key").AsString); } [Fact] [Specification("3.1.3", "The evaluation context MUST support fetching the custom fields by key and also fetching all key value pairs.")] public void Should_Be_Able_To_Get_All_Values() { - var context = new EvaluationContext() - .Add("key1", "value1") - .Add("key2", "value2") - .Add("key3", "value3") - .Add("key4", "value4") - .Add("key5", "value5"); + var context = new EvaluationContextBuilder() + .Set("key1", "value1") + .Set("key2", "value2") + .Set("key3", "value3") + .Set("key4", "value4") + .Set("key5", "value5") + .Build(); // Iterate over key value pairs and check consistency var count = 0; diff --git a/test/OpenFeatureSDK.Tests/OpenFeatureHookTests.cs b/test/OpenFeatureSDK.Tests/OpenFeatureHookTests.cs index 8266e2d7..0c97e18b 100644 --- a/test/OpenFeatureSDK.Tests/OpenFeatureHookTests.cs +++ b/test/OpenFeatureSDK.Tests/OpenFeatureHookTests.cs @@ -33,19 +33,19 @@ public async Task Hooks_Should_Be_Called_In_Order() apiHook.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), It.IsAny>())) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); clientHook.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), It.IsAny>())) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); invocationHook.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), It.IsAny>())) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); providerHook.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), It.IsAny>())) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); providerHook.InSequence(sequence).Setup(x => x.After(It.IsAny>(), It.IsAny>(), @@ -82,7 +82,7 @@ public async Task Hooks_Should_Be_Called_In_Order() var client = OpenFeature.Instance.GetClient(clientName, clientVersion); client.AddHooks(clientHook.Object); - await client.GetBooleanValue(flagName, defaultValue, new EvaluationContext(), + await client.GetBooleanValue(flagName, defaultValue, EvaluationContext.Empty, new FlagEvaluationOptions(invocationHook.Object, new Dictionary())); apiHook.Verify(x => x.Before( @@ -127,19 +127,19 @@ public async Task Hooks_Should_Be_Called_In_Order() public void Hook_Context_Should_Not_Allow_Nulls() { Assert.Throws(() => - new HookContext(null, new Structure(), FlagValueType.Object, new ClientMetadata(null, null), - new Metadata(null), new EvaluationContext())); + new HookContext(null, Structure.Empty, FlagValueType.Object, new ClientMetadata(null, null), + new Metadata(null), EvaluationContext.Empty)); Assert.Throws(() => - new HookContext("test", new Structure(), FlagValueType.Object, null, - new Metadata(null), new EvaluationContext())); + new HookContext("test", Structure.Empty, FlagValueType.Object, null, + new Metadata(null), EvaluationContext.Empty)); Assert.Throws(() => - new HookContext("test", new Structure(), FlagValueType.Object, new ClientMetadata(null, null), - null, new EvaluationContext())); + new HookContext("test", Structure.Empty, FlagValueType.Object, new ClientMetadata(null, null), + null, EvaluationContext.Empty)); Assert.Throws(() => - new HookContext("test", new Structure(), FlagValueType.Object, new ClientMetadata(null, null), + new HookContext("test", Structure.Empty, FlagValueType.Object, new ClientMetadata(null, null), new Metadata(null), null)); } @@ -150,9 +150,9 @@ public void Hook_Context_Should_Have_Properties_And_Be_Immutable() { var clientMetadata = new ClientMetadata("client", "1.0.0"); var providerMetadata = new Metadata("provider"); - var testStructure = new Structure(); + var testStructure = Structure.Empty; var context = new HookContext("test", testStructure, FlagValueType.Object, clientMetadata, - providerMetadata, new EvaluationContext()); + providerMetadata, EvaluationContext.Empty); context.ClientMetadata.Should().BeSameAs(clientMetadata); context.ProviderMetadata.Should().BeSameAs(providerMetadata); @@ -166,7 +166,7 @@ public void Hook_Context_Should_Have_Properties_And_Be_Immutable() [Specification("4.3.3", "Any `evaluation context` returned from a `before` hook MUST be passed to subsequent `before` hooks (via `HookContext`).")] public async Task Evaluation_Context_Must_Be_Mutable_Before_Hook() { - var evaluationContext = new EvaluationContext().Add("test", "test"); + var evaluationContext = new EvaluationContextBuilder().Set("test", "test").Build(); var hook1 = new Mock(MockBehavior.Strict); var hook2 = new Mock(MockBehavior.Strict); var hookContext = new HookContext("test", false, @@ -181,7 +181,7 @@ public async Task Evaluation_Context_Must_Be_Mutable_Before_Hook() .ReturnsAsync(evaluationContext); var client = OpenFeature.Instance.GetClient("test", "1.0.0"); - await client.GetBooleanValue("test", false, new EvaluationContext(), + await client.GetBooleanValue("test", false, EvaluationContext.Empty, new FlagEvaluationOptions(new[] { hook1.Object, hook2.Object }, new Dictionary())); hook1.Verify(x => x.Before(It.IsAny>(), It.IsAny>()), Times.Once); @@ -204,23 +204,27 @@ public async Task Evaluation_Context_Must_Be_Merged_In_Correct_Order() var propHook = "4.3.4hook"; // setup a cascade of overwriting properties - OpenFeature.Instance.SetContext(new EvaluationContext() - .Add(propGlobal, true) - .Add(propGlobalToOverwrite, false)); - - var clientContext = new EvaluationContext() - .Add(propClient, true) - .Add(propGlobalToOverwrite, true) - .Add(propClientToOverwrite, false); - - var invocationContext = new EvaluationContext() - .Add(propInvocation, true) - .Add(propClientToOverwrite, true) - .Add(propInvocationToOverwrite, false); - - var hookContext = new EvaluationContext() - .Add(propHook, true) - .Add(propInvocationToOverwrite, true); + OpenFeature.Instance.SetContext(new EvaluationContextBuilder() + .Set(propGlobal, true) + .Set(propGlobalToOverwrite, false) + .Build()); + + var clientContext = new EvaluationContextBuilder() + .Set(propClient, true) + .Set(propGlobalToOverwrite, true) + .Set(propClientToOverwrite, false) + .Build(); + + var invocationContext = new EvaluationContextBuilder() + .Set(propInvocation, true) + .Set(propClientToOverwrite, true) + .Set(propInvocationToOverwrite, false) + .Build(); + + var hookContext = new EvaluationContextBuilder() + .Set(propHook, true) + .Set(propInvocationToOverwrite, true) + .Build(); var provider = new Mock(MockBehavior.Strict); @@ -270,10 +274,10 @@ public async Task Hook_Should_Return_No_Errors() ["number"] = 1, ["boolean"] = true, ["datetime"] = DateTime.Now, - ["structure"] = new Structure() + ["structure"] = Structure.Empty }; var hookContext = new HookContext("test", false, FlagValueType.Boolean, - new ClientMetadata(null, null), new Metadata(null), new EvaluationContext()); + new ClientMetadata(null, null), new Metadata(null), EvaluationContext.Empty); await hook.Before(hookContext, hookHints); await hook.After(hookContext, new FlagEvaluationDetails("test", false, ErrorType.None, "testing", "testing"), hookHints); @@ -307,7 +311,7 @@ public async Task Hook_Should_Execute_In_Correct_Order() hook.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), It.IsAny>())) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); featureProvider.InSequence(sequence) .Setup(x => x.ResolveBooleanValue(It.IsAny(), It.IsAny(), It.IsAny())) @@ -372,11 +376,11 @@ public async Task Finally_Hook_Should_Be_Executed_Even_If_Abnormal_Termination() hook1.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), null)) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); hook2.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), null)) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); featureProvider.InSequence(sequence) .Setup(x => x.ResolveBooleanValue(It.IsAny(), It.IsAny(), It.IsAny())) @@ -431,11 +435,11 @@ public async Task Error_Hook_Should_Be_Executed_Even_If_Abnormal_Termination() hook1.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), null)) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); hook2.InSequence(sequence).Setup(x => x.Before(It.IsAny>(), null)) - .ReturnsAsync(new EvaluationContext()); + .ReturnsAsync(EvaluationContext.Empty); featureProvider1.InSequence(sequence) .Setup(x => x.ResolveBooleanValue(It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/test/OpenFeatureSDK.Tests/OpenFeatureTests.cs b/test/OpenFeatureSDK.Tests/OpenFeatureTests.cs index 5150f032..5f9a778b 100644 --- a/test/OpenFeatureSDK.Tests/OpenFeatureTests.cs +++ b/test/OpenFeatureSDK.Tests/OpenFeatureTests.cs @@ -1,4 +1,3 @@ -using AutoFixture; using FluentAssertions; using Moq; using OpenFeatureSDK.Constant; @@ -79,8 +78,13 @@ public void OpenFeature_Should_Create_Client(string name = null, string version [Fact] public void Should_Set_Given_Context() { - var fixture = new Fixture(); - var context = fixture.Create(); + var context = EvaluationContext.Empty; + + OpenFeature.Instance.SetContext(context); + + OpenFeature.Instance.GetContext().Should().BeSameAs(context); + + context = EvaluationContext.Builder().Build(); OpenFeature.Instance.SetContext(context); diff --git a/test/OpenFeatureSDK.Tests/StructureTests.cs b/test/OpenFeatureSDK.Tests/StructureTests.cs index 2e78a62f..12bde7fb 100644 --- a/test/OpenFeatureSDK.Tests/StructureTests.cs +++ b/test/OpenFeatureSDK.Tests/StructureTests.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using FluentAssertions; using OpenFeatureSDK.Model; using Xunit; @@ -10,18 +13,16 @@ public class StructureTests [Fact] public void No_Arg_Should_Contain_Empty_Attributes() { - Structure structure = new Structure(); + Structure structure = Structure.Empty; Assert.Equal(0, structure.Count); - Assert.Equal(0, structure.AsDictionary().Keys.Count); + Assert.Empty(structure.AsDictionary()); } [Fact] public void Dictionary_Arg_Should_Contain_New_Dictionary() { string KEY = "key"; - IDictionary dictionary = new Dictionary(){ - { KEY, new Value(KEY) } - }; + IDictionary dictionary = new Dictionary() { { KEY, new Value(KEY) } }; Structure structure = new Structure(dictionary); Assert.Equal(KEY, structure.AsDictionary()[KEY].AsString); Assert.NotSame(structure.AsDictionary(), dictionary); // should be a copy @@ -44,19 +45,20 @@ public void Add_And_Get_Add_And_Return_Values() int INT_VAL = 13; double DOUBLE_VAL = .5; DateTime DATE_VAL = DateTime.Now; - Structure STRUCT_VAL = new Structure(); + Structure STRUCT_VAL = Structure.Empty; IList LIST_VAL = new List(); Value VALUE_VAL = new Value(); - Structure structure = new Structure(); - structure.Add(BOOL_KEY, BOOL_VAL); - structure.Add(STRING_KEY, STRING_VAL); - structure.Add(INT_KEY, INT_VAL); - structure.Add(DOUBLE_KEY, DOUBLE_VAL); - structure.Add(DATE_KEY, DATE_VAL); - structure.Add(STRUCT_KEY, STRUCT_VAL); - structure.Add(LIST_KEY, LIST_VAL); - structure.Add(VALUE_KEY, VALUE_VAL); + var structureBuilder = Structure.Builder(); + structureBuilder.Set(BOOL_KEY, BOOL_VAL); + structureBuilder.Set(STRING_KEY, STRING_VAL); + structureBuilder.Set(INT_KEY, INT_VAL); + structureBuilder.Set(DOUBLE_KEY, DOUBLE_VAL); + structureBuilder.Set(DATE_KEY, DATE_VAL); + structureBuilder.Set(STRUCT_KEY, STRUCT_VAL); + structureBuilder.Set(LIST_KEY, ImmutableList.CreateRange(LIST_VAL)); + structureBuilder.Set(VALUE_KEY, VALUE_VAL); + var structure = structureBuilder.Build(); Assert.Equal(BOOL_VAL, structure.GetValue(BOOL_KEY).AsBoolean); Assert.Equal(STRING_VAL, structure.GetValue(STRING_KEY).AsString); @@ -68,27 +70,14 @@ public void Add_And_Get_Add_And_Return_Values() Assert.True(structure.GetValue(VALUE_KEY).IsNull); } - [Fact] - public void Remove_Should_Remove_Value() - { - String KEY = "key"; - bool VAL = true; - - Structure structure = new Structure(); - structure.Add(KEY, VAL); - Assert.Equal(1, structure.Count); - structure.Remove(KEY); - Assert.Equal(0, structure.Count); - } - [Fact] public void TryGetValue_Should_Return_Value() { String KEY = "key"; String VAL = "val"; - Structure structure = new Structure(); - structure.Add(KEY, VAL); + var structure = Structure.Builder() + .Set(KEY, VAL).Build(); Value value; Assert.True(structure.TryGetValue(KEY, out value)); Assert.Equal(VAL, value.AsString); @@ -100,8 +89,8 @@ public void Values_Should_Return_Values() String KEY = "key"; Value VAL = new Value("val"); - Structure structure = new Structure(); - structure.Add(KEY, VAL); + var structure = Structure.Builder() + .Set(KEY, VAL).Build(); Assert.Equal(1, structure.Values.Count); } @@ -111,10 +100,10 @@ public void Keys_Should_Return_Keys() String KEY = "key"; Value VAL = new Value("val"); - Structure structure = new Structure(); - structure.Add(KEY, VAL); + var structure = Structure.Builder() + .Set(KEY, VAL).Build(); Assert.Equal(1, structure.Keys.Count); - Assert.True(structure.Keys.Contains(KEY)); + Assert.Equal(0, structure.Keys.IndexOf(KEY)); } [Fact] @@ -123,8 +112,8 @@ public void GetEnumerator_Should_Return_Enumerator() string KEY = "key"; string VAL = "val"; - Structure structure = new Structure(); - structure.Add(KEY, VAL); + var structure = Structure.Builder() + .Set(KEY, VAL).Build(); IEnumerator> enumerator = structure.GetEnumerator(); enumerator.MoveNext(); Assert.Equal(VAL, enumerator.Current.Value.AsString); diff --git a/test/OpenFeatureSDK.Tests/TestImplementations.cs b/test/OpenFeatureSDK.Tests/TestImplementations.cs index 980aea01..4236077c 100644 --- a/test/OpenFeatureSDK.Tests/TestImplementations.cs +++ b/test/OpenFeatureSDK.Tests/TestImplementations.cs @@ -11,7 +11,7 @@ public class TestHook : Hook { public override Task Before(HookContext context, IReadOnlyDictionary hints = null) { - return Task.FromResult(new EvaluationContext()); + return Task.FromResult(EvaluationContext.Empty); } public override Task After(HookContext context, FlagEvaluationDetails details, diff --git a/test/OpenFeatureSDK.Tests/ValueTests.cs b/test/OpenFeatureSDK.Tests/ValueTests.cs index cf3f214c..8c3ae37d 100644 --- a/test/OpenFeatureSDK.Tests/ValueTests.cs +++ b/test/OpenFeatureSDK.Tests/ValueTests.cs @@ -7,7 +7,9 @@ namespace OpenFeatureSDK.Tests { public class ValueTests { - class Foo { } + class Foo + { + } [Fact] public void No_Arg_Should_Contain_Null() @@ -19,24 +21,23 @@ public void No_Arg_Should_Contain_Null() [Fact] public void Object_Arg_Should_Contain_Object() { - try + // int is a special case, see Int_Object_Arg_Should_Contain_Object() + IList list = new List() { - // int is a special case, see Int_Object_Arg_Should_Contain_Object() - IList list = new List(){ - true, "val", .5, new Structure(), new List(), DateTime.Now - }; + true, + "val", + .5, + Structure.Empty, + new List(), + DateTime.Now + }; - int i = 0; - foreach (Object l in list) - { - Value value = new Value(l); - Assert.Equal(list[i], value.AsObject); - i++; - } - } - catch (Exception) + int i = 0; + foreach (Object l in list) { - Assert.True(false, "Expected no exception."); + Value value = new Value(l); + Assert.Equal(list[i], value.AsObject); + i++; } } @@ -80,7 +81,7 @@ public void Numeric_Arg_Should_Return_Double_Or_Int() double innerDoubleValue = .75; Value doubleValue = new Value(innerDoubleValue); Assert.True(doubleValue.IsNumber); - Assert.Equal(1, doubleValue.AsInteger); // should be rounded + Assert.Equal(1, doubleValue.AsInteger); // should be rounded Assert.Equal(.75, doubleValue.AsDouble); int innerIntValue = 100; @@ -113,20 +114,17 @@ public void Structure_Arg_Should_Contain_Structure() { string INNER_KEY = "key"; string INNER_VALUE = "val"; - Structure innerValue = new Structure().Add(INNER_KEY, INNER_VALUE); + Structure innerValue = Structure.Builder().Set(INNER_KEY, INNER_VALUE).Build(); Value value = new Value(innerValue); Assert.True(value.IsStructure); Assert.Equal(INNER_VALUE, value.AsStructure.GetValue(INNER_KEY).AsString); } [Fact] - public void LIst_Arg_Should_Contain_LIst() + public void List_Arg_Should_Contain_List() { string ITEM_VALUE = "val"; - IList innerValue = new List() - { - new Value(ITEM_VALUE) - }; + IList innerValue = new List() { new Value(ITEM_VALUE) }; Value value = new Value(innerValue); Assert.True(value.IsList); Assert.Equal(ITEM_VALUE, value.AsList[0].AsString);