Skip to content

Commit 743b767

Browse files
Copilotkhvn26
andauthored
feat: Send a standard User-Agent: sdk-name/version header (#172)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: khvn26 <979078+khvn26@users.noreply.github.com>
1 parent a6cc439 commit 743b767

File tree

8 files changed

+189
-5
lines changed

8 files changed

+189
-5
lines changed

Flagsmith.Client.Test/Fixtures.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ public static string ApiFlagResponseWithTenFlags
169169
environment = 1,
170170
};
171171
flags.Add(flag);
172-
};
172+
}
173+
;
173174
var json = JsonConvert.SerializeObject(flags);
174175
// Return the JSON string representation of the flags list
175176
return json;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Flagsmith;
2+
using Xunit;
3+
4+
namespace Flagsmith.FlagsmithClientTest
5+
{
6+
public class SdkVersionTest
7+
{
8+
[Fact]
9+
public void TestGetUserAgentReturnsExpectedVersion()
10+
{
11+
// x-release-please-start-version
12+
string expectedVersion = "8.0.2";
13+
// x-release-please-end
14+
15+
// When
16+
var userAgent = SdkVersion.GetUserAgent();
17+
18+
// Then
19+
Assert.Equal($"flagsmith-dotnet-sdk/{expectedVersion}", userAgent);
20+
}
21+
}
22+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Flagsmith;
9+
using Moq;
10+
using Xunit;
11+
12+
namespace Flagsmith.FlagsmithClientTest
13+
{
14+
public class UserAgentTest
15+
{
16+
[Fact]
17+
public async Task TestUserAgentHeaderIsSentInGetEnvironmentFlags()
18+
{
19+
// Given
20+
HttpRequestMessage capturedRequest = null!;
21+
22+
var httpClientMock = new Mock<HttpClient>();
23+
httpClientMock.Setup(x => x.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>()))
24+
.Callback<HttpRequestMessage, CancellationToken>((request, token) =>
25+
{
26+
capturedRequest = request;
27+
})
28+
.ReturnsAsync(new HttpResponseMessage
29+
{
30+
StatusCode = HttpStatusCode.OK,
31+
Content = new StringContent(Fixtures.ApiFlagResponse)
32+
});
33+
34+
var config = new FlagsmithConfiguration
35+
{
36+
EnvironmentKey = Fixtures.ApiKey,
37+
HttpClient = httpClientMock.Object
38+
};
39+
40+
var client = new FlagsmithClient(config);
41+
42+
// When
43+
await client.GetEnvironmentFlags();
44+
45+
// Then
46+
Assert.NotNull(capturedRequest);
47+
Assert.True(capturedRequest.Headers.Contains("User-Agent"));
48+
49+
var userAgentValues = capturedRequest.Headers.GetValues("User-Agent").ToList();
50+
Assert.Single(userAgentValues);
51+
52+
var userAgent = userAgentValues[0];
53+
Assert.Equal(SdkVersion.GetUserAgent(), userAgent);
54+
}
55+
56+
[Fact]
57+
public async Task TestUserAgentHeaderIsSentInGetIdentityFlags()
58+
{
59+
// Given
60+
HttpRequestMessage capturedRequest = null!;
61+
62+
var httpClientMock = new Mock<HttpClient>();
63+
httpClientMock.Setup(x => x.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>()))
64+
.Callback<HttpRequestMessage, CancellationToken>((request, token) =>
65+
{
66+
capturedRequest = request;
67+
})
68+
.ReturnsAsync(new HttpResponseMessage
69+
{
70+
StatusCode = HttpStatusCode.OK,
71+
Content = new StringContent(Fixtures.ApiIdentityResponse)
72+
});
73+
74+
var config = new FlagsmithConfiguration
75+
{
76+
EnvironmentKey = Fixtures.ApiKey,
77+
HttpClient = httpClientMock.Object
78+
};
79+
80+
var client = new FlagsmithClient(config);
81+
82+
// When
83+
await client.GetIdentityFlags("test-identity");
84+
85+
// Then
86+
Assert.NotNull(capturedRequest);
87+
Assert.True(capturedRequest.Headers.Contains("User-Agent"));
88+
89+
var userAgentValues = capturedRequest.Headers.GetValues("User-Agent").ToList();
90+
Assert.Single(userAgentValues);
91+
92+
var userAgent = userAgentValues[0];
93+
Assert.Equal(SdkVersion.GetUserAgent(), userAgent);
94+
}
95+
96+
[Fact]
97+
public async Task TestUserAgentHeaderIsSentInAnalyticsFlush()
98+
{
99+
// Given
100+
HttpRequestMessage capturedRequest = null!;
101+
102+
var httpClientMock = new Mock<HttpClient>();
103+
httpClientMock.Setup(x => x.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>()))
104+
.Callback<HttpRequestMessage, CancellationToken>((request, token) =>
105+
{
106+
capturedRequest = request;
107+
})
108+
.ReturnsAsync(new HttpResponseMessage
109+
{
110+
StatusCode = HttpStatusCode.OK
111+
});
112+
113+
var analyticsProcessor = new AnalyticsProcessor(
114+
httpClientMock.Object,
115+
Fixtures.ApiKey,
116+
Fixtures.ApiUrl
117+
);
118+
119+
// When
120+
await analyticsProcessor.TrackFeature("test_feature");
121+
await analyticsProcessor.Flush();
122+
123+
// Then
124+
Assert.NotNull(capturedRequest);
125+
Assert.True(capturedRequest.Headers.Contains("User-Agent"));
126+
127+
var userAgentValues = capturedRequest.Headers.GetValues("User-Agent").ToList();
128+
Assert.Single(userAgentValues);
129+
130+
var userAgent = userAgentValues[0];
131+
Assert.Equal(SdkVersion.GetUserAgent(), userAgent);
132+
}
133+
}
134+
}

Flagsmith.Engine/Engine.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public Dictionary<FeatureModel, FeatureStateModel> GetIdentityFeatureStatesMappi
7070
}
7171

7272
featureStates[feature] = featureState;
73-
};
73+
}
74+
;
7475
}
7576
identity.IdentityFeatures?.ForEach(x =>
7677
{

Flagsmith.FlagsmithClient/AnalyticsProcessor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ private async Task FlushWithoutLock()
7575
{
7676
Headers =
7777
{
78-
{ "X-Environment-Key", _EnvironmentKey }
78+
{ "X-Environment-Key", _EnvironmentKey },
79+
{ "User-Agent", SdkVersion.GetUserAgent() }
7980
},
8081
Content = new StringContent(analyticsJson, Encoding.UTF8, "application/json")
8182
};

Flagsmith.FlagsmithClient/FlagsmithClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ private async Task<string> GetJson(HttpMethod method, string url, string? body =
196196
{
197197
Headers =
198198
{
199-
{ "X-Environment-Key", _config.EnvironmentKey }
199+
{ "X-Environment-Key", _config.EnvironmentKey },
200+
{ "User-Agent", SdkVersion.GetUserAgent() }
200201
}
201202
};
202203
_config.CustomHeaders?.ForEach(kvp => request.Headers.Add(kvp.Key, kvp.Value));
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#nullable enable
2+
3+
namespace Flagsmith
4+
{
5+
/// <summary>
6+
/// Provides SDK version information for User-Agent header
7+
/// </summary>
8+
public static class SdkVersion
9+
{
10+
// x-release-please-start-version
11+
private const string Version = "8.0.2";
12+
// x-release-please-end
13+
14+
/// <summary>
15+
/// Gets the SDK version in the format "flagsmith-dotnet-sdk/version"
16+
/// </summary>
17+
public static string GetUserAgent()
18+
{
19+
return $"flagsmith-dotnet-sdk/{Version}";
20+
}
21+
}
22+
}

release-please-config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"type": "xml",
1616
"path": "Flagsmith.FlagsmithClient/Flagsmith.FlagsmithClient.csproj",
1717
"xpath": "//Project/PropertyGroup/Version"
18-
}
18+
},
19+
"Flagsmith.FlagsmithClient/SdkVersion.cs",
20+
"Flagsmith.Client.Test/SdkVersionTest.cs"
1921
]
2022
}
2123
},

0 commit comments

Comments
 (0)