330 lines
12 KiB
C#
330 lines
12 KiB
C#
using MicCheck.Api.Identities;
|
|
using MicCheck.Api.Segments;
|
|
using NUnit.Framework;
|
|
|
|
namespace MicCheck.Api.Tests.Unit.Segments;
|
|
|
|
[TestFixture]
|
|
public class SegmentEvaluatorTests
|
|
{
|
|
private SegmentEvaluator _evaluator = null!;
|
|
|
|
[SetUp]
|
|
public void SetUp() => _evaluator = new SegmentEvaluator();
|
|
|
|
private static Segment BuildSegment(SegmentRuleType ruleType, params SegmentCondition[] conditions)
|
|
{
|
|
var segment = new Segment { Name = "Test", ProjectId = 1, CreatedAt = DateTimeOffset.UtcNow };
|
|
var rule = new SegmentRule { SegmentId = segment.Id, Type = ruleType };
|
|
foreach (var condition in conditions)
|
|
rule.Conditions.Add(condition);
|
|
segment.Rules.Add(rule);
|
|
return segment;
|
|
}
|
|
|
|
private static SegmentCondition Condition(string property, SegmentConditionOperator op, string value) =>
|
|
new() { Property = property, Operator = op, Value = value };
|
|
|
|
private static IdentityTrait Trait(string key, string value) =>
|
|
new() { Key = key, Value = value, IdentityId = 1 };
|
|
|
|
[Test]
|
|
public void WhenTraitEqualsConditionValue_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("plan", SegmentConditionOperator.Equal, "premium"));
|
|
var traits = new[] { Trait("plan", "premium") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenTraitDoesNotEqualConditionValue_ThenSegmentDoesNotMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("plan", SegmentConditionOperator.Equal, "premium"));
|
|
var traits = new[] { Trait("plan", "free") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenNotEqualOperatorAndValuesAreDifferent_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("plan", SegmentConditionOperator.NotEqual, "premium"));
|
|
var traits = new[] { Trait("plan", "free") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenGreaterThanConditionIsSatisfied_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("age", SegmentConditionOperator.GreaterThan, "18"));
|
|
var traits = new[] { Trait("age", "25") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenGreaterThanConditionIsNotSatisfied_ThenSegmentDoesNotMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("age", SegmentConditionOperator.GreaterThan, "18"));
|
|
var traits = new[] { Trait("age", "16") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenGreaterThanOrEqualConditionIsExactMatch_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("age", SegmentConditionOperator.GreaterThanOrEqual, "18"));
|
|
var traits = new[] { Trait("age", "18") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenLessThanConditionIsSatisfied_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("age", SegmentConditionOperator.LessThan, "18"));
|
|
var traits = new[] { Trait("age", "16") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenLessThanOrEqualConditionIsExactMatch_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("age", SegmentConditionOperator.LessThanOrEqual, "18"));
|
|
var traits = new[] { Trait("age", "18") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenContainsConditionIsSatisfied_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("email", SegmentConditionOperator.Contains, "@example.com"));
|
|
var traits = new[] { Trait("email", "alice@example.com") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenNotContainsConditionIsSatisfied_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("email", SegmentConditionOperator.NotContains, "@test.com"));
|
|
var traits = new[] { Trait("email", "alice@example.com") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenRegexConditionMatchesTraitValue_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("email", SegmentConditionOperator.Regex, @"^[^@]+@example\.com$"));
|
|
var traits = new[] { Trait("email", "alice@example.com") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenRegexConditionDoesNotMatchTraitValue_ThenSegmentDoesNotMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("email", SegmentConditionOperator.Regex, @"^[^@]+@example\.com$"));
|
|
var traits = new[] { Trait("email", "alice@other.com") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenIsSetConditionAndTraitExists_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("plan", SegmentConditionOperator.IsSet, ""));
|
|
var traits = new[] { Trait("plan", "premium") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenIsSetConditionAndTraitDoesNotExist_ThenSegmentDoesNotMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("plan", SegmentConditionOperator.IsSet, ""));
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, [], "user1"), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenIsNotSetConditionAndTraitDoesNotExist_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("plan", SegmentConditionOperator.IsNotSet, ""));
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, [], "user1"), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenInConditionAndValueIsInList_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("country", SegmentConditionOperator.In, "US,UK,CA"));
|
|
var traits = new[] { Trait("country", "UK") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenNotInConditionAndValueIsNotInList_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("country", SegmentConditionOperator.NotIn, "US,UK,CA"));
|
|
var traits = new[] { Trait("country", "AU") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenIsTrueConditionAndValueIsTrue_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("beta", SegmentConditionOperator.IsTrue, ""));
|
|
var traits = new[] { Trait("beta", "true") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenIsFalseConditionAndValueIsFalse_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("beta", SegmentConditionOperator.IsFalse, ""));
|
|
var traits = new[] { Trait("beta", "false") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenModuloConditionIsSatisfied_ThenSegmentMatches()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("userId", SegmentConditionOperator.ModuloValueDivisorRemainder, "3|0"));
|
|
var traits = new[] { Trait("userId", "9") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenModuloConditionIsNotSatisfied_ThenSegmentDoesNotMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.All,
|
|
Condition("userId", SegmentConditionOperator.ModuloValueDivisorRemainder, "3|0"));
|
|
var traits = new[] { Trait("userId", "7") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenPercentageSplitIsDeterministic_ThenSameInputProducesSameResult()
|
|
{
|
|
var segment = new Segment { Id = 1, Name = "Test", ProjectId = 1, CreatedAt = DateTimeOffset.UtcNow };
|
|
var rule = new SegmentRule { SegmentId = 1, Type = SegmentRuleType.All };
|
|
rule.Conditions.Add(Condition("", SegmentConditionOperator.PercentageSplit, "50"));
|
|
segment.Rules.Add(rule);
|
|
|
|
var result1 = _evaluator.Evaluate(segment, [], "user-abc");
|
|
var result2 = _evaluator.Evaluate(segment, [], "user-abc");
|
|
|
|
Assert.That(result1, Is.EqualTo(result2));
|
|
}
|
|
|
|
[Test]
|
|
public void WhenPercentageSplitIs100_ThenAllIdentitiesAreIncluded()
|
|
{
|
|
var segment = new Segment { Id = 42, Name = "Test", ProjectId = 1, CreatedAt = DateTimeOffset.UtcNow };
|
|
var rule = new SegmentRule { SegmentId = 42, Type = SegmentRuleType.All };
|
|
rule.Conditions.Add(Condition("", SegmentConditionOperator.PercentageSplit, "100"));
|
|
segment.Rules.Add(rule);
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, [], "any-user"), Is.True);
|
|
Assert.That(_evaluator.Evaluate(segment, [], "another-user"), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenPercentageSplitIs0_ThenNoIdentitiesAreIncluded()
|
|
{
|
|
var segment = new Segment { Id = 99, Name = "Test", ProjectId = 1, CreatedAt = DateTimeOffset.UtcNow };
|
|
var rule = new SegmentRule { SegmentId = 99, Type = SegmentRuleType.All };
|
|
rule.Conditions.Add(Condition("", SegmentConditionOperator.PercentageSplit, "0"));
|
|
segment.Rules.Add(rule);
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, [], "any-user"), Is.False);
|
|
Assert.That(_evaluator.Evaluate(segment, [], "another-user"), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenRuleTypeIsAny_ThenAtLeastOneConditionMustMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.Any,
|
|
Condition("plan", SegmentConditionOperator.Equal, "premium"),
|
|
Condition("plan", SegmentConditionOperator.Equal, "enterprise"));
|
|
var traits = new[] { Trait("plan", "enterprise") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenRuleTypeIsNone_ThenNoConditionMustMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.None,
|
|
Condition("plan", SegmentConditionOperator.Equal, "premium"),
|
|
Condition("plan", SegmentConditionOperator.Equal, "enterprise"));
|
|
var traits = new[] { Trait("plan", "free") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenRuleTypeIsNoneAndOneConditionMatches_ThenSegmentDoesNotMatch()
|
|
{
|
|
var segment = BuildSegment(SegmentRuleType.None,
|
|
Condition("plan", SegmentConditionOperator.Equal, "premium"));
|
|
var traits = new[] { Trait("plan", "premium") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, traits), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void WhenAllTopLevelRulesMustPass_ThenSegmentOnlyMatchesWhenAllPass()
|
|
{
|
|
var segment = new Segment { Name = "Test", ProjectId = 1, CreatedAt = DateTimeOffset.UtcNow };
|
|
|
|
var rule1 = new SegmentRule { Type = SegmentRuleType.All };
|
|
rule1.Conditions.Add(Condition("plan", SegmentConditionOperator.Equal, "premium"));
|
|
|
|
var rule2 = new SegmentRule { Type = SegmentRuleType.All };
|
|
rule2.Conditions.Add(Condition("country", SegmentConditionOperator.Equal, "US"));
|
|
|
|
segment.Rules.Add(rule1);
|
|
segment.Rules.Add(rule2);
|
|
|
|
var matchingTraits = new[] { Trait("plan", "premium"), Trait("country", "US") };
|
|
var nonMatchingTraits = new[] { Trait("plan", "premium"), Trait("country", "UK") };
|
|
|
|
Assert.That(_evaluator.Evaluate(segment, matchingTraits), Is.True);
|
|
Assert.That(_evaluator.Evaluate(segment, nonMatchingTraits), Is.False);
|
|
}
|
|
}
|