Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions pkgs/sdk/server/src/Integrations/TestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ internal ItemDescriptor CreateFlag(int version)
null,
$"rule{index}",
r._clauses.Select(c => new Internal.Model.Clause(
null,
c._contextKind,
c._attribute,
Operator.ForName(c._operator),
c._values.ToArray(),
Expand Down Expand Up @@ -985,7 +985,9 @@ public FlagRuleBuilder AndNotMatch(string attribute, params LdValue[] values) =>

private FlagRuleBuilder AddClause(ContextKind contextKind, AttributeRef attr, string op, LdValue[] values, bool negate)
{
_clauses.Add(new Clause(attr, op, values, negate));
// Convert ContextKind.Default to null for consistency
ContextKind? storedContextKind = contextKind == ContextKind.Default ? (ContextKind?)null : contextKind;
_clauses.Add(new Clause(storedContextKind, attr, op, values, negate));
return this;
}

Expand Down Expand Up @@ -1046,13 +1048,15 @@ public FlagMigrationBuilder CheckRatio(long? checkRatio)

internal class Clause
{
internal readonly ContextKind? _contextKind;
internal readonly AttributeRef _attribute;
internal readonly string _operator;
internal readonly LdValue[] _values;
internal readonly bool _negate;

internal Clause(AttributeRef attribute, string op, LdValue[] values, bool negate)
internal Clause(ContextKind? contextKind, AttributeRef attribute, string op, LdValue[] values, bool negate)
{
_contextKind = contextKind;
_attribute = attribute;
_operator = op;
_values = values;
Expand Down
106 changes: 106 additions & 0 deletions pkgs/sdk/server/test/Integrations/TestDataTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,112 @@ public void FlagRules()
);
}

[Fact]
public void IfMatchContext_WithSpecificContextKind_CreatesClauseWithContextKind()
{
var companyKind = ContextKind.Of("company");

VerifyFlag(
f => f.IfMatchContext(companyKind, "name", LdValue.Of("Acme")).ThenReturn(true),
fb => ExpectedBooleanFlag(fb).Rules(new RuleBuilder().Id("rule0").Variation(0).Clauses(
new ClauseBuilder().ContextKind(companyKind).Attribute("name").Op("in").Values("Acme").Build()
).Build())
);
}

[Fact]
public void IfNotMatchContext_WithSpecificContextKind_CreatesNegatedClauseWithContextKind()
{
var companyKind = ContextKind.Of("company");

VerifyFlag(
f => f.IfNotMatchContext(companyKind, "name", LdValue.Of("Acme")).ThenReturn(true),
fb => ExpectedBooleanFlag(fb).Rules(new RuleBuilder().Id("rule0").Variation(0).Clauses(
new ClauseBuilder().ContextKind(companyKind).Attribute("name").Op("in").Values("Acme").Negate(true).Build()
).Build())
);
}

[Fact]
public void AndMatchContext_WithMultipleContextKinds_CreatesClausesWithDifferentContextKinds()
{
var companyKind = ContextKind.Of("company");
var orgKind = ContextKind.Of("org");

VerifyFlag(
f => f.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.AndMatchContext(orgKind, "key", LdValue.Of("org-123"))
.ThenReturn(true),
fb => ExpectedBooleanFlag(fb).Rules(new RuleBuilder().Id("rule0").Variation(0).Clauses(
new ClauseBuilder().ContextKind(companyKind).Attribute("name").Op("in").Values("Acme").Build(),
new ClauseBuilder().ContextKind(orgKind).Attribute("key").Op("in").Values("org-123").Build()
).Build())
);
}

[Fact]
public void AndNotMatchContext_WithContextKind_CreatesNegatedClauseWithContextKind()
{
var companyKind = ContextKind.Of("company");

VerifyFlag(
f => f.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.AndNotMatchContext(companyKind, "status", LdValue.Of("inactive"))
.ThenReturn(true),
fb => ExpectedBooleanFlag(fb).Rules(new RuleBuilder().Id("rule0").Variation(0).Clauses(
new ClauseBuilder().ContextKind(companyKind).Attribute("name").Op("in").Values("Acme").Build(),
new ClauseBuilder().ContextKind(companyKind).Attribute("status").Op("in").Values("inactive").Negate(true).Build()
).Build())
);
}

[Fact]
public void IfMatch_WithDefaultUser_CreatesClauseWithNullContextKind()
{
VerifyFlag(
f => f.IfMatch("name", LdValue.Of("Lucy")).ThenReturn(true),
fb => ExpectedBooleanFlag(fb).Rules(new RuleBuilder().Id("rule0").Variation(0).Clauses(
new ClauseBuilder().Attribute("name").Op("in").Values("Lucy").Build()
).Build())
);
}

[Fact]
public void IfMatch_AndMatchContext_MixesDefaultUserAndSpecificContextKind()
{
var companyKind = ContextKind.Of("company");

VerifyFlag(
f => f.IfMatch("name", LdValue.Of("Lucy"))
.AndMatchContext(companyKind, "name", LdValue.Of("Acme"))
.ThenReturn(true),
fb => ExpectedBooleanFlag(fb).Rules(new RuleBuilder().Id("rule0").Variation(0).Clauses(
new ClauseBuilder().Attribute("name").Op("in").Values("Lucy").Build(),
new ClauseBuilder().ContextKind(companyKind).Attribute("name").Op("in").Values("Acme").Build()
).Build())
);
}

[Fact]
public void AndMatchContext_TwoCustomContextKindsOnSameAttribute_StoresCorrectContextKinds()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replication of github issue.

{
var contextA = ContextKind.Of("context_a");
var contextB = ContextKind.Of("context_b");

VerifyFlag(
f => f.IfMatchContext(contextA, "key", LdValue.Of("A1"))
.AndMatchContext(contextB, "key", LdValue.Of("B2"))
.ThenReturn(true),
fb => ExpectedBooleanFlag(fb).Rules(new RuleBuilder().Id("rule0").Variation(0).Clauses(
new ClauseBuilder().ContextKind(contextA).Attribute("key").Op("in").Values("A1").Build(),
new ClauseBuilder().ContextKind(contextB).Attribute("key").Op("in").Values("B2").Build()
).Build())
);
}

private static Func<FeatureFlagBuilder, FeatureFlagBuilder> ExpectedBooleanFlag =>
fb => fb.Variations(true, false).On(true).OffVariation(1).FallthroughVariation(0);

[Fact]
public void ItCanSetTheSamplingRatio()
{
Expand Down
237 changes: 237 additions & 0 deletions pkgs/sdk/server/test/Integrations/TestDataWithClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,242 @@ public void DataSourcePropagatesToMultipleClients()
}
}
}

[Fact]
public void IfMatchContext_MatchingContextKind_EvaluatesToTrue()
{
var companyKind = ContextKind.Of("company");

_td.Update(_td.Flag("company-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
var matchingCompany = Context.Builder("company-123")
.Kind(companyKind)
.Set("name", "Acme")
.Build();

Assert.True(client.BoolVariation("company-flag", matchingCompany, false));
}
}

[Fact]
public void IfMatchContext_NonMatchingAttributeValue_EvaluatesToFalse()
{
var companyKind = ContextKind.Of("company");

_td.Update(_td.Flag("company-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
var nonMatchingCompany = Context.Builder("company-456")
.Kind(companyKind)
.Set("name", "OtherCorp")
.Build();

Assert.False(client.BoolVariation("company-flag", nonMatchingCompany, false));
}
}

[Fact]
public void IfMatchContext_WrongContextKind_EvaluatesToFalse()
{
var companyKind = ContextKind.Of("company");

_td.Update(_td.Flag("company-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
// User context with same attribute value should not match (wrong context kind)
var userContext = Context.Builder("user-123")
.Set("name", "Acme")
.Build();

Assert.False(client.BoolVariation("company-flag", userContext, false));
}
}

[Fact]
public void IfMatchContext_MultiContextWithMatchingKind_EvaluatesToTrue()
{
var companyKind = ContextKind.Of("company");

_td.Update(_td.Flag("company-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
// Multi-context with matching company should return true
var multiContext = Context.NewMulti(
Context.New("user-123"),
Context.Builder("company-123").Kind(companyKind).Set("name", "Acme").Build()
);

Assert.True(client.BoolVariation("company-flag", multiContext, false));
}
}

[Fact]
public void AndMatchContext_BothConditionsMatch_EvaluatesToTrue()
{
var companyKind = ContextKind.Of("company");
var orgKind = ContextKind.Of("org");

_td.Update(_td.Flag("multi-kind-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.AndMatchContext(orgKind, "tier", LdValue.Of("premium"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
var matchingMulti = Context.NewMulti(
Context.Builder("company-123").Kind(companyKind).Set("name", "Acme").Build(),
Context.Builder("org-456").Kind(orgKind).Set("tier", "premium").Build()
);

Assert.True(client.BoolVariation("multi-kind-flag", matchingMulti, false));
}
}

[Fact]
public void AndMatchContext_OnlyFirstConditionMatches_EvaluatesToFalse()
{
var companyKind = ContextKind.Of("company");
var orgKind = ContextKind.Of("org");

_td.Update(_td.Flag("multi-kind-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.AndMatchContext(orgKind, "tier", LdValue.Of("premium"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
var onlyCompanyMatches = Context.NewMulti(
Context.Builder("company-123").Kind(companyKind).Set("name", "Acme").Build(),
Context.Builder("org-456").Kind(orgKind).Set("tier", "standard").Build()
);

Assert.False(client.BoolVariation("multi-kind-flag", onlyCompanyMatches, false));
}
}

[Fact]
public void AndMatchContext_OnlySecondConditionMatches_EvaluatesToFalse()
{
var companyKind = ContextKind.Of("company");
var orgKind = ContextKind.Of("org");

_td.Update(_td.Flag("multi-kind-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.AndMatchContext(orgKind, "tier", LdValue.Of("premium"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
var onlyOrgMatches = Context.NewMulti(
Context.Builder("company-123").Kind(companyKind).Set("name", "OtherCorp").Build(),
Context.Builder("org-456").Kind(orgKind).Set("tier", "premium").Build()
);

Assert.False(client.BoolVariation("multi-kind-flag", onlyOrgMatches, false));
}
}

[Fact]
public void AndMatchContext_NeitherConditionMatches_EvaluatesToFalse()
{
var companyKind = ContextKind.Of("company");
var orgKind = ContextKind.Of("org");

_td.Update(_td.Flag("multi-kind-flag")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(companyKind, "name", LdValue.Of("Acme"))
.AndMatchContext(orgKind, "tier", LdValue.Of("premium"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
var neitherMatches = Context.NewMulti(
Context.Builder("company-123").Kind(companyKind).Set("name", "OtherCorp").Build(),
Context.Builder("org-456").Kind(orgKind).Set("tier", "standard").Build()
);

Assert.False(client.BoolVariation("multi-kind-flag", neitherMatches, false));
}
}

[Fact]
public void AndMatchContext_TwoCustomContextKindsOnSameAttribute_EvaluatesCorrectly()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also customer replication.

{
var contextA = ContextKind.Of("context_a");
var contextB = ContextKind.Of("context_b");

_td.Update(_td.Flag("flag_A")
.BooleanFlag()
.FallthroughVariation(false)
.IfMatchContext(contextA, "key", LdValue.Of("A1"))
.AndMatchContext(contextB, "key", LdValue.Of("B2"))
.ThenReturn(true));

using (var client = new LdClient(_config))
{
// Both contexts match - should return true
var bothMatch = Context.NewMulti(
Context.Builder("A1").Kind(contextA).Build(),
Context.Builder("B2").Kind(contextB).Build()
);
Assert.True(client.BoolVariation("flag_A", bothMatch, false));

// Only context_a matches - should return false
var onlyAMatches = Context.NewMulti(
Context.Builder("A1").Kind(contextA).Build(),
Context.Builder("wrong").Kind(contextB).Build()
);
Assert.False(client.BoolVariation("flag_A", onlyAMatches, false));

// Only context_b matches - should return false
var onlyBMatches = Context.NewMulti(
Context.Builder("wrong").Kind(contextA).Build(),
Context.Builder("B2").Kind(contextB).Build()
);
Assert.False(client.BoolVariation("flag_A", onlyBMatches, false));

// Neither matches - should return false
var neitherMatches = Context.NewMulti(
Context.Builder("wrong1").Kind(contextA).Build(),
Context.Builder("wrong2").Kind(contextB).Build()
);
Assert.False(client.BoolVariation("flag_A", neitherMatches, false));

// Wrong context kinds - should return false
var wrongKinds = Context.NewMulti(
Context.Builder("A1").Build(), // user context, not context_a
Context.Builder("B2").Build() // user context, not context_b
);
Assert.False(client.BoolVariation("flag_A", wrongKinds, false));
}
}
}
}
Loading