Skip to content

Commit 5a91539

Browse files
committed
Azure EntraID (Token Based Authentication) integration tests (#344)
* TBA integration test * add TargetEnvironmentAttribute and FaultInjectorClient * fix formatting * add namespace for httpclient * add dummy test for client testing * add AttributeUsage to TargetEnvironment * some clean up and test execution optimization * fix formatting
1 parent 15fd7d2 commit 5a91539

File tree

6 files changed

+218
-5
lines changed

6 files changed

+218
-5
lines changed

tests/NRedisStack.Tests/NRedisStack.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<PackageReference Include="xunit" Version="2.4.1" />
3232
<PackageReference Include="xunit.assert" Version="2.4.1" />
3333
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0" />
34+
<PackageReference Include="Microsoft.Azure.StackExchangeRedis" Version="3.1.0" />
3435
</ItemGroup>
3536

3637
<ItemGroup>

tests/NRedisStack.Tests/RedisFixture.cs

+12-4
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,12 @@ public class RedisFixture : IDisposable
5252
public bool isEnterprise = Environment.GetEnvironmentVariable("IS_ENTERPRISE") == "true";
5353
public bool isOSSCluster;
5454

55+
private ConnectionMultiplexer redis;
56+
private ConfigurationOptions defaultConfig;
57+
5558
public RedisFixture()
5659
{
57-
ConfigurationOptions clusterConfig = new ConfigurationOptions
60+
defaultConfig = new ConfigurationOptions
5861
{
5962
AsyncTimeout = 10000,
6063
SyncTimeout = 10000
@@ -93,16 +96,21 @@ public RedisFixture()
9396
isOSSCluster = true;
9497
}
9598
}
96-
97-
Redis = GetConnectionById(clusterConfig, defaultEndpointId);
9899
}
99100

100101
public void Dispose()
101102
{
102103
Redis.Close();
103104
}
104105

105-
public ConnectionMultiplexer Redis { get; }
106+
public ConnectionMultiplexer Redis
107+
{
108+
get
109+
{
110+
redis = redis ?? GetConnectionById(defaultConfig, defaultEndpointId);
111+
return redis;
112+
}
113+
}
106114

107115
public ConnectionMultiplexer GetConnectionById(ConfigurationOptions configurationOptions, string id)
108116
{

tests/NRedisStack.Tests/SkipIfRedisAttribute.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public class SkipIfRedisAttribute : FactAttribute
2121
private readonly Comparison _comparison;
2222
private readonly List<Is> _environments = new List<Is>();
2323

24+
private static Version serverVersion = null;
25+
2426
public SkipIfRedisAttribute(
2527
Is environment,
2628
Comparison comparison = Comparison.LessThan,
@@ -95,7 +97,7 @@ public override string? Skip
9597
}
9698
// Version check (if Is.Standalone/Is.OSSCluster is set then )
9799

98-
var serverVersion = redisFixture.Redis.GetServer(redisFixture.Redis.GetEndPoints()[0]).Version;
100+
serverVersion = serverVersion ?? redisFixture.Redis.GetServer(redisFixture.Redis.GetEndPoints()[0]).Version;
99101
var targetVersion = new Version(_targetVersion);
100102
int comparisonResult = serverVersion.CompareTo(targetVersion);
101103

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Xunit;
2+
3+
namespace NRedisStack.Tests;
4+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
5+
public class TargetEnvironmentAttribute : SkipIfRedisAttribute
6+
{
7+
private string targetEnv;
8+
public TargetEnvironmentAttribute(string targetEnv) : base(Comparison.LessThan, "0.0.0")
9+
{
10+
this.targetEnv = targetEnv;
11+
}
12+
13+
public TargetEnvironmentAttribute(string targetEnv, Is environment, Comparison comparison = Comparison.LessThan,
14+
string targetVersion = "0.0.0") : base(environment, comparison, targetVersion)
15+
{
16+
this.targetEnv = targetEnv;
17+
}
18+
19+
public TargetEnvironmentAttribute(string targetEnv, Is environment1, Is environment2, Comparison comparison = Comparison.LessThan,
20+
string targetVersion = "0.0.0") : base(environment1, environment2, comparison, targetVersion)
21+
{
22+
this.targetEnv = targetEnv;
23+
}
24+
25+
public override string? Skip
26+
{
27+
get
28+
{
29+
if (!new RedisFixture().IsTargetConnectionExist(targetEnv))
30+
{
31+
return "Test skipped, because: target environment not found.";
32+
}
33+
return base.Skip;
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Xunit;
2+
using StackExchange.Redis;
3+
using Azure.Identity;
4+
using NRedisStack.RedisStackCommands;
5+
using NRedisStack.Search;
6+
7+
namespace NRedisStack.Tests.TokenBasedAuthentication
8+
{
9+
public class AuthenticationTests : AbstractNRedisStackTest
10+
{
11+
static readonly string key = "myKey";
12+
static readonly string value = "myValue";
13+
static readonly string index = "myIndex";
14+
static readonly string field = "myField";
15+
static readonly string alias = "myAlias";
16+
public AuthenticationTests(RedisFixture redisFixture) : base(redisFixture) { }
17+
18+
[TargetEnvironment("standalone-entraid-acl")]
19+
public void TestTokenBasedAuthentication()
20+
{
21+
22+
var configurationOptions = new ConfigurationOptions().ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()).Result!;
23+
configurationOptions.Ssl = false;
24+
configurationOptions.AbortOnConnectFail = true; // Fail fast for the purposes of this sample. In production code, this should remain false to retry connections on startup
25+
26+
ConnectionMultiplexer? connectionMultiplexer = redisFixture.GetConnectionById(configurationOptions, "standalone-entraid-acl");
27+
28+
IDatabase db = connectionMultiplexer.GetDatabase();
29+
30+
db.KeyDelete(key);
31+
try
32+
{
33+
db.FT().DropIndex(index);
34+
}
35+
catch { }
36+
37+
db.StringSet(key, value);
38+
string result = db.StringGet(key);
39+
Assert.Equal(value, result);
40+
41+
var ft = db.FT();
42+
Schema sc = new Schema().AddTextField(field);
43+
Assert.True(ft.Create(index, FTCreateParams.CreateParams(), sc));
44+
45+
db.HashSet(index, new HashEntry[] { new HashEntry(field, value) });
46+
47+
Assert.True(ft.AliasAdd(alias, index));
48+
SearchResult res1 = ft.Search(alias, new Query("*").ReturnFields(field));
49+
Assert.Equal(1, res1.TotalResults);
50+
Assert.Equal(value, res1.Documents[0][field]);
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System.Text;
2+
using System.Text.Json;
3+
using System.Net.Http;
4+
5+
public class FaultInjectorClient
6+
{
7+
private static readonly string BASE_URL;
8+
9+
static FaultInjectorClient()
10+
{
11+
BASE_URL = Environment.GetEnvironmentVariable("FAULT_INJECTION_API_URL") ?? "http://127.0.0.1:20324";
12+
}
13+
14+
public class TriggerActionResponse
15+
{
16+
public string ActionId { get; }
17+
private DateTime? LastRequestTime { get; set; }
18+
private DateTime? CompletedAt { get; set; }
19+
private DateTime? FirstRequestAt { get; set; }
20+
21+
public TriggerActionResponse(string actionId)
22+
{
23+
ActionId = actionId;
24+
}
25+
26+
public async Task<bool> IsCompletedAsync(TimeSpan checkInterval, TimeSpan delayAfter, TimeSpan timeout)
27+
{
28+
if (CompletedAt.HasValue)
29+
{
30+
return DateTime.UtcNow - CompletedAt.Value >= delayAfter;
31+
}
32+
33+
if (FirstRequestAt.HasValue && DateTime.UtcNow - FirstRequestAt.Value >= timeout)
34+
{
35+
throw new TimeoutException("Timeout");
36+
}
37+
38+
if (!LastRequestTime.HasValue || DateTime.UtcNow - LastRequestTime.Value >= checkInterval)
39+
{
40+
LastRequestTime = DateTime.UtcNow;
41+
42+
if (!FirstRequestAt.HasValue)
43+
{
44+
FirstRequestAt = LastRequestTime;
45+
}
46+
47+
using var httpClient = GetHttpClient();
48+
var request = new HttpRequestMessage(HttpMethod.Get, $"{BASE_URL}/action/{ActionId}");
49+
50+
try
51+
{
52+
var response = await httpClient.SendAsync(request);
53+
var result = await response.Content.ReadAsStringAsync();
54+
55+
56+
if (result.Contains("success"))
57+
{
58+
CompletedAt = DateTime.UtcNow;
59+
return DateTime.UtcNow - CompletedAt.Value >= delayAfter;
60+
}
61+
}
62+
catch (HttpRequestException e)
63+
{
64+
throw new Exception("Fault injection proxy error", e);
65+
}
66+
}
67+
return false;
68+
}
69+
}
70+
71+
private static HttpClient GetHttpClient()
72+
{
73+
var httpClient = new HttpClient
74+
{
75+
Timeout = TimeSpan.FromMilliseconds(5000)
76+
};
77+
return httpClient;
78+
}
79+
80+
public async Task<TriggerActionResponse> TriggerActionAsync(string actionType, Dictionary<string, object> parameters)
81+
{
82+
var payload = new Dictionary<string, object>
83+
{
84+
{ "type", actionType },
85+
{ "parameters", parameters }
86+
};
87+
88+
var jsonString = JsonSerializer.Serialize(payload, new JsonSerializerOptions
89+
{
90+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
91+
});
92+
93+
using var httpClient = GetHttpClient();
94+
var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/action")
95+
{
96+
Content = new StringContent(jsonString, Encoding.UTF8, "application/json")
97+
};
98+
99+
try
100+
{
101+
var response = await httpClient.SendAsync(request);
102+
var result = await response.Content.ReadAsStringAsync();
103+
return JsonSerializer.Deserialize<TriggerActionResponse>(result, new JsonSerializerOptions
104+
{
105+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
106+
});
107+
}
108+
catch (HttpRequestException e)
109+
{
110+
throw;
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)