Skip to content

Commit 4f89311

Browse files
committed
feat: Add comprehensive tracking tests for MultiProvider functionality
Signed-off-by: André Silva <[email protected]>
1 parent 60619fb commit 4f89311

File tree

1 file changed

+299
-0
lines changed

1 file changed

+299
-0
lines changed
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
using NSubstitute;
2+
using OpenFeature.Constant;
3+
using OpenFeature.Model;
4+
using OpenFeature.Providers.MultiProvider.Models;
5+
using OpenFeature.Providers.MultiProvider.Strategies;
6+
using OpenFeature.Providers.MultiProvider.Strategies.Models;
7+
using OpenFeature.Providers.MultiProvider.Tests.Utils;
8+
9+
namespace OpenFeature.Providers.MultiProvider.Tests;
10+
11+
public class MultiProviderTrackingTests
12+
{
13+
private const string TestTrackingEventName = "test-event";
14+
private const string Provider1Name = "provider1";
15+
private const string Provider2Name = "provider2";
16+
private const string Provider3Name = "provider3";
17+
18+
private readonly TestProvider _testProvider1 = new(Provider1Name);
19+
private readonly TestProvider _testProvider2 = new(Provider2Name);
20+
private readonly TestProvider _testProvider3 = new(Provider3Name);
21+
private readonly EvaluationContext _evaluationContext = EvaluationContext.Builder().Build();
22+
23+
[Fact]
24+
public async Task Track_WithMultipleReadyProviders_CallsTrackOnAllReadyProviders()
25+
{
26+
// Arrange
27+
var providerEntries = new List<ProviderEntry>
28+
{
29+
new(this._testProvider1, Provider1Name),
30+
new(this._testProvider2, Provider2Name),
31+
new(this._testProvider3, Provider3Name)
32+
};
33+
34+
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
35+
await multiProvider.InitializeAsync(this._evaluationContext);
36+
37+
var trackingDetails = TrackingEventDetails.Builder().SetValue(99.99).Build();
38+
39+
// Act
40+
multiProvider.Track(TestTrackingEventName, this._evaluationContext, trackingDetails);
41+
42+
// Assert
43+
var provider1Invocations = this._testProvider1.GetTrackingInvocations();
44+
var provider2Invocations = this._testProvider2.GetTrackingInvocations();
45+
var provider3Invocations = this._testProvider3.GetTrackingInvocations();
46+
47+
Assert.Single(provider1Invocations);
48+
Assert.Single(provider2Invocations);
49+
Assert.Single(provider3Invocations);
50+
51+
Assert.Equal(TestTrackingEventName, provider1Invocations[0].EventName);
52+
Assert.Equal(TestTrackingEventName, provider2Invocations[0].EventName);
53+
Assert.Equal(TestTrackingEventName, provider3Invocations[0].EventName);
54+
55+
Assert.Equal(trackingDetails.Value, provider1Invocations[0].TrackingEventDetails?.Value);
56+
Assert.Equal(trackingDetails.Value, provider2Invocations[0].TrackingEventDetails?.Value);
57+
Assert.Equal(trackingDetails.Value, provider3Invocations[0].TrackingEventDetails?.Value);
58+
}
59+
60+
[Fact]
61+
public async Task Track_WithNullEvaluationContext_CallsTrackWithNullContext()
62+
{
63+
// Arrange
64+
var providerEntries = new List<ProviderEntry>
65+
{
66+
new(this._testProvider1, Provider1Name),
67+
new(this._testProvider2, Provider2Name)
68+
};
69+
70+
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
71+
await multiProvider.InitializeAsync(this._evaluationContext);
72+
73+
// Act
74+
multiProvider.Track(TestTrackingEventName);
75+
76+
// Assert
77+
var provider1Invocations = this._testProvider1.GetTrackingInvocations();
78+
var provider2Invocations = this._testProvider2.GetTrackingInvocations();
79+
80+
Assert.Single(provider1Invocations);
81+
Assert.Single(provider2Invocations);
82+
83+
Assert.Equal(TestTrackingEventName, provider1Invocations[0].EventName);
84+
Assert.Equal(TestTrackingEventName, provider2Invocations[0].EventName);
85+
}
86+
87+
[Fact]
88+
public async Task Track_WithNullTrackingDetails_CallsTrackWithNullDetails()
89+
{
90+
// Arrange
91+
var providerEntries = new List<ProviderEntry>
92+
{
93+
new(this._testProvider1, Provider1Name),
94+
new(this._testProvider2, Provider2Name)
95+
};
96+
97+
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
98+
await multiProvider.InitializeAsync(this._evaluationContext);
99+
100+
// Act
101+
multiProvider.Track(TestTrackingEventName, this._evaluationContext);
102+
103+
// Assert
104+
var provider1Invocations = this._testProvider1.GetTrackingInvocations();
105+
var provider2Invocations = this._testProvider2.GetTrackingInvocations();
106+
107+
Assert.Single(provider1Invocations);
108+
Assert.Single(provider2Invocations);
109+
110+
Assert.Equal(TestTrackingEventName, provider1Invocations[0].EventName);
111+
Assert.Null(provider1Invocations[0].TrackingEventDetails);
112+
113+
Assert.Equal(TestTrackingEventName, provider2Invocations[0].EventName);
114+
Assert.Null(provider2Invocations[0].TrackingEventDetails);
115+
}
116+
117+
[Fact]
118+
public async Task Track_WhenProviderThrowsException_ContinuesWithOtherProviders()
119+
{
120+
// Arrange
121+
var throwingProvider = Substitute.For<FeatureProvider>();
122+
throwingProvider.GetMetadata().Returns(new Metadata(Provider2Name));
123+
throwingProvider.When(x => x.Track(Arg.Any<string>(), Arg.Any<EvaluationContext>(), Arg.Any<TrackingEventDetails>()))
124+
.Do(_ => throw new InvalidOperationException("Test exception"));
125+
126+
var providerEntries = new List<ProviderEntry>
127+
{
128+
new(this._testProvider1, Provider1Name),
129+
new(throwingProvider, Provider2Name),
130+
new(this._testProvider3, Provider3Name)
131+
};
132+
133+
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
134+
await multiProvider.InitializeAsync(this._evaluationContext);
135+
136+
// Manually set all providers to Ready status
137+
throwingProvider.Status.Returns(ProviderStatus.Ready);
138+
139+
var trackingDetails = TrackingEventDetails.Builder().SetValue(99.99).Build();
140+
141+
// Act
142+
multiProvider.Track(TestTrackingEventName, this._evaluationContext, trackingDetails);
143+
144+
// Assert - should not throw and should continue with other providers
145+
var provider1Invocations = this._testProvider1.GetTrackingInvocations();
146+
var provider3Invocations = this._testProvider3.GetTrackingInvocations();
147+
148+
Assert.Single(provider1Invocations);
149+
Assert.Single(provider3Invocations);
150+
151+
throwingProvider.Received(1).Track(TestTrackingEventName, Arg.Any<EvaluationContext>(), trackingDetails);
152+
}
153+
154+
[Fact]
155+
public async Task Track_WhenDisposed_ThrowsObjectDisposedException()
156+
{
157+
// Arrange
158+
var providerEntries = new List<ProviderEntry>
159+
{
160+
new(this._testProvider1, Provider1Name)
161+
};
162+
163+
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
164+
await multiProvider.InitializeAsync(this._evaluationContext);
165+
await multiProvider.DisposeAsync();
166+
167+
// Act & Assert
168+
Assert.Throws<ObjectDisposedException>(() => multiProvider.Track(TestTrackingEventName, this._evaluationContext));
169+
}
170+
171+
[Fact]
172+
public async Task Track_WithCustomStrategy_RespectsStrategyDecision()
173+
{
174+
// Arrange
175+
var customStrategy = Substitute.For<BaseEvaluationStrategy>();
176+
customStrategy.RunMode.Returns(RunMode.Sequential);
177+
178+
// Only allow tracking with the first provider
179+
customStrategy.ShouldTrackWithThisProvider(
180+
Arg.Is<StrategyPerProviderContext<object>>(ctx => ctx.ProviderName == Provider1Name),
181+
Arg.Any<EvaluationContext>(),
182+
Arg.Any<string>(),
183+
Arg.Any<TrackingEventDetails>()
184+
).Returns(true);
185+
186+
customStrategy.ShouldTrackWithThisProvider(
187+
Arg.Is<StrategyPerProviderContext<object>>(ctx => ctx.ProviderName != Provider1Name),
188+
Arg.Any<EvaluationContext>(),
189+
Arg.Any<string>(),
190+
Arg.Any<TrackingEventDetails>()
191+
).Returns(false);
192+
193+
var providerEntries = new List<ProviderEntry>
194+
{
195+
new(this._testProvider1, Provider1Name),
196+
new(this._testProvider2, Provider2Name),
197+
new(this._testProvider3, Provider3Name)
198+
};
199+
200+
var multiProvider = new MultiProvider(providerEntries, customStrategy);
201+
await multiProvider.InitializeAsync(this._evaluationContext);
202+
203+
var trackingDetails = TrackingEventDetails.Builder().SetValue(99.99).Build();
204+
205+
// Act
206+
multiProvider.Track(TestTrackingEventName, this._evaluationContext, trackingDetails);
207+
208+
// Assert - only provider1 should receive the tracking call
209+
var provider1Invocations = this._testProvider1.GetTrackingInvocations();
210+
var provider2Invocations = this._testProvider2.GetTrackingInvocations();
211+
var provider3Invocations = this._testProvider3.GetTrackingInvocations();
212+
213+
Assert.Single(provider1Invocations);
214+
Assert.Empty(provider2Invocations);
215+
Assert.Empty(provider3Invocations);
216+
217+
customStrategy.Received(3).ShouldTrackWithThisProvider(
218+
Arg.Any<StrategyPerProviderContext<object>>(),
219+
Arg.Any<EvaluationContext>(),
220+
TestTrackingEventName,
221+
trackingDetails
222+
);
223+
}
224+
225+
[Fact]
226+
public async Task Track_WithComplexTrackingDetails_PropagatesAllDetails()
227+
{
228+
// Arrange
229+
var providerEntries = new List<ProviderEntry>
230+
{
231+
new(this._testProvider1, Provider1Name),
232+
new(this._testProvider2, Provider2Name)
233+
};
234+
235+
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
236+
await multiProvider.InitializeAsync(this._evaluationContext);
237+
238+
var trackingDetails = TrackingEventDetails.Builder()
239+
.SetValue(199.99)
240+
.Set("currency", new Value("USD"))
241+
.Set("productId", new Value("prod-123"))
242+
.Set("quantity", new Value(5))
243+
.Build();
244+
245+
// Act
246+
multiProvider.Track(TestTrackingEventName, this._evaluationContext, trackingDetails);
247+
248+
// Assert
249+
var provider1Invocations = this._testProvider1.GetTrackingInvocations();
250+
var provider2Invocations = this._testProvider2.GetTrackingInvocations();
251+
252+
Assert.Single(provider1Invocations);
253+
Assert.Single(provider2Invocations);
254+
255+
var details1 = provider1Invocations[0].TrackingEventDetails;
256+
var details2 = provider2Invocations[0].TrackingEventDetails;
257+
258+
Assert.NotNull(details1);
259+
Assert.NotNull(details2);
260+
261+
Assert.Equal(199.99, details1.Value);
262+
Assert.Equal(199.99, details2.Value);
263+
264+
Assert.Equal("USD", details1.GetValue("currency").AsString);
265+
Assert.Equal("USD", details2.GetValue("currency").AsString);
266+
267+
Assert.Equal("prod-123", details1.GetValue("productId").AsString);
268+
Assert.Equal("prod-123", details2.GetValue("productId").AsString);
269+
270+
Assert.Equal(5, details1.GetValue("quantity").AsInteger);
271+
Assert.Equal(5, details2.GetValue("quantity").AsInteger);
272+
}
273+
274+
[Theory]
275+
[InlineData(null)]
276+
[InlineData("")]
277+
[InlineData(" ")]
278+
public async Task Track_WhenEmptyTrackingDetails_DoesNotThrow(string? trackingEventName)
279+
{
280+
// Arrange
281+
var providerEntries = new List<ProviderEntry>
282+
{
283+
new(this._testProvider1, Provider1Name),
284+
new(this._testProvider2, Provider2Name)
285+
};
286+
287+
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
288+
await multiProvider.InitializeAsync(this._evaluationContext);
289+
290+
// Act & Assert
291+
multiProvider.Track(trackingEventName!, this._evaluationContext, TrackingEventDetails.Empty);
292+
293+
var provider1Invocations = this._testProvider1.GetTrackingInvocations();
294+
var provider2Invocations = this._testProvider2.GetTrackingInvocations();
295+
296+
Assert.Empty(provider1Invocations);
297+
Assert.Empty(provider2Invocations);
298+
}
299+
}

0 commit comments

Comments
 (0)