From 19618d6a8ea32a72d37ab470c48ac3b751ef0008 Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Wed, 2 Oct 2024 11:42:43 +0300 Subject: [PATCH 1/8] TBA integration test --- .../NRedisStack.Tests.csproj | 1 + .../AuthenticationTests.cs | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs diff --git a/tests/NRedisStack.Tests/NRedisStack.Tests.csproj b/tests/NRedisStack.Tests/NRedisStack.Tests.csproj index 7aceede5..cb522fcc 100644 --- a/tests/NRedisStack.Tests/NRedisStack.Tests.csproj +++ b/tests/NRedisStack.Tests/NRedisStack.Tests.csproj @@ -31,6 +31,7 @@ <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.assert" Version="2.4.1" /> <PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0" /> + <PackageReference Include="Microsoft.Azure.StackExchangeRedis" Version="3.1.0" /> </ItemGroup> <ItemGroup> diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs new file mode 100644 index 00000000..17f4cbdf --- /dev/null +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs @@ -0,0 +1,59 @@ +using Xunit; +using StackExchange.Redis; +using Azure.Identity; +using NRedisStack.RedisStackCommands; +using NRedisStack.Search; + +namespace NRedisStack.Tests.TokenBasedAuthentication +{ + public class AuthenticationTests + { + static readonly string key = "myKey"; + static readonly string value = "myValue"; + static readonly string index = "myIndex"; + static readonly string field = "myField"; + static readonly string alias = "myAlias"; + + string env0Host = "localhost:6379"; + + [Fact] + public void TestTokenBasedAuthentication() + { + + // NOTE: ConnectionMultiplexer instances should be as long-lived as possible. Ideally a single ConnectionMultiplexer per cache is reused over the lifetime of the client application process. + ConnectionMultiplexer? connectionMultiplexer = null; + StringWriter connectionLog = new(); + + var configurationOptions = ConfigurationOptions.Parse($"{env0Host}").ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()).Result!; + configurationOptions.Ssl = false; + configurationOptions.AbortOnConnectFail = true; // Fail fast for the purposes of this sample. In production code, this should remain false to retry connections on startup + + connectionMultiplexer = ConnectionMultiplexer.ConnectAsync(configurationOptions, connectionLog).Result; + + IDatabase db = connectionMultiplexer.GetDatabase(); + + db.KeyDelete(key); + try + { + db.FT().DropIndex(index); + } + catch { } + + db.StringSet(key, value); + string result = db.StringGet(key); + Assert.Equal(value, result); + + var ft = db.FT(); + Schema sc = new Schema().AddTextField(field); + Assert.True(ft.Create(index, FTCreateParams.CreateParams(), sc)); + + db.HashSet(index, new HashEntry[] { new HashEntry(field, value) }); + + Assert.True(ft.AliasAdd(alias, index)); + SearchResult res1 = ft.Search(alias, new Query("*").ReturnFields(field)); + Assert.Equal(1, res1.TotalResults); + Assert.Equal(value, res1.Documents[0][field]); + + } + } +} \ No newline at end of file From 2366f1cfe6364194818f10e225918794f86faf56 Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Mon, 7 Oct 2024 09:49:20 +0300 Subject: [PATCH 2/8] add TargetEnvironmentAttribute and FaultInjectorClient --- .../TargetEnvironmentAttribute.cs | 35 ++++++ .../AuthenticationTests.cs | 16 ++- .../FaultInjectorClient.cs | 112 ++++++++++++++++++ 3 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs create mode 100644 tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs diff --git a/tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs b/tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs new file mode 100644 index 00000000..607d7412 --- /dev/null +++ b/tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs @@ -0,0 +1,35 @@ +using Xunit; + +namespace NRedisStack.Tests; +public class TargetEnvironmentAttribute : SkipIfRedisAttribute +{ + private string targetEnv; + public TargetEnvironmentAttribute(string targetEnv) : base(Comparison.LessThan, "0.0.0") + { + this.targetEnv = targetEnv; + } + + public TargetEnvironmentAttribute(string targetEnv, Is environment, Comparison comparison = Comparison.LessThan, + string targetVersion = "0.0.0") : base(environment, comparison, targetVersion) + { + this.targetEnv = targetEnv; + } + + public TargetEnvironmentAttribute(string targetEnv, Is environment1, Is environment2, Comparison comparison = Comparison.LessThan, + string targetVersion = "0.0.0") : base(environment1, environment2, comparison, targetVersion) + { + this.targetEnv = targetEnv; + } + + public override string? Skip + { + get + { + if (!new RedisFixture().IsTargetConnectionExist(targetEnv)) + { + return "Test skipped, because: target environment not found."; + } + return base.Skip; + } + } +} \ No newline at end of file diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs index 17f4cbdf..b46235a0 100644 --- a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs @@ -6,29 +6,28 @@ namespace NRedisStack.Tests.TokenBasedAuthentication { - public class AuthenticationTests + public class AuthenticationTests : AbstractNRedisStackTest { static readonly string key = "myKey"; static readonly string value = "myValue"; static readonly string index = "myIndex"; static readonly string field = "myField"; static readonly string alias = "myAlias"; + public AuthenticationTests(RedisFixture redisFixture) : base(redisFixture) { } - string env0Host = "localhost:6379"; - - [Fact] + [TargetEnvironment("standalone-entraid-acl")] public void TestTokenBasedAuthentication() { - + Assert.True(new FaultInjectorClient().TriggerActionAsync("enable_entraid", new Dictionary<string, object>()).Wait(5000),"Entraid could not be enabled this time!!!"); // NOTE: ConnectionMultiplexer instances should be as long-lived as possible. Ideally a single ConnectionMultiplexer per cache is reused over the lifetime of the client application process. ConnectionMultiplexer? connectionMultiplexer = null; - StringWriter connectionLog = new(); + // StringWriter connectionLog = new(); - var configurationOptions = ConfigurationOptions.Parse($"{env0Host}").ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()).Result!; + var configurationOptions = new ConfigurationOptions().ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()).Result!; configurationOptions.Ssl = false; configurationOptions.AbortOnConnectFail = true; // Fail fast for the purposes of this sample. In production code, this should remain false to retry connections on startup - connectionMultiplexer = ConnectionMultiplexer.ConnectAsync(configurationOptions, connectionLog).Result; + connectionMultiplexer = redisFixture.GetConnectionById(configurationOptions, "standalone-entraid-acl"); IDatabase db = connectionMultiplexer.GetDatabase(); @@ -53,7 +52,6 @@ public void TestTokenBasedAuthentication() SearchResult res1 = ft.Search(alias, new Query("*").ReturnFields(field)); Assert.Equal(1, res1.TotalResults); Assert.Equal(value, res1.Documents[0][field]); - } } } \ No newline at end of file diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs new file mode 100644 index 00000000..6c52a201 --- /dev/null +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs @@ -0,0 +1,112 @@ +using System.Text; +using System.Text.Json; + +public class FaultInjectorClient +{ + private static readonly string BASE_URL; + + static FaultInjectorClient() + { + BASE_URL = Environment.GetEnvironmentVariable("FAULT_INJECTION_API_URL") ?? "http://127.0.0.1:20324"; + } + + public class TriggerActionResponse + { + public string ActionId { get; } + private DateTime? LastRequestTime { get; set; } + private DateTime? CompletedAt { get; set; } + private DateTime? FirstRequestAt { get; set; } + + public TriggerActionResponse(string actionId) + { + ActionId = actionId; + } + + public async Task<bool> IsCompletedAsync(TimeSpan checkInterval, TimeSpan delayAfter, TimeSpan timeout) + { + if (CompletedAt.HasValue) + { + return DateTime.UtcNow - CompletedAt.Value >= delayAfter; + } + + if (FirstRequestAt.HasValue && DateTime.UtcNow - FirstRequestAt.Value >= timeout) + { + throw new TimeoutException("Timeout"); + } + + if (!LastRequestTime.HasValue || DateTime.UtcNow - LastRequestTime.Value >= checkInterval) + { + LastRequestTime = DateTime.UtcNow; + + if (!FirstRequestAt.HasValue) + { + FirstRequestAt = LastRequestTime; + } + + using var httpClient = GetHttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, $"{BASE_URL}/action/{ActionId}"); + + try + { + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadAsStringAsync(); + + + if (result.Contains("success")) + { + CompletedAt = DateTime.UtcNow; + return DateTime.UtcNow - CompletedAt.Value >= delayAfter; + } + } + catch (HttpRequestException e) + { + throw new Exception("Fault injection proxy error", e); + } + } + return false; + } + } + + private static HttpClient GetHttpClient() + { + var httpClient = new HttpClient + { + Timeout = TimeSpan.FromMilliseconds(5000) + }; + return httpClient; + } + + public async Task<TriggerActionResponse> TriggerActionAsync(string actionType, Dictionary<string, object> parameters) + { + var payload = new Dictionary<string, object> + { + { "type", actionType }, + { "parameters", parameters } + }; + + var jsonString = JsonSerializer.Serialize(payload, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + using var httpClient = GetHttpClient(); + var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/action") + { + Content = new StringContent(jsonString, Encoding.UTF8, "application/json") + }; + + try + { + var response = await httpClient.SendAsync(request); + var result = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize<TriggerActionResponse>(result, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + } + catch (HttpRequestException e) + { + throw; + } + } +} From c5dad60a8d48df368ba9b57ed0fe2f43dfa1d9dd Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Mon, 7 Oct 2024 10:15:14 +0300 Subject: [PATCH 3/8] fix formatting --- .../TokenBasedAuthentication/AuthenticationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs index b46235a0..bc515ad4 100644 --- a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs @@ -18,7 +18,7 @@ public AuthenticationTests(RedisFixture redisFixture) : base(redisFixture) { } [TargetEnvironment("standalone-entraid-acl")] public void TestTokenBasedAuthentication() { - Assert.True(new FaultInjectorClient().TriggerActionAsync("enable_entraid", new Dictionary<string, object>()).Wait(5000),"Entraid could not be enabled this time!!!"); + Assert.True(new FaultInjectorClient().TriggerActionAsync("enable_entraid", new Dictionary<string, object>()).Wait(5000), "Entraid could not be enabled this time!!!"); // NOTE: ConnectionMultiplexer instances should be as long-lived as possible. Ideally a single ConnectionMultiplexer per cache is reused over the lifetime of the client application process. ConnectionMultiplexer? connectionMultiplexer = null; // StringWriter connectionLog = new(); From 53db0735e0cd7202c2d5a8912938d66600c88619 Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Mon, 7 Oct 2024 13:52:37 +0300 Subject: [PATCH 4/8] add namespace for httpclient --- .../TokenBasedAuthentication/FaultInjectorClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs index 6c52a201..cf319cf1 100644 --- a/tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/FaultInjectorClient.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json; +using System.Net.Http; public class FaultInjectorClient { From 4022d4020f94dbdf86a99e1b97913fa27f5954a7 Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Mon, 7 Oct 2024 17:21:07 +0300 Subject: [PATCH 5/8] add dummy test for client testing --- .../TokenBasedAuthentication/AuthenticationTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs index bc515ad4..890f47e2 100644 --- a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs @@ -16,6 +16,7 @@ public class AuthenticationTests : AbstractNRedisStackTest public AuthenticationTests(RedisFixture redisFixture) : base(redisFixture) { } [TargetEnvironment("standalone-entraid-acl")] + public void TestTokenBasedAuthentication() { Assert.True(new FaultInjectorClient().TriggerActionAsync("enable_entraid", new Dictionary<string, object>()).Wait(5000), "Entraid could not be enabled this time!!!"); @@ -53,5 +54,11 @@ public void TestTokenBasedAuthentication() Assert.Equal(1, res1.TotalResults); Assert.Equal(value, res1.Documents[0][field]); } + + [Fact] + public void DummyTest() + { + Assert.True(true); + } } } \ No newline at end of file From 325ad130e75a5cc1e4e50ca6ee1af20f0965923e Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Mon, 7 Oct 2024 21:44:34 +0300 Subject: [PATCH 6/8] add AttributeUsage to TargetEnvironment --- tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs | 1 + .../TokenBasedAuthentication/AuthenticationTests.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs b/tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs index 607d7412..4497aef0 100644 --- a/tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs +++ b/tests/NRedisStack.Tests/TargetEnvironmentAttribute.cs @@ -1,6 +1,7 @@ using Xunit; namespace NRedisStack.Tests; +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class TargetEnvironmentAttribute : SkipIfRedisAttribute { private string targetEnv; diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs index 890f47e2..5bb15721 100644 --- a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs @@ -16,7 +16,6 @@ public class AuthenticationTests : AbstractNRedisStackTest public AuthenticationTests(RedisFixture redisFixture) : base(redisFixture) { } [TargetEnvironment("standalone-entraid-acl")] - public void TestTokenBasedAuthentication() { Assert.True(new FaultInjectorClient().TriggerActionAsync("enable_entraid", new Dictionary<string, object>()).Wait(5000), "Entraid could not be enabled this time!!!"); From 11c8784ed374b0fe4fda7e5e506f14ca1004f383 Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Thu, 10 Oct 2024 15:10:12 +0300 Subject: [PATCH 7/8] some clean up and test execution optimization --- tests/NRedisStack.Tests/RedisFixture.cs | 16 ++++++++++++---- tests/NRedisStack.Tests/SkipIfRedisAttribute.cs | 4 +++- .../AuthenticationTests.cs | 12 +----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/NRedisStack.Tests/RedisFixture.cs b/tests/NRedisStack.Tests/RedisFixture.cs index 0a145e85..9dfdf2eb 100644 --- a/tests/NRedisStack.Tests/RedisFixture.cs +++ b/tests/NRedisStack.Tests/RedisFixture.cs @@ -52,9 +52,12 @@ public class RedisFixture : IDisposable public bool isEnterprise = Environment.GetEnvironmentVariable("IS_ENTERPRISE") == "true"; public bool isOSSCluster; + private ConnectionMultiplexer redis; + private ConfigurationOptions defaultConfig; + public RedisFixture() { - ConfigurationOptions clusterConfig = new ConfigurationOptions + defaultConfig = new ConfigurationOptions { AsyncTimeout = 10000, SyncTimeout = 10000 @@ -93,8 +96,6 @@ public RedisFixture() isOSSCluster = true; } } - - Redis = GetConnectionById(clusterConfig, defaultEndpointId); } public void Dispose() @@ -102,7 +103,14 @@ public void Dispose() Redis.Close(); } - public ConnectionMultiplexer Redis { get; } + public ConnectionMultiplexer Redis + { + get + { + redis = redis ?? GetConnectionById(defaultConfig, defaultEndpointId); + return redis; + } + } public ConnectionMultiplexer GetConnectionById(ConfigurationOptions configurationOptions, string id) { diff --git a/tests/NRedisStack.Tests/SkipIfRedisAttribute.cs b/tests/NRedisStack.Tests/SkipIfRedisAttribute.cs index b62dc17a..eae76c4d 100644 --- a/tests/NRedisStack.Tests/SkipIfRedisAttribute.cs +++ b/tests/NRedisStack.Tests/SkipIfRedisAttribute.cs @@ -21,6 +21,8 @@ public class SkipIfRedisAttribute : FactAttribute private readonly Comparison _comparison; private readonly List<Is> _environments = new List<Is>(); + private static Version serverVersion = null; + public SkipIfRedisAttribute( Is environment, Comparison comparison = Comparison.LessThan, @@ -95,7 +97,7 @@ public override string? Skip } // Version check (if Is.Standalone/Is.OSSCluster is set then ) - var serverVersion = redisFixture.Redis.GetServer(redisFixture.Redis.GetEndPoints()[0]).Version; + serverVersion = serverVersion ?? redisFixture.Redis.GetServer(redisFixture.Redis.GetEndPoints()[0]).Version; var targetVersion = new Version(_targetVersion); int comparisonResult = serverVersion.CompareTo(targetVersion); diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs index 5bb15721..32f8c379 100644 --- a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs @@ -18,16 +18,12 @@ public AuthenticationTests(RedisFixture redisFixture) : base(redisFixture) { } [TargetEnvironment("standalone-entraid-acl")] public void TestTokenBasedAuthentication() { - Assert.True(new FaultInjectorClient().TriggerActionAsync("enable_entraid", new Dictionary<string, object>()).Wait(5000), "Entraid could not be enabled this time!!!"); - // NOTE: ConnectionMultiplexer instances should be as long-lived as possible. Ideally a single ConnectionMultiplexer per cache is reused over the lifetime of the client application process. - ConnectionMultiplexer? connectionMultiplexer = null; - // StringWriter connectionLog = new(); var configurationOptions = new ConfigurationOptions().ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()).Result!; configurationOptions.Ssl = false; configurationOptions.AbortOnConnectFail = true; // Fail fast for the purposes of this sample. In production code, this should remain false to retry connections on startup - connectionMultiplexer = redisFixture.GetConnectionById(configurationOptions, "standalone-entraid-acl"); + ConnectionMultiplexer?connectionMultiplexer = redisFixture.GetConnectionById(configurationOptions, "standalone-entraid-acl"); IDatabase db = connectionMultiplexer.GetDatabase(); @@ -53,11 +49,5 @@ public void TestTokenBasedAuthentication() Assert.Equal(1, res1.TotalResults); Assert.Equal(value, res1.Documents[0][field]); } - - [Fact] - public void DummyTest() - { - Assert.True(true); - } } } \ No newline at end of file From 2303ec796763b4d994e3a49a17d62c7135c2d3d0 Mon Sep 17 00:00:00 2001 From: atakavci <a_takavci@yahoo.com> Date: Thu, 10 Oct 2024 17:24:02 +0300 Subject: [PATCH 8/8] fix formatting --- .../TokenBasedAuthentication/AuthenticationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs index 32f8c379..db8842c2 100644 --- a/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs +++ b/tests/NRedisStack.Tests/TokenBasedAuthentication/AuthenticationTests.cs @@ -23,7 +23,7 @@ public void TestTokenBasedAuthentication() configurationOptions.Ssl = false; configurationOptions.AbortOnConnectFail = true; // Fail fast for the purposes of this sample. In production code, this should remain false to retry connections on startup - ConnectionMultiplexer?connectionMultiplexer = redisFixture.GetConnectionById(configurationOptions, "standalone-entraid-acl"); + ConnectionMultiplexer? connectionMultiplexer = redisFixture.GetConnectionById(configurationOptions, "standalone-entraid-acl"); IDatabase db = connectionMultiplexer.GetDatabase();