diff --git a/pkgs/sdk/server/contract-tests/TestService.cs b/pkgs/sdk/server/contract-tests/TestService.cs
index d6e7da05..50a008f0 100644
--- a/pkgs/sdk/server/contract-tests/TestService.cs
+++ b/pkgs/sdk/server/contract-tests/TestService.cs
@@ -37,7 +37,8 @@ public class Webapp
"tags",
"inline-context",
"anonymous-redaction",
- "evaluation-hooks"
+ "evaluation-hooks",
+ "client-prereq-events"
};
public readonly Handler Handler;
diff --git a/pkgs/sdk/server/src/FeatureFlagsState.cs b/pkgs/sdk/server/src/FeatureFlagsState.cs
index 7d5ea093..e7603726 100644
--- a/pkgs/sdk/server/src/FeatureFlagsState.cs
+++ b/pkgs/sdk/server/src/FeatureFlagsState.cs
@@ -156,6 +156,7 @@ public FeatureFlagsState Build()
///
/// true if valid, false if invalid (default is valid)
/// the same builder
+ [Obsolete("Unused, construct a FeatureFlagState with valid/invalid state directly")]
public FeatureFlagsStateBuilder Valid(bool valid)
{
_valid = valid;
@@ -169,6 +170,19 @@ public FeatureFlagsStateBuilder Valid(bool valid)
/// the evaluation result
///
public FeatureFlagsStateBuilder AddFlag(string flagKey, EvaluationDetail result)
+ {
+ return AddFlag(flagKey, result, new List());
+ }
+
+
+ ///
+ /// Adds the result of a flag evaluation, including direct prerequisites.
+ ///
+ /// the flag key
+ /// the evaluation result
+ /// the direct prerequisites evaluated for this flag
+ ///
+ public FeatureFlagsStateBuilder AddFlag(string flagKey, EvaluationDetail result, List prerequisites)
{
return AddFlag(flagKey,
result.Value,
@@ -177,13 +191,14 @@ public FeatureFlagsStateBuilder AddFlag(string flagKey, EvaluationDetail prerequisites)
{
bool flagIsTracked = flagTrackEvents || flagDebugEventsUntilDate != null;
var flag = new FlagState
@@ -194,14 +209,15 @@ internal FeatureFlagsStateBuilder AddFlag(string flagKey, LdValue value, int? va
Reason = trackReason || (_withReasons && (!_detailsOnlyIfTracked || flagIsTracked)) ? reason : (EvaluationReason?)null,
DebugEventsUntilDate = flagDebugEventsUntilDate,
TrackEvents = flagTrackEvents,
- TrackReason = trackReason
+ TrackReason = trackReason,
+ Prerequisites = prerequisites
};
_flags[flagKey] = flag;
return this;
}
}
- internal struct FlagState
+ internal struct FlagState : IEquatable
{
internal LdValue Value { get; set; }
internal int? Variation { get; set; }
@@ -211,24 +227,37 @@ internal struct FlagState
internal UnixMillisecondTime? DebugEventsUntilDate { get; set; }
internal EvaluationReason? Reason { get; set; }
- public override bool Equals(object other)
+ internal List Prerequisites { get; set; }
+
+ public bool Equals(FlagState o)
{
- if (other is FlagState o)
- {
- return Variation == o.Variation &&
- Version == o.Version &&
- TrackEvents == o.TrackEvents &&
- TrackReason == o.TrackReason &&
- DebugEventsUntilDate.Equals(o.DebugEventsUntilDate) &&
- Object.Equals(Reason, o.Reason);
- }
- return false;
+ return Variation == o.Variation &&
+ Version == o.Version &&
+ TrackEvents == o.TrackEvents &&
+ TrackReason == o.TrackReason &&
+ DebugEventsUntilDate.Equals(o.DebugEventsUntilDate) &&
+ Object.Equals(Reason, o.Reason) &&
+ Prerequisites.SequenceEqual(o.Prerequisites);
+ }
+ public override bool Equals(object obj)
+ {
+ return obj is FlagState other && Equals(other);
+ }
+
+ public static bool operator ==(FlagState lhs, FlagState rhs)
+ {
+ return lhs.Equals(rhs);
+ }
+
+ public static bool operator !=(FlagState lhs, FlagState rhs)
+ {
+ return !(lhs == rhs);
}
public override int GetHashCode()
{
- return new HashCodeBuilder().With(Variation).With(Version).With(TrackEvents).With(TrackReason).
- With(DebugEventsUntilDate).With(Reason).Value;
+ return new HashCodeBuilder().With(Variation).With(Version).With(TrackEvents).With(TrackReason)
+ .With(DebugEventsUntilDate).With(Reason).With(Prerequisites).Value;
}
}
@@ -271,6 +300,14 @@ public override void Write(Utf8JsonWriter w, FeatureFlagsState state, JsonSerial
w.WritePropertyName("reason");
EvaluationReasonConverter.WriteJsonValue(meta.Reason.Value, w);
}
+ if (meta.Prerequisites.Count > 0) {
+ w.WriteStartArray("prerequisites");
+ foreach (var p in meta.Prerequisites)
+ {
+ w.WriteStringValue(p);
+ }
+ w.WriteEndArray();
+ }
w.WriteEndObject();
}
w.WriteEndObject();
@@ -295,7 +332,10 @@ public override FeatureFlagsState Read(ref Utf8JsonReader reader, Type typeToCon
for (var flagsObj = RequireObject(ref reader); flagsObj.Next(ref reader);)
{
var subKey = flagsObj.Name;
- var flag = flags.ContainsKey(subKey) ? flags[subKey] : new FlagState();
+ var flag = flags.ContainsKey(subKey)
+ ? flags[subKey]
+ : new FlagState() { Prerequisites = new List() };
+
for (var metaObj = RequireObject(ref reader); metaObj.Next(ref reader);)
{
switch (metaObj.Name)
@@ -318,6 +358,13 @@ public override FeatureFlagsState Read(ref Utf8JsonReader reader, Type typeToCon
flag.Reason = reader.TokenType == JsonTokenType.Null ? (EvaluationReason?)null :
EvaluationReasonConverter.ReadJsonValue(ref reader);
break;
+ case "prerequisites":
+ flag.Prerequisites = new List();
+ for (var prereqs = RequireArray(ref reader); prereqs.Next(ref reader);)
+ {
+ flag.Prerequisites.Add(reader.GetString());
+ }
+ break;
}
}
flags[subKey] = flag;
@@ -325,7 +372,7 @@ public override FeatureFlagsState Read(ref Utf8JsonReader reader, Type typeToCon
break;
default:
- var flagForValue = flags.ContainsKey(key) ? flags[key] : new FlagState();
+ var flagForValue = flags.ContainsKey(key) ? flags[key] : new FlagState(){Prerequisites = new List()};
flagForValue.Value = LdValueConverter.ReadJsonValue(ref reader);
flags[key] = flagForValue;
break;
diff --git a/pkgs/sdk/server/src/Internal/Evaluation/EvaluatorTypes.cs b/pkgs/sdk/server/src/Internal/Evaluation/EvaluatorTypes.cs
index 561d81e8..3deb788d 100644
--- a/pkgs/sdk/server/src/Internal/Evaluation/EvaluatorTypes.cs
+++ b/pkgs/sdk/server/src/Internal/Evaluation/EvaluatorTypes.cs
@@ -21,14 +21,14 @@ internal EvalResult(EvaluationDetail result, IList Result;
- internal PrerequisiteEvalRecord(FeatureFlag prerequisiteFlag, string prerequisiteOfFlagKey,
+ internal PrerequisiteEvalRecord(FeatureFlag prerequisiteFlag, string flagKey,
EvaluationDetail result)
{
PrerequisiteFlag = prerequisiteFlag;
- PrerequisiteOfFlagKey = prerequisiteOfFlagKey;
+ FlagKey = flagKey;
Result = result;
}
}
diff --git a/pkgs/sdk/server/src/LdClient.cs b/pkgs/sdk/server/src/LdClient.cs
index cbff359a..ecc32f28 100644
--- a/pkgs/sdk/server/src/LdClient.cs
+++ b/pkgs/sdk/server/src/LdClient.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using LaunchDarkly.Logging;
@@ -371,8 +372,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[
var builder = new FeatureFlagsStateBuilder(options);
var clientSideOnly = FlagsStateOption.HasOption(options, FlagsStateOption.ClientSideOnly);
- var withReasons = FlagsStateOption.HasOption(options, FlagsStateOption.WithReasons);
- var detailsOnlyIfTracked = FlagsStateOption.HasOption(options, FlagsStateOption.DetailsOnlyForTrackedFlags);
+
KeyedItems flags;
try
{
@@ -397,6 +397,11 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[
{
EvaluatorTypes.EvalResult result = _evaluator.Evaluate(flag, context);
bool inExperiment = EventFactory.IsExperiment(flag, result.Result.Reason);
+
+ var directPrerequisites = result.PrerequisiteEvals.Where(
+ e => e.FlagKey == flag.Key)
+ .Select(p => p.PrerequisiteFlag.Key).ToList();
+
builder.AddFlag(
flag.Key,
result.Result.Value,
@@ -405,8 +410,8 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[
flag.Version,
flag.TrackEvents || inExperiment,
inExperiment,
- flag.DebugEventsUntilDate
- );
+ flag.DebugEventsUntilDate,
+ directPrerequisites);
}
catch (Exception e)
{
@@ -414,7 +419,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[
string.Format("Exception caught for feature flag \"{0}\" when evaluating all flags", flag.Key),
e);
EvaluationReason reason = EvaluationReason.ErrorReason(EvaluationErrorKind.Exception);
- builder.AddFlag(flag.Key, new EvaluationDetail(LdValue.Null, null, reason));
+ builder.AddFlag(flag.Key, new EvaluationDetail(LdValue.Null, null, reason), new List());
}
}
return builder.Build();
@@ -477,7 +482,7 @@ public FeatureFlagsState AllFlagsState(Context context, params FlagsStateOption[
foreach (var prereqEvent in evalResult.PrerequisiteEvals)
{
_eventProcessor.RecordEvaluationEvent(eventFactory.NewPrerequisiteEvaluationEvent(
- prereqEvent.PrerequisiteFlag, context, prereqEvent.Result, prereqEvent.PrerequisiteOfFlagKey));
+ prereqEvent.PrerequisiteFlag, context, prereqEvent.Result, prereqEvent.FlagKey));
}
}
var evalDetail = evalResult.Result;
diff --git a/pkgs/sdk/server/test/FeatureFlagsStateTest.cs b/pkgs/sdk/server/test/FeatureFlagsStateTest.cs
index 685a323b..6d763953 100644
--- a/pkgs/sdk/server/test/FeatureFlagsStateTest.cs
+++ b/pkgs/sdk/server/test/FeatureFlagsStateTest.cs
@@ -71,9 +71,13 @@ public void CanConvertToValuesMap()
public void CanSerializeToJson()
{
var state = FeatureFlagsState.Builder(FlagsStateOption.WithReasons)
- .AddFlag("key1", LdValue.Of("value1"), 0, EvaluationReason.OffReason, 100, false, false, null)
- .AddFlag("key2", LdValue.Of("value2"), 1, EvaluationReason.FallthroughReason, 200, true, false, UnixMillisecondTime.OfMillis(1000))
- .AddFlag("key3", LdValue.Null, null, EvaluationReason.ErrorReason(EvaluationErrorKind.MalformedFlag), 300, false, false, null)
+ .AddFlag("key1", LdValue.Of("value1"), 0, EvaluationReason.OffReason, 100, false, false, null,
+ new List())
+ .AddFlag("key2", LdValue.Of("value2"), 1, EvaluationReason.FallthroughReason, 200, true, false,
+ UnixMillisecondTime.OfMillis(1000), new List())
+ .AddFlag("key3", LdValue.Null, null, EvaluationReason.ErrorReason(EvaluationErrorKind.MalformedFlag),
+ 300, false, false, null, new List()
+ )
.Build();
var expectedString = @"{""key1"":""value1"",""key2"":""value2"",""key3"":null,
@@ -92,18 +96,63 @@ public void CanSerializeToJson()
JsonAssertions.AssertJsonEqual(expectedString, actualString);
}
+ [Fact]
+ public void CanSerializeFlagPrerequisites()
+ {
+ var state = FeatureFlagsState.Builder(FlagsStateOption.WithReasons)
+ .AddFlag("prereq1", LdValue.Of("value1"), 0, EvaluationReason.OffReason, 100, false, false, null,
+ new List())
+ .AddFlag("prereq2", LdValue.Of("value2"), 1, EvaluationReason.FallthroughReason, 200, true, false,
+ UnixMillisecondTime.OfMillis(1000), new List())
+ .AddFlag("toplevel", LdValue.Null, null,
+ EvaluationReason.ErrorReason(EvaluationErrorKind.MalformedFlag), 300, false, false, null,
+ new List
+ {
+ "prereq1", "prereq2"
+ })
+ .Build();
+
+
+ var expectedString = @"{""prereq1"":""value1"",""prereq2"":""value2"",""toplevel"":null,
+ ""$flagsState"":{
+ ""prereq1"":{
+ ""variation"":0,""version"":100,""reason"":{""kind"":""OFF""}
+ },""prereq2"":{
+ ""variation"":1,""version"":200,""reason"":{""kind"":""FALLTHROUGH""},""trackEvents"":true,""debugEventsUntilDate"":1000
+ },""toplevel"":{
+ ""version"":300,""reason"":{""kind"":""ERROR"",""errorKind"":""MALFORMED_FLAG""},""prerequisites"":[""prereq1"",""prereq2""]
+ }
+ },
+ ""$valid"":true
+ }";
+ var actualString = LdJsonSerialization.SerializeObject(state);
+ JsonAssertions.AssertJsonEqual(expectedString, actualString);
+ }
+
+
[Fact]
public void CanDeserializeFromJson()
{
var state = FeatureFlagsState.Builder(FlagsStateOption.WithReasons)
- .AddFlag("key1", LdValue.Of("value1"), 0, EvaluationReason.OffReason, 100, false, false, null)
- .AddFlag("key2", LdValue.Of("value2"), 1, EvaluationReason.FallthroughReason, 200, true, false, UnixMillisecondTime.OfMillis(1000))
+ .AddFlag("key1", LdValue.Of("value1"), 0, EvaluationReason.OffReason, 100, false, false, null,
+ new List())
+ .AddFlag("key2", LdValue.Of("value2"), 1, EvaluationReason.FallthroughReason, 200, true, false,
+ UnixMillisecondTime.OfMillis(1000), new List { "key1" })
.Build();
var jsonString = LdJsonSerialization.SerializeObject(state);
var state1 = LdJsonSerialization.DeserializeObject(jsonString);
+ var jsonString2 = LdJsonSerialization.SerializeObject(state1);
+
+ // Ensure a roundtrip state -> json -> json is equal.
+ Assert.Equal(jsonString, jsonString2);
+
+ // Ensure a roundtrip state -> json -> state is equal.
Assert.Equal(state, state1);
}
}
-}
\ No newline at end of file
+
+
+
+}
diff --git a/pkgs/sdk/server/test/Internal/Evaluation/EvaluatorPrerequisitesTest.cs b/pkgs/sdk/server/test/Internal/Evaluation/EvaluatorPrerequisitesTest.cs
index bc953375..913b43df 100644
--- a/pkgs/sdk/server/test/Internal/Evaluation/EvaluatorPrerequisitesTest.cs
+++ b/pkgs/sdk/server/test/Internal/Evaluation/EvaluatorPrerequisitesTest.cs
@@ -64,7 +64,7 @@ public void FlagReturnsOffVariationAndEventIfPrerequisiteIsOff()
Assert.Equal(f1.Key, e.PrerequisiteFlag.Key);
Assert.Equal(LdValue.Of("go"), e.Result.Value);
Assert.Equal(f1.Version, e.PrerequisiteFlag.Version);
- Assert.Equal(f0.Key, e.PrerequisiteOfFlagKey);
+ Assert.Equal(f0.Key, e.FlagKey);
});
}
@@ -99,7 +99,7 @@ public void FlagReturnsOffVariationAndEventIfPrerequisiteIsNotMet()
Assert.Equal(f1.Key, e.PrerequisiteFlag.Key);
Assert.Equal(LdValue.Of("nogo"), e.Result.Value);
Assert.Equal(f1.Version, e.PrerequisiteFlag.Version);
- Assert.Equal(f0.Key, e.PrerequisiteOfFlagKey);
+ Assert.Equal(f0.Key, e.FlagKey);
});
}
@@ -133,7 +133,7 @@ public void FlagReturnsFallthroughVariationAndEventIfPrerequisiteIsMetAndThereAr
Assert.Equal(f1.Key, e.PrerequisiteFlag.Key);
Assert.Equal(LdValue.Of("go"), e.Result.Value);
Assert.Equal(f1.Version, e.PrerequisiteFlag.Version);
- Assert.Equal(f0.Key, e.PrerequisiteOfFlagKey);
+ Assert.Equal(f0.Key, e.FlagKey);
});
}
@@ -176,14 +176,14 @@ public void MultipleLevelsOfPrerequisitesProduceMultipleEvents()
Assert.Equal(f2.Key, e.PrerequisiteFlag.Key);
Assert.Equal(LdValue.Of("go"), e.Result.Value);
Assert.Equal(f2.Version, e.PrerequisiteFlag.Version);
- Assert.Equal(f1.Key, e.PrerequisiteOfFlagKey);
+ Assert.Equal(f1.Key, e.FlagKey);
},
e =>
{
Assert.Equal(f1.Key, e.PrerequisiteFlag.Key);
Assert.Equal(LdValue.Of("go"), e.Result.Value);
Assert.Equal(f1.Version, e.PrerequisiteFlag.Version);
- Assert.Equal(f0.Key, e.PrerequisiteOfFlagKey);
+ Assert.Equal(f0.Key, e.FlagKey);
});
}
diff --git a/pkgs/sdk/server/test/Internal/Model/FeatureFlagBuilder.cs b/pkgs/sdk/server/test/Internal/Model/FeatureFlagBuilder.cs
index 20c60fe0..c51975a5 100644
--- a/pkgs/sdk/server/test/Internal/Model/FeatureFlagBuilder.cs
+++ b/pkgs/sdk/server/test/Internal/Model/FeatureFlagBuilder.cs
@@ -202,6 +202,11 @@ internal FeatureFlagBuilder OffWithValue(LdValue value)
return On(false).OffVariation(0).Variations(value);
}
+ internal FeatureFlagBuilder OnWithValue(LdValue value)
+ {
+ return On(true).OffVariation(0).FallthroughVariation(0).Variations(value);
+ }
+
internal FeatureFlagBuilder BooleanWithClauses(params Clause[] clauses)
{
return On(true).OffVariation(0)
diff --git a/pkgs/sdk/server/test/LdClientEvaluationTest.cs b/pkgs/sdk/server/test/LdClientEvaluationTest.cs
index 3fea194a..b63c074e 100644
--- a/pkgs/sdk/server/test/LdClientEvaluationTest.cs
+++ b/pkgs/sdk/server/test/LdClientEvaluationTest.cs
@@ -389,6 +389,195 @@ public void ExceptionWhenEvaluatingFlagInAllFlagsIsHandledCorrectly()
AssertLogMessageRegex(true, Logging.LogLevel.Error, Evaluator.ErrorMessageForTesting);
}
+
+
+ [Fact]
+ public void AllFlagsStateCanExposePrerequisiteRelationshipsWhenPrereqIsNotVisibleToClients()
+ {
+ var prereq1 = new FeatureFlagBuilder("prereq1")
+ .OnWithValue(LdValue.Of(true)).ClientSide(false).Build();
+
+ var prereq2 = new FeatureFlagBuilder("prereq2")
+ .OnWithValue(LdValue.Of(true)).ClientSide(false).Build();
+
+ var toplevel = new FeatureFlagBuilder("toplevel")
+ .Prerequisites(new Prerequisite("prereq1", 0), new Prerequisite("prereq2", 0))
+ .Variations(LdValue.Of(false), LdValue.Of(true))
+ .On(true)
+ .ClientSide(true)
+ .OffVariation(0)
+ .FallthroughVariation(1)
+ .Build();
+
+ testData.UsePreconfiguredFlag(prereq1);
+ testData.UsePreconfiguredFlag(prereq2);
+ testData.UsePreconfiguredFlag(toplevel);
+
+ var state = client.AllFlagsState(context, FlagsStateOption.ClientSideOnly);
+ Assert.True(state.Valid);
+
+
+ var expectedString = @"{""toplevel"":true,
+ ""$flagsState"":{
+ ""toplevel"":{
+ ""variation"":1,""version"":1,""prerequisites"":[
+ ""prereq1"",""prereq2""
+ ]
+ }
+ },
+ ""$valid"":true
+ }";
+
+ var actualString = LdJsonSerialization.SerializeObject(state);
+
+ JsonAssertions.AssertJsonEqual(expectedString, actualString);
+ }
+
+ [Fact]
+ public void AllFlagsStateCanExposePrerequisiteRelationshipsInEvaluationOrderShortCircuit()
+ {
+ var prereq1 = new FeatureFlagBuilder("prereq1")
+ .OffWithValue(LdValue.Of(false)).Build();
+
+ var prereq2 = new FeatureFlagBuilder("prereq2")
+ .OnWithValue(LdValue.Of(true)).Build();
+
+ var toplevel = new FeatureFlagBuilder("toplevel")
+ .Prerequisites(new Prerequisite("prereq1", 0), new Prerequisite("prereq2", 0))
+ .Variations(LdValue.Of(false), LdValue.Of(true))
+ .On(true)
+ .OffVariation(0)
+ .FallthroughVariation(1)
+ .Build();
+
+ testData.UsePreconfiguredFlag(prereq1);
+ testData.UsePreconfiguredFlag(prereq2);
+ testData.UsePreconfiguredFlag(toplevel);
+
+ var state = client.AllFlagsState(context);
+ Assert.True(state.Valid);
+
+
+ var expectedString = @"{""prereq1"":false,""prereq2"":true,""toplevel"":false,
+ ""$flagsState"":{
+ ""prereq1"":{
+ ""variation"":0,
+ ""version"":1
+ },""prereq2"":{
+ ""variation"":0,
+ ""version"":1
+ },""toplevel"":{
+ ""variation"":0,""version"":1,""prerequisites"":[
+ ""prereq1""
+ ]
+ }
+ },
+ ""$valid"":true
+ }";
+
+ var actualString = LdJsonSerialization.SerializeObject(state);
+
+ JsonAssertions.AssertJsonEqual(expectedString, actualString);
+ }
+
+ [Fact]
+ public void AllFlagsStateCanExposePrerequisiteRelationshipsInEvaluationOrderBothOn()
+ {
+ var prereq1 = new FeatureFlagBuilder("prereq1")
+ .OnWithValue(LdValue.Of(true)).Build();
+
+ var prereq2 = new FeatureFlagBuilder("prereq2")
+ .OnWithValue(LdValue.Of(true)).Build();
+
+ var toplevel = new FeatureFlagBuilder("toplevel")
+ .Prerequisites(new Prerequisite("prereq1", 0), new Prerequisite("prereq2", 0))
+ .Variations(LdValue.Of(false), LdValue.Of(true))
+ .On(true)
+ .OffVariation(0)
+ .FallthroughVariation(1)
+ .Build();
+
+ testData.UsePreconfiguredFlag(prereq1);
+ testData.UsePreconfiguredFlag(prereq2);
+ testData.UsePreconfiguredFlag(toplevel);
+
+ var state = client.AllFlagsState(context);
+ Assert.True(state.Valid);
+
+
+ var expectedString = @"{""prereq1"":true,""prereq2"":true,""toplevel"":true,
+ ""$flagsState"":{
+ ""prereq1"":{
+ ""variation"":0,
+ ""version"":1
+ },""prereq2"":{
+ ""variation"":0,
+ ""version"":1
+ },""toplevel"":{
+ ""variation"":1,""version"":1,""prerequisites"":[
+ ""prereq1"",""prereq2""
+ ]
+ }
+ },
+ ""$valid"":true
+ }";
+
+ var actualString = LdJsonSerialization.SerializeObject(state);
+
+ JsonAssertions.AssertJsonEqual(expectedString, actualString);
+ }
+
+
+ [Fact]
+ public void AllFlagsStateCanExposePrerequisiteRelationshipsInEvaluationOrderBothOnSwapped()
+ {
+ // Same as previous test, but the order of prerequisites in the toplevel flag is swapped. This is to
+ // ensure we're not sorting the prerequisite list.
+
+ var prereq1 = new FeatureFlagBuilder("prereq1")
+ .OnWithValue(LdValue.Of(true)).Build();
+
+ var prereq2 = new FeatureFlagBuilder("prereq2")
+ .OnWithValue(LdValue.Of(true)).Build();
+
+ var toplevel = new FeatureFlagBuilder("toplevel")
+ .Prerequisites(new Prerequisite("prereq2", 0), new Prerequisite("prereq1", 0)) // swapped
+ .Variations(LdValue.Of(false), LdValue.Of(true))
+ .On(true)
+ .OffVariation(0)
+ .FallthroughVariation(1)
+ .Build();
+
+ testData.UsePreconfiguredFlag(prereq1);
+ testData.UsePreconfiguredFlag(prereq2);
+ testData.UsePreconfiguredFlag(toplevel);
+
+ var state = client.AllFlagsState(context);
+ Assert.True(state.Valid);
+
+
+ var expectedString = @"{""prereq1"":true,""prereq2"":true,""toplevel"":true,
+ ""$flagsState"":{
+ ""prereq1"":{
+ ""variation"":0,
+ ""version"":1
+ },""prereq2"":{
+ ""variation"":0,
+ ""version"":1
+ },""toplevel"":{
+ ""variation"":1,""version"":1,""prerequisites"":[
+ ""prereq2"",""prereq1""
+ ]
+ }
+ },
+ ""$valid"":true
+ }";
+
+ var actualString = LdJsonSerialization.SerializeObject(state);
+
+ JsonAssertions.AssertJsonEqual(expectedString, actualString);
+ }
+
[Theory]
[InlineData(MigrationStage.Off)]
[InlineData(MigrationStage.DualWrite)]