Files
tiku-backend.net/Tiku.IntegrationTests/RedisSecurityStoreTests.cs

82 lines
3.4 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
using System.Security.Cryptography;
using System.Text;
using Tiku.Application.Security;
using Tiku.Infrastructure;
using Tiku.Infrastructure.Security;
namespace Tiku.IntegrationTests;
public sealed class RedisSecurityStoreTests
{
[Fact]
public async Task Unavailable_redis_fails_closed_for_security_operations()
{
await using var provider = BuildProvider(
"localhost:6399,connectTimeout=200,syncTimeout=200,asyncTimeout=200,abortConnect=false",
$"integration-unavailable-{Guid.NewGuid():N}");
var store = provider.GetRequiredService<IRedisSecurityStore>();
await Assert.ThrowsAsync<RedisSecurityUnavailableException>(() => store.ConsumeAsync(
[
new DistributedRateLimitBucket("password:ip:test", 1, TimeSpan.FromSeconds(1))
]));
Assert.False(await store.PingAsync());
}
[Fact]
public async Task Two_instances_share_atomic_limit_and_keys_contain_no_plaintext_identifier()
{
var connectionString = Environment.GetEnvironmentVariable("TIKU_TEST_REDIS");
if (string.IsNullOrWhiteSpace(connectionString))
{
return;
}
var environment = $"integration-{Guid.NewGuid():N}";
await using var first = BuildProvider(connectionString, environment);
await using var second = BuildProvider(connectionString, environment);
var store1 = first.GetRequiredService<IRedisSecurityStore>();
var store2 = second.GetRequiredService<IRedisSecurityStore>();
const string phone = "13812345678";
var phoneHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(phone))).ToLowerInvariant();
var bucket = new DistributedRateLimitBucket(
$"sms-verify:tenant-id:login:{phoneHash}", 1, TimeSpan.FromMinutes(1));
try
{
var attempts = await Task.WhenAll(Enumerable.Range(0, 12).Select(index =>
(index & 1) == 0
? store1.ConsumeAsync([bucket])
: store2.ConsumeAsync([bucket])));
Assert.Single(attempts, result => result.Allowed);
Assert.All(attempts.Where(result => !result.Allowed), result => Assert.NotNull(result.RetryAfter));
var multiplexer = first.GetRequiredService<IConnectionMultiplexer>();
var server = multiplexer.GetServer(multiplexer.GetEndPoints().Single());
var keys = server.Keys(pattern: $"tiku:{environment}:*").Select(key => key.ToString()).ToArray();
Assert.NotEmpty(keys);
Assert.DoesNotContain(keys, key => key.Contains(phone, StringComparison.Ordinal));
}
finally
{
var multiplexer = first.GetRequiredService<IConnectionMultiplexer>();
var server = multiplexer.GetServer(multiplexer.GetEndPoints().Single());
var keys = server.Keys(pattern: $"tiku:{environment}:*").ToArray();
if (keys.Length > 0)
{
await multiplexer.GetDatabase().KeyDeleteAsync(keys);
}
}
}
private static ServiceProvider BuildProvider(string connectionString, string environment)
{
var services = new ServiceCollection();
services.AddLogging();
services.AddRedisSecurity(connectionString, environment);
return services.BuildServiceProvider();
}
}