diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index 47e69860..340df917 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -2,9 +2,9 @@ name: Continuous Integration on: push: - branches: [ release-3.11.3 ] + branches: [ release-3.11.4 ] pull_request: - branches: [ release-3.11.3 ] + branches: [ release-3.11.4 ] jobs: lintCodebase: @@ -101,7 +101,7 @@ jobs: integration_tests: name: Run Integration Tests needs: [ netFrameworksAndUnitTest, netStandard16, netStandard20 ] - uses: optimizely/csharp-sdk/.github/workflows/integration_test.yml@cb1c68ba0847f04ea54384c9b4500502c00681e6 + uses: optimizely/csharp-sdk/.github/workflows/integration_test.yml@mike/pick-prep-3.11.4 secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} @@ -109,7 +109,7 @@ jobs: fullstack_production_suite: name: Run Performance Tests needs: [ netFrameworksAndUnitTest, netStandard16, netStandard20 ] - uses: optimizely/csharp-sdk/.github/workflows/integration_test.yml@cb1c68ba0847f04ea54384c9b4500502c00681e6 + uses: optimizely/csharp-sdk/.github/workflows/integration_test.yml@mike/pick-prep-3.11.4 with: FULLSTACK_TEST_REPO: ProdTesting secrets: diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index eaa54405..6540da3f 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -41,7 +41,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.CI_USER_TOKEN }} EVENT_TYPE: ${{ github.event_name }} GITHUB_CONTEXT: ${{ toJson(github) }} - REPO_SLUG: optimizely/fullstack-sdk-compatibility-suite@b45d804ce7670090341fcbae91f5daa39b03aa94 + # REPO_SLUG: ${{ github.repository }} PULL_REQUEST_SLUG: ${{ github.repository }} UPSTREAM_REPO: ${{ github.repository }} PULL_REQUEST_SHA: ${{ github.event.pull_request.head.sha }} @@ -52,4 +52,6 @@ jobs: HOME: 'home/runner' run: | echo "$GITHUB_CONTEXT" - home/runner/travisci-tools/trigger-script-with-status-update.sh + # Adding the FSC branch that does not include tests for ODP (before-odp) + # Remember to also disable the ODP_SERVER and ATS_API features in app-dot + home/runner/travisci-tools/trigger-script-with-status-update.sh before-odp diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a777ff..8b6c9f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Optimizely C# SDK Changelog +## 3.11.4 +July 26, 2023 + +### Bug Fix +- Fix Last-Modified date & time format for If-Modified-Since ([#361](https://github.com/optimizely/csharp-sdk/pull/361)). + ## 3.11.3 July 18, 2023 diff --git a/OptimizelySDK.DemoApp/Properties/AssemblyInfo.cs b/OptimizelySDK.DemoApp/Properties/AssemblyInfo.cs index 95223eb2..3427d947 100644 --- a/OptimizelySDK.DemoApp/Properties/AssemblyInfo.cs +++ b/OptimizelySDK.DemoApp/Properties/AssemblyInfo.cs @@ -35,7 +35,7 @@ // Build Number // Revision // -[assembly: AssemblyVersion("3.11.3.0")] -[assembly: AssemblyFileVersion("3.11.3.0")] -[assembly: AssemblyInformationalVersion("3.11.3")] // Used by Nuget. +[assembly: AssemblyVersion("3.11.4.0")] +[assembly: AssemblyFileVersion("3.11.4.0")] +[assembly: AssemblyInformationalVersion("3.11.4")] // Used by Nuget. diff --git a/OptimizelySDK.Net35/Properties/AssemblyInfo.cs b/OptimizelySDK.Net35/Properties/AssemblyInfo.cs index 763e1bd1..676b6492 100644 --- a/OptimizelySDK.Net35/Properties/AssemblyInfo.cs +++ b/OptimizelySDK.Net35/Properties/AssemblyInfo.cs @@ -35,6 +35,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("3.11.3.0")] -[assembly: AssemblyFileVersion("3.11.3.0")] -[assembly: AssemblyInformationalVersion("3.11.3")] // Used by Nuget. +[assembly: AssemblyVersion("3.11.4.0")] +[assembly: AssemblyFileVersion("3.11.4.0")] +[assembly: AssemblyInformationalVersion("3.11.4")] // Used by Nuget. diff --git a/OptimizelySDK.Net40/Properties/AssemblyInfo.cs b/OptimizelySDK.Net40/Properties/AssemblyInfo.cs index 1d7137a0..54f5b88a 100644 --- a/OptimizelySDK.Net40/Properties/AssemblyInfo.cs +++ b/OptimizelySDK.Net40/Properties/AssemblyInfo.cs @@ -35,6 +35,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("3.11.3.0")] -[assembly: AssemblyFileVersion("3.11.3.0")] -[assembly: AssemblyInformationalVersion("3.11.3")] // Used by Nuget. +[assembly: AssemblyVersion("3.11.4.0")] +[assembly: AssemblyFileVersion("3.11.4.0")] +[assembly: AssemblyInformationalVersion("3.11.4")] // Used by Nuget. diff --git a/OptimizelySDK.NetStandard16/Properties/AssemblyInfo.cs b/OptimizelySDK.NetStandard16/Properties/AssemblyInfo.cs index 9ecccade..f90fcb0a 100644 --- a/OptimizelySDK.NetStandard16/Properties/AssemblyInfo.cs +++ b/OptimizelySDK.NetStandard16/Properties/AssemblyInfo.cs @@ -35,6 +35,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("3.11.3.0")] -[assembly: AssemblyFileVersion("3.11.3.0")] -[assembly: AssemblyInformationalVersion("3.11.3")] // Used by Nuget. +[assembly: AssemblyVersion("3.11.4.0")] +[assembly: AssemblyFileVersion("3.11.4.0")] +[assembly: AssemblyInformationalVersion("3.11.4")] // Used by Nuget. diff --git a/OptimizelySDK.NetStandard20/Properties/AssemblyInfo.cs b/OptimizelySDK.NetStandard20/Properties/AssemblyInfo.cs index 4f7815f6..4629cb23 100644 --- a/OptimizelySDK.NetStandard20/Properties/AssemblyInfo.cs +++ b/OptimizelySDK.NetStandard20/Properties/AssemblyInfo.cs @@ -35,6 +35,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("3.11.3.0")] -[assembly: AssemblyFileVersion("3.11.3.0")] -[assembly: AssemblyInformationalVersion("3.11.3")] // Used by Nuget. +[assembly: AssemblyVersion("3.11.4.0")] +[assembly: AssemblyFileVersion("3.11.4.0")] +[assembly: AssemblyInformationalVersion("3.11.4")] // Used by Nuget. diff --git a/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs b/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs index 1c558f49..8a4f831b 100644 --- a/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs +++ b/OptimizelySDK.Tests/ConfigTest/HttpProjectConfigManagerTest.cs @@ -22,10 +22,8 @@ using OptimizelySDK.Tests.Utils; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; namespace OptimizelySDK.Tests.DatafileManagement_Tests @@ -39,6 +37,11 @@ public class HttpProjectConfigManagerTest private Mock NotificationCallbackMock = new Mock(); + private const string ExpectedRfc1123DateTime = "Thu, 03 Nov 2022 16:00:00 GMT"; + + private readonly DateTime _pastLastModified = + new DateTimeOffset(new DateTime(2022, 11, 3, 16, 0, 0, DateTimeKind.Utc)).UtcDateTime; + [SetUp] public void Setup() { @@ -54,7 +57,7 @@ public void Setup() public void TestHttpConfigManagerRetrieveProjectConfigByURL() { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() + var httpManager = new HttpProjectConfigManager.Builder() .WithUrl("https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) @@ -67,7 +70,7 @@ public void TestHttpConfigManagerRetrieveProjectConfigByURL() t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); @@ -77,9 +80,9 @@ public void TestHttpConfigManagerRetrieveProjectConfigByURL() [Test] public void TestHttpConfigManagerWithInvalidStatus() { - var t = MockSendAsync(statusCode: HttpStatusCode.Forbidden); + MockSendAsync(statusCode: HttpStatusCode.Forbidden); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() + var httpManager = new HttpProjectConfigManager.Builder() .WithUrl("https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) @@ -102,24 +105,29 @@ public void TestSettingIfModifiedSinceInRequestHeader() statusCode: HttpStatusCode.NotModified, responseContentHeaders: new Dictionary { - { - "Last-Modified", new DateTime(2050, 10, 10).ToString("R") - }, + { "Last-Modified", _pastLastModified.ToString("r") }, } ); var httpManager = new HttpProjectConfigManager.Builder() - .WithDatafile(string.Empty) + .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(2000)) .WithStartByDefault() .Build(defer: true); - httpManager.LastModifiedSince = new DateTime(2020, 4, 4).ToString("R"); + httpManager.LastModifiedSince = _pastLastModified.ToString("r"); t.Wait(3000); + HttpClientMock.Verify(_ => _.SendAsync( + It.Is(requestMessage => + requestMessage.Headers.IfModifiedSince.HasValue && + requestMessage.Headers.IfModifiedSince.Value.UtcDateTime.ToString("r") == + ExpectedRfc1123DateTime + )), Times.Once); LoggerMock.Verify( - _ => _.Log(LogLevel.DEBUG, "Set If-Modified-Since in request header."), + _ => _.Log(LogLevel.DEBUG, + $"Set If-Modified-Since in request header: {ExpectedRfc1123DateTime}"), Times.AtLeastOnce); httpManager.Dispose(); @@ -129,16 +137,15 @@ public void TestSettingIfModifiedSinceInRequestHeader() public void TestSettingLastModifiedFromResponseHeader() { MockSendAsync( + datafile: TestData.Datafile, statusCode: HttpStatusCode.OK, responseContentHeaders: new Dictionary { - { - "Last-Modified", new DateTime(2050, 10, 10).ToString("R") - }, + { "Last-Modified", _pastLastModified.ToString("r") }, } ); var httpManager = new HttpProjectConfigManager.Builder() - .WithUrl("https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json") + .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) .WithBlockingTimeoutPeriod(TimeSpan.FromMilliseconds(500)) @@ -146,7 +153,8 @@ public void TestSettingLastModifiedFromResponseHeader() .Build(); LoggerMock.Verify( - _ => _.Log(LogLevel.DEBUG, "Set LastModifiedSince from response header."), + _ => _.Log(LogLevel.DEBUG, + $"Set LastModifiedSince from response header: {ExpectedRfc1123DateTime}"), Times.AtLeastOnce); httpManager.Dispose(); @@ -157,8 +165,7 @@ public void TestHttpClientHandler() { var httpConfigHandler = HttpProjectConfigManager.HttpClient.GetHttpClientHandler(); Assert.IsTrue(httpConfigHandler.AutomaticDecompression == - (System.Net.DecompressionMethods.Deflate | - System.Net.DecompressionMethods.GZip)); + (DecompressionMethods.Deflate | DecompressionMethods.GZip)); } [Test] @@ -166,7 +173,7 @@ public void TestHttpConfigManagerRetrieveProjectConfigGivenEmptyFormatUseDefault { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() + var httpManager = new HttpProjectConfigManager.Builder() .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") .WithFormat("") .WithLogger(LoggerMock.Object) @@ -179,7 +186,7 @@ public void TestHttpConfigManagerRetrieveProjectConfigGivenEmptyFormatUseDefault // Time is given here to avoid hanging-up in any worst case. t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); @@ -191,7 +198,7 @@ public void TestHttpConfigManagerRetrieveProjectConfigBySDKKey() { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() + var httpManager = new HttpProjectConfigManager.Builder() .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) @@ -201,7 +208,7 @@ public void TestHttpConfigManagerRetrieveProjectConfigBySDKKey() t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); @@ -214,8 +221,7 @@ public void TestHttpConfigManagerRetrieveProjectConfigByFormat() { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("10192104166") + var httpManager = new HttpProjectConfigManager.Builder().WithSdkKey("10192104166") .WithFormat("https://cdn.optimizely.com/json/{0}.json") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) @@ -225,7 +231,7 @@ public void TestHttpConfigManagerRetrieveProjectConfigByFormat() t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/json/10192104166.json" ))); @@ -242,8 +248,7 @@ public void TestHttpProjectConfigManagerDoesntRaiseExceptionForDefaultErrorHandl { var t = MockSendAsync(TestData.Datafile); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() - .WithSdkKey("10192104166") + var httpManager = new HttpProjectConfigManager.Builder().WithSdkKey("10192104166") .WithFormat("https://cdn.optimizely.com/json/{0}.json") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromMilliseconds(1000)) @@ -253,7 +258,7 @@ public void TestHttpProjectConfigManagerDoesntRaiseExceptionForDefaultErrorHandl t.Wait(1000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/json/10192104166.json" ))); @@ -272,7 +277,7 @@ public void TestOnReadyPromiseResolvedImmediatelyWhenDatafileIsProvided() var t = MockSendAsync(TestData.SimpleABExperimentsDatafile, TimeSpan.FromMilliseconds(100)); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() + var httpManager = new HttpProjectConfigManager.Builder() // Revision - 15 .WithSdkKey("10192104166") .WithDatafile(TestData.Datafile) @@ -302,7 +307,7 @@ public void TestOnReadyPromiseWaitsForProjectConfigRetrievalWhenDatafileIsNotPro var t = MockSendAsync(TestData.SimpleABExperimentsDatafile, TimeSpan.FromMilliseconds(1000)); - HttpProjectConfigManager httpManager = new HttpProjectConfigManager.Builder() + var httpManager = new HttpProjectConfigManager.Builder() .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") .WithLogger(LoggerMock.Object) .WithPollingInterval(TimeSpan.FromSeconds(2)) @@ -350,7 +355,7 @@ public void TestHttpConfigManagerDoesNotWaitForTheConfigWhenDeferIsTrue() [Test] public void TestHttpConfigManagerSendConfigUpdateNotificationWhenProjectConfigGetsUpdated() { - var t = MockSendAsync(TestData.Datafile); + MockSendAsync(TestData.Datafile); var httpManager = new HttpProjectConfigManager.Builder() .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") @@ -373,7 +378,7 @@ public void TestHttpConfigManagerSendConfigUpdateNotificationWhenProjectConfigGe [Test] public void TestHttpConfigManagerDoesNotSendConfigUpdateNotificationWhenDatafileIsProvided() { - var t = MockSendAsync(TestData.Datafile, TimeSpan.FromMilliseconds(100)); + MockSendAsync(TestData.Datafile, TimeSpan.FromMilliseconds(100)); var httpManager = new HttpProjectConfigManager.Builder() .WithSdkKey("QBw9gFM8oTn7ogY9ANCC1z") @@ -533,7 +538,7 @@ public void TestAuthUrlWhenTokenProvided() t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "https://config.optimizely.com/datafiles/auth/QBw9gFM8oTn7ogY9ANCC1z.json" ))); @@ -554,7 +559,7 @@ public void TestDefaultUrlWhenTokenNotProvided() // it's to wait if SendAsync is not triggered. t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "https://cdn.optimizely.com/datafiles/QBw9gFM8oTn7ogY9ANCC1z.json" ))); @@ -577,7 +582,7 @@ public void TestAuthenticationHeaderWhenTokenProvided() t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.Headers.Authorization.ToString() == "Bearer datafile1" ))); httpManager.Dispose(); @@ -597,7 +602,7 @@ public void TestFormatUrlHigherPriorityThanDefaultUrl() // it's to wait if SendAsync is not triggered. t.Wait(2000); HttpClientMock.Verify(_ => _.SendAsync( - It.Is(requestMessage => + It.Is(requestMessage => requestMessage.RequestUri.ToString() == "http://customformat/QBw9gFM8oTn7ogY9ANCC1z.json" ))); diff --git a/OptimizelySDK.Tests/Properties/AssemblyInfo.cs b/OptimizelySDK.Tests/Properties/AssemblyInfo.cs index f91d74aa..801bba21 100644 --- a/OptimizelySDK.Tests/Properties/AssemblyInfo.cs +++ b/OptimizelySDK.Tests/Properties/AssemblyInfo.cs @@ -29,6 +29,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("3.11.3.0")] -[assembly: AssemblyFileVersion("3.11.3.0")] -[assembly: AssemblyInformationalVersion("3.11.3")] // Used by Nuget. +[assembly: AssemblyVersion("3.11.4.0")] +[assembly: AssemblyFileVersion("3.11.4.0")] +[assembly: AssemblyInformationalVersion("3.11.4")] // Used by Nuget. diff --git a/OptimizelySDK/Config/HttpProjectConfigManager.cs b/OptimizelySDK/Config/HttpProjectConfigManager.cs index f27559f5..ed032b8e 100644 --- a/OptimizelySDK/Config/HttpProjectConfigManager.cs +++ b/OptimizelySDK/Config/HttpProjectConfigManager.cs @@ -18,6 +18,7 @@ using OptimizelySDK.Logger; using OptimizelySDK.Notifications; using System; +using System.Linq; using System.Net; using System.Threading.Tasks; @@ -108,7 +109,8 @@ private string GetRemoteDatafileResponse() if (!string.IsNullOrEmpty(LastModifiedSince)) { request.Headers.Add("If-Modified-Since", LastModifiedSince); - Logger.Log(LogLevel.DEBUG, $"Set If-Modified-Since in request header."); + Logger.Log(LogLevel.DEBUG, + $"Set If-Modified-Since in request header: {LastModifiedSince}"); } if (!string.IsNullOrEmpty(DatafileAccessToken)) @@ -138,8 +140,9 @@ private string GetRemoteDatafileResponse() // Update Last-Modified header if provided. if (result.Content.Headers.LastModified.HasValue) { - LastModifiedSince = result.Content.Headers.LastModified.ToString(); - Logger.Log(LogLevel.DEBUG, $"Set LastModifiedSince from response header."); + LastModifiedSince = result.Content.Headers.LastModified?.UtcDateTime.ToString("r"); + Logger.Log(LogLevel.DEBUG, + $"Set LastModifiedSince from response header: {LastModifiedSince}"); } var content = result.Content.ReadAsStringAsync(); diff --git a/OptimizelySDK/Properties/AssemblyInfo.cs b/OptimizelySDK/Properties/AssemblyInfo.cs index d32e281e..45d1dfa7 100644 --- a/OptimizelySDK/Properties/AssemblyInfo.cs +++ b/OptimizelySDK/Properties/AssemblyInfo.cs @@ -40,9 +40,6 @@ // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.11.2.1")] -[assembly: AssemblyFileVersion("3.11.2.1")] -[assembly: AssemblyInformationalVersion("3.11.1")] // Used by Nuget. +[assembly: AssemblyVersion("3.11.4.0")] +[assembly: AssemblyFileVersion("3.11.4.0")] +[assembly: AssemblyInformationalVersion("3.11.4")] // Used by Nuget.