diff --git a/Flagsmith.Client.Test/Fixtures.cs b/Flagsmith.Client.Test/Fixtures.cs index 6cd17af..5a3c5a6 100644 --- a/Flagsmith.Client.Test/Fixtures.cs +++ b/Flagsmith.Client.Test/Fixtures.cs @@ -169,7 +169,8 @@ public static string ApiFlagResponseWithTenFlags environment = 1, }; flags.Add(flag); - }; + } + ; var json = JsonConvert.SerializeObject(flags); // Return the JSON string representation of the flags list return json; diff --git a/Flagsmith.Client.Test/SdkVersionTest.cs b/Flagsmith.Client.Test/SdkVersionTest.cs new file mode 100644 index 0000000..62dbd7c --- /dev/null +++ b/Flagsmith.Client.Test/SdkVersionTest.cs @@ -0,0 +1,22 @@ +using Flagsmith; +using Xunit; + +namespace Flagsmith.FlagsmithClientTest +{ + public class SdkVersionTest + { + [Fact] + public void TestGetUserAgentReturnsExpectedVersion() + { + // x-release-please-start-version + string expectedVersion = "8.0.2"; + // x-release-please-end + + // When + var userAgent = SdkVersion.GetUserAgent(); + + // Then + Assert.Equal($"flagsmith-dotnet-sdk/{expectedVersion}", userAgent); + } + } +} diff --git a/Flagsmith.Client.Test/UserAgentTest.cs b/Flagsmith.Client.Test/UserAgentTest.cs new file mode 100644 index 0000000..14b2346 --- /dev/null +++ b/Flagsmith.Client.Test/UserAgentTest.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Flagsmith; +using Moq; +using Xunit; + +namespace Flagsmith.FlagsmithClientTest +{ + public class UserAgentTest + { + [Fact] + public async Task TestUserAgentHeaderIsSentInGetEnvironmentFlags() + { + // Given + HttpRequestMessage capturedRequest = null!; + + var httpClientMock = new Mock(); + httpClientMock.Setup(x => x.SendAsync(It.IsAny(), It.IsAny())) + .Callback((request, token) => + { + capturedRequest = request; + }) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(Fixtures.ApiFlagResponse) + }); + + var config = new FlagsmithConfiguration + { + EnvironmentKey = Fixtures.ApiKey, + HttpClient = httpClientMock.Object + }; + + var client = new FlagsmithClient(config); + + // When + await client.GetEnvironmentFlags(); + + // Then + Assert.NotNull(capturedRequest); + Assert.True(capturedRequest.Headers.Contains("User-Agent")); + + var userAgentValues = capturedRequest.Headers.GetValues("User-Agent").ToList(); + Assert.Single(userAgentValues); + + var userAgent = userAgentValues[0]; + Assert.Equal(SdkVersion.GetUserAgent(), userAgent); + } + + [Fact] + public async Task TestUserAgentHeaderIsSentInGetIdentityFlags() + { + // Given + HttpRequestMessage capturedRequest = null!; + + var httpClientMock = new Mock(); + httpClientMock.Setup(x => x.SendAsync(It.IsAny(), It.IsAny())) + .Callback((request, token) => + { + capturedRequest = request; + }) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(Fixtures.ApiIdentityResponse) + }); + + var config = new FlagsmithConfiguration + { + EnvironmentKey = Fixtures.ApiKey, + HttpClient = httpClientMock.Object + }; + + var client = new FlagsmithClient(config); + + // When + await client.GetIdentityFlags("test-identity"); + + // Then + Assert.NotNull(capturedRequest); + Assert.True(capturedRequest.Headers.Contains("User-Agent")); + + var userAgentValues = capturedRequest.Headers.GetValues("User-Agent").ToList(); + Assert.Single(userAgentValues); + + var userAgent = userAgentValues[0]; + Assert.Equal(SdkVersion.GetUserAgent(), userAgent); + } + + [Fact] + public async Task TestUserAgentHeaderIsSentInAnalyticsFlush() + { + // Given + HttpRequestMessage capturedRequest = null!; + + var httpClientMock = new Mock(); + httpClientMock.Setup(x => x.SendAsync(It.IsAny(), It.IsAny())) + .Callback((request, token) => + { + capturedRequest = request; + }) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK + }); + + var analyticsProcessor = new AnalyticsProcessor( + httpClientMock.Object, + Fixtures.ApiKey, + Fixtures.ApiUrl + ); + + // When + await analyticsProcessor.TrackFeature("test_feature"); + await analyticsProcessor.Flush(); + + // Then + Assert.NotNull(capturedRequest); + Assert.True(capturedRequest.Headers.Contains("User-Agent")); + + var userAgentValues = capturedRequest.Headers.GetValues("User-Agent").ToList(); + Assert.Single(userAgentValues); + + var userAgent = userAgentValues[0]; + Assert.Equal(SdkVersion.GetUserAgent(), userAgent); + } + } +} diff --git a/Flagsmith.Engine/Engine.cs b/Flagsmith.Engine/Engine.cs index 2b28057..8316702 100644 --- a/Flagsmith.Engine/Engine.cs +++ b/Flagsmith.Engine/Engine.cs @@ -70,7 +70,8 @@ public Dictionary GetIdentityFeatureStatesMappi } featureStates[feature] = featureState; - }; + } + ; } identity.IdentityFeatures?.ForEach(x => { diff --git a/Flagsmith.FlagsmithClient/AnalyticsProcessor.cs b/Flagsmith.FlagsmithClient/AnalyticsProcessor.cs index a4e0436..99f4201 100644 --- a/Flagsmith.FlagsmithClient/AnalyticsProcessor.cs +++ b/Flagsmith.FlagsmithClient/AnalyticsProcessor.cs @@ -75,7 +75,8 @@ private async Task FlushWithoutLock() { Headers = { - { "X-Environment-Key", _EnvironmentKey } + { "X-Environment-Key", _EnvironmentKey }, + { "User-Agent", SdkVersion.GetUserAgent() } }, Content = new StringContent(analyticsJson, Encoding.UTF8, "application/json") }; diff --git a/Flagsmith.FlagsmithClient/FlagsmithClient.cs b/Flagsmith.FlagsmithClient/FlagsmithClient.cs index 55ccf8d..d4c28f6 100644 --- a/Flagsmith.FlagsmithClient/FlagsmithClient.cs +++ b/Flagsmith.FlagsmithClient/FlagsmithClient.cs @@ -196,7 +196,8 @@ private async Task GetJson(HttpMethod method, string url, string? body = { Headers = { - { "X-Environment-Key", _config.EnvironmentKey } + { "X-Environment-Key", _config.EnvironmentKey }, + { "User-Agent", SdkVersion.GetUserAgent() } } }; _config.CustomHeaders?.ForEach(kvp => request.Headers.Add(kvp.Key, kvp.Value)); diff --git a/Flagsmith.FlagsmithClient/SdkVersion.cs b/Flagsmith.FlagsmithClient/SdkVersion.cs new file mode 100644 index 0000000..dc50d9e --- /dev/null +++ b/Flagsmith.FlagsmithClient/SdkVersion.cs @@ -0,0 +1,22 @@ +#nullable enable + +namespace Flagsmith +{ + /// + /// Provides SDK version information for User-Agent header + /// + public static class SdkVersion + { + // x-release-please-start-version + private const string Version = "8.0.2"; + // x-release-please-end + + /// + /// Gets the SDK version in the format "flagsmith-dotnet-sdk/version" + /// + public static string GetUserAgent() + { + return $"flagsmith-dotnet-sdk/{Version}"; + } + } +} diff --git a/release-please-config.json b/release-please-config.json index 727affa..bd97ccf 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -15,7 +15,9 @@ "type": "xml", "path": "Flagsmith.FlagsmithClient/Flagsmith.FlagsmithClient.csproj", "xpath": "//Project/PropertyGroup/Version" - } + }, + "Flagsmith.FlagsmithClient/SdkVersion.cs", + "Flagsmith.Client.Test/SdkVersionTest.cs" ] } },