-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathLocalFeatureFlagsLoaderTests.cs
More file actions
114 lines (95 loc) · 3.75 KB
/
Copy pathLocalFeatureFlagsLoaderTests.cs
File metadata and controls
114 lines (95 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
using System.Net;
using PostHog;
using UnitTests.Fakes;
#if NETCOREAPP3_1
using TestLibrary.Fakes.Polyfills;
#endif
namespace LocalFeatureFlagsLoaderTests;
public class TheDisposeAsyncMethod
{
const string LocalEvaluationResponse = """
{
"flags": [
{
"key": "test-flag",
"active": true,
"rollout_percentage": 100,
"filters": {
"groups": [
{
"properties": [],
"rollout_percentage": 100
}
]
}
}
]
}
""";
static readonly Uri LocalEvaluationUrl =
new("https://us.i.posthog.com/api/feature_flag/local_evaluation?token=fake-project-api-key&send_cohorts");
[Fact]
public async Task CompletesGracefullyDuringInFlightPoll()
{
var container = new TestContainer("fake-personal-api-key");
var pollStarted = new TaskCompletionSource();
var pollCanProceed = new TaskCompletionSource();
// First response succeeds immediately (the initial load).
container.FakeHttpMessageHandler.AddLocalEvaluationResponse(LocalEvaluationResponse);
// Second response (the timer-triggered poll) blocks until we signal it.
container.FakeHttpMessageHandler.AddResponse(
LocalEvaluationUrl,
HttpMethod.Get,
async () =>
{
pollStarted.SetResult();
await pollCanProceed.Task;
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(
LocalEvaluationResponse,
System.Text.Encoding.UTF8,
"application/json")
};
});
var client = container.Activate<PostHogClient>();
// Initial load starts the polling loop and makes the first API call.
await client.LoadFeatureFlagsAsync(CancellationToken.None);
// Advance past the poll interval so the background poll fires.
container.FakeTimeProvider.Advance(TimeSpan.FromSeconds(31));
// Wait for the poll's API call to begin.
await pollStarted.Task;
// Begin disposal while the poll is mid-flight.
var disposeTask = client.DisposeAsync().AsTask();
// Unblock the in-flight API call so the poll can finish.
pollCanProceed.SetResult();
// Verify disposal completes without deadlock or exception.
var timeout = TimeSpan.FromSeconds(5);
var completed = await Task.WhenAny(disposeTask, Task.Delay(timeout));
if (completed != disposeTask)
{
throw new TimeoutException("DisposeAsync did not complete within 5 seconds; possible deadlock.");
}
// Surface any exception thrown during disposal.
await disposeTask;
}
[Fact]
public async Task DoesNotDisposeTwice()
{
var container = new TestContainer("fake-personal-api-key");
container.FakeHttpMessageHandler.AddLocalEvaluationResponse(LocalEvaluationResponse);
var client = container.Activate<PostHogClient>();
await client.LoadFeatureFlagsAsync(CancellationToken.None);
await Task.WhenAll(
client.DisposeAsync().AsTask(),
client.DisposeAsync().AsTask());
}
[Fact]
public async Task CompletesGracefullyWhenPollingNeverStarted()
{
var container = new TestContainer();
var client = container.Activate<PostHogClient>();
// Dispose without ever calling LoadFeatureFlagsAsync.
await client.DisposeAsync();
}
}