Перевести FakeOperatorAuthStore на NSubstitute
Хелпер Support/TestOperatorAuthStore: операторы/сессии/журнал Calls, поиск и мутации через Returns/When-Do. Потребители (14 файлов) перетипизированы на .Store, фейк удалён, тесты 1340 зелёные.
This commit is contained in:
@@ -128,13 +128,13 @@ public sealed class OperatorBootstrapHostedServiceTests
|
||||
IConfiguration configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(values)
|
||||
.Build();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
var passwordHasher = TestHashers.New();
|
||||
var logger = new ListLogger();
|
||||
// Hosted-шаг резолвит scoped OperatorBootstrapService из scope (как TenantBootstrapService):
|
||||
// провайдер собирается с реальной регистрацией модуля поверх фейков.
|
||||
var services = new ServiceCollection();
|
||||
services.AddScoped<IOperatorAuthStore>(_ => store);
|
||||
services.AddScoped<IOperatorAuthStore>(_ => store.Store);
|
||||
services.AddScoped<IPasswordHasher>(_ => passwordHasher);
|
||||
services.AddScoped<OperatorBootstrapService>();
|
||||
// Провайдер живёт всё время теста (без using): hosted резолвит scope в StartAsync после создания.
|
||||
@@ -150,7 +150,7 @@ public sealed class OperatorBootstrapHostedServiceTests
|
||||
// Контекст теста bootstrap-шага.
|
||||
private sealed record Context(
|
||||
OperatorBootstrapHostedService Hosted,
|
||||
FakeOperatorAuthStore Store,
|
||||
TestOperatorAuthStore Store,
|
||||
IPasswordHasher PasswordHasher,
|
||||
ListLogger Logs);
|
||||
|
||||
|
||||
@@ -14,15 +14,15 @@ public sealed class OperatorAuthServiceTests
|
||||
|
||||
private static readonly Guid OperatorId = Guid.NewGuid();
|
||||
|
||||
private readonly FakeOperatorAuthStore _store;
|
||||
private readonly TestOperatorAuthStore _store;
|
||||
private readonly OperatorAuthService _service;
|
||||
|
||||
public OperatorAuthServiceTests()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
_store = new FakeOperatorAuthStore();
|
||||
_store = new TestOperatorAuthStore();
|
||||
_store.AddOperator(new StoredOperatorDto(OperatorId, OperatorLogin, "active", passwordHasher.Hash(OperatorPassword)));
|
||||
_service = new OperatorAuthService(_store, passwordHasher);
|
||||
_service = new OperatorAuthService(_store.Store, passwordHasher);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -10,15 +10,15 @@ namespace Deal.Tests.Unit.Modules.Tenants;
|
||||
/// </summary>
|
||||
public sealed class OperatorBootstrapServiceTests
|
||||
{
|
||||
private readonly FakeOperatorAuthStore _store;
|
||||
private readonly TestOperatorAuthStore _store;
|
||||
private readonly IPasswordHasher _passwordHasher;
|
||||
private readonly OperatorBootstrapService _service;
|
||||
|
||||
public OperatorBootstrapServiceTests()
|
||||
{
|
||||
_passwordHasher = TestHashers.New();
|
||||
_store = new FakeOperatorAuthStore();
|
||||
_service = new OperatorBootstrapService(_store, _passwordHasher);
|
||||
_store = new TestOperatorAuthStore();
|
||||
_service = new OperatorBootstrapService(_store.Store, _passwordHasher);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class LoginAttemptEndpointHttpTests
|
||||
public async Task Login_AfterFiveWrongAttempts_Returns429WithBlockedDetail()
|
||||
{
|
||||
await OperatorAuthHttpHost.RunAsync(
|
||||
new FakeOperatorAuthStore(),
|
||||
new TestOperatorAuthStore(),
|
||||
NewUserStore(),
|
||||
async (baseAddress, _, users, _, _, _, _) =>
|
||||
{
|
||||
@@ -66,7 +66,7 @@ public sealed class LoginAttemptEndpointHttpTests
|
||||
public async Task Login_SuccessResetsCounter_FurtherFailuresNotBlocked()
|
||||
{
|
||||
await OperatorAuthHttpHost.RunAsync(
|
||||
new FakeOperatorAuthStore(),
|
||||
new TestOperatorAuthStore(),
|
||||
NewUserStore(),
|
||||
async (baseAddress, _, users, _, _, _, _) =>
|
||||
{
|
||||
@@ -108,7 +108,7 @@ public sealed class LoginAttemptEndpointHttpTests
|
||||
using var capture = new SecurityEventCapture();
|
||||
|
||||
await OperatorAuthHttpHost.RunAsync(
|
||||
new FakeOperatorAuthStore(),
|
||||
new TestOperatorAuthStore(),
|
||||
NewUserStore(),
|
||||
async (baseAddress, _, _, _, _, _, _) =>
|
||||
{
|
||||
|
||||
@@ -256,10 +256,10 @@ public sealed class OperatorAnalyticsEndpointsHttpTests
|
||||
new TenantRecordDto(ActiveTenant, "active-tenant", TenantStatuses.Active, DateTimeOffset.UtcNow.AddDays(-2)),
|
||||
new TenantRecordDto(SuspendedTenant, "suspended-tenant", TenantStatuses.Suspended, DateTimeOffset.UtcNow.AddDays(-1)));
|
||||
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(), OperatorLogin, "active", passwordHasher.Hash(OperatorPassword)));
|
||||
return store;
|
||||
|
||||
@@ -279,10 +279,10 @@ public sealed class OperatorAuditEndpointsHttpTests
|
||||
// ─── Хелперы ─────────────────────────────────────────────────────────
|
||||
|
||||
// Фейк-хранилище оператора с активным operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -131,7 +131,7 @@ public sealed class OperatorAuthEndpointsHttpTests
|
||||
[Fact]
|
||||
public async Task OperatorWithNonActiveStatus_LoginSucceeds_ButMeReturns401()
|
||||
{
|
||||
FakeOperatorAuthStore operatorStore = NewOperatorStore(active: false);
|
||||
TestOperatorAuthStore operatorStore = NewOperatorStore(active: false);
|
||||
|
||||
await OperatorAuthHttpHost.RunAsync(
|
||||
operatorStore,
|
||||
@@ -189,10 +189,10 @@ public sealed class OperatorAuthEndpointsHttpTests
|
||||
}
|
||||
|
||||
// Фейк-хранилище оператора с активным (или нет) оператором operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore(bool active = true)
|
||||
private static TestOperatorAuthStore NewOperatorStore(bool active = true)
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -40,9 +40,9 @@ internal static class OperatorAuthHttpHost
|
||||
/// <param name="scenario">Сценарий: получает base-адрес и хранилища для проверок/досидирования.</param>
|
||||
/// <param name="auditStore">Фейк-хранилище аудита (IAuditLogStore); null — создаётся пустое внутри.</param>
|
||||
public static async Task RunAsync(
|
||||
FakeOperatorAuthStore operatorStore,
|
||||
TestOperatorAuthStore operatorStore,
|
||||
FakeAuthStore userStore,
|
||||
Func<string, FakeOperatorAuthStore, FakeAuthStore, Task> scenario,
|
||||
Func<string, TestOperatorAuthStore, FakeAuthStore, Task> scenario,
|
||||
TestAuditLogStore? auditStore = null) =>
|
||||
await RunCoreAsync(
|
||||
operatorStore,
|
||||
@@ -62,9 +62,9 @@ internal static class OperatorAuthHttpHost
|
||||
/// <param name="auditStore">Фейк-хранилище аудита (IAuditLogStore); null — создаётся пустое внутри.</param>
|
||||
/// <param name="inviteStore">Фейк-хранилище приглашений (IInviteStore); null — создаётся пустое внутри.</param>
|
||||
public static async Task RunAsync(
|
||||
FakeOperatorAuthStore operatorStore,
|
||||
TestOperatorAuthStore operatorStore,
|
||||
FakeAuthStore userStore,
|
||||
Func<string, FakeOperatorAuthStore, FakeAuthStore, TestInviteStore, TestAuditLogStore, Task> scenario,
|
||||
Func<string, TestOperatorAuthStore, FakeAuthStore, TestInviteStore, TestAuditLogStore, Task> scenario,
|
||||
TestAuditLogStore? auditStore = null,
|
||||
TestInviteStore? inviteStore = null) =>
|
||||
await RunCoreAsync(
|
||||
@@ -86,9 +86,9 @@ internal static class OperatorAuthHttpHost
|
||||
/// <param name="inviteStore">Фейк-хранилище приглашений (IInviteStore); null — создаётся пустое внутри.</param>
|
||||
/// <param name="tenantStore">Фейк-хранилище реестра тенантов (ITenantRepository); null — создаётся пустое внутри.</param>
|
||||
public static async Task RunAsync(
|
||||
FakeOperatorAuthStore operatorStore,
|
||||
TestOperatorAuthStore operatorStore,
|
||||
FakeAuthStore userStore,
|
||||
Func<string, FakeOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, Task> scenario,
|
||||
Func<string, TestOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, Task> scenario,
|
||||
TestAuditLogStore? auditStore = null,
|
||||
TestInviteStore? inviteStore = null,
|
||||
TestTenantStore? tenantStore = null) =>
|
||||
@@ -113,9 +113,9 @@ internal static class OperatorAuthHttpHost
|
||||
/// <param name="limitStore">Фейк-хранилище лимитов (ITenantLimitStore); null — создаётся пустое внутри.</param>
|
||||
/// <param name="rateLimitOptions">Опции rate limiting: null — выключенные по умолчанию (LoginAttemptGuard no-op, существующие сценарии не режутся); Enabled-опции — сценарии защиты входа.</param>
|
||||
public static async Task RunAsync(
|
||||
FakeOperatorAuthStore operatorStore,
|
||||
TestOperatorAuthStore operatorStore,
|
||||
FakeAuthStore userStore,
|
||||
Func<string, FakeOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, Task> scenario,
|
||||
Func<string, TestOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, Task> scenario,
|
||||
TestAuditLogStore? auditStore = null,
|
||||
TestInviteStore? inviteStore = null,
|
||||
TestTenantStore? tenantStore = null,
|
||||
@@ -142,9 +142,9 @@ internal static class OperatorAuthHttpHost
|
||||
/// <param name="auditStore">Фейк-хранилище аудита (IAuditLogStore); null — создаётся пустое внутри.</param>
|
||||
/// <param name="globalSettingsStore">Фейк глобального KV-хранилища (IGlobalSettingsStore); null — создаётся пустое.</param>
|
||||
public static async Task RunWithGlobalSettingsAsync(
|
||||
FakeOperatorAuthStore operatorStore,
|
||||
TestOperatorAuthStore operatorStore,
|
||||
FakeAuthStore userStore,
|
||||
Func<string, FakeOperatorAuthStore, FakeAuthStore, TestGlobalSettingsStore, TestAuditLogStore, Task> scenario,
|
||||
Func<string, TestOperatorAuthStore, FakeAuthStore, TestGlobalSettingsStore, TestAuditLogStore, Task> scenario,
|
||||
TestAuditLogStore? auditStore = null,
|
||||
TestGlobalSettingsStore? globalSettingsStore = null) =>
|
||||
await RunCoreAsync(
|
||||
@@ -159,13 +159,13 @@ internal static class OperatorAuthHttpHost
|
||||
|
||||
// Поднимает in-process Kestrel-хост и прогоняет сценарий (см. публичные перегрузки).
|
||||
private static async Task RunCoreAsync(
|
||||
FakeOperatorAuthStore operatorStore,
|
||||
TestOperatorAuthStore operatorStore,
|
||||
FakeAuthStore userStore,
|
||||
TestAuditLogStore? auditStore,
|
||||
TestInviteStore? inviteStore,
|
||||
TestTenantStore? tenantStore,
|
||||
FakeTenantLimitStore? limitStore,
|
||||
Func<string, FakeOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, TestGlobalSettingsStore, Task> scenario,
|
||||
Func<string, TestOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, TestGlobalSettingsStore, Task> scenario,
|
||||
RateLimitOptions? rateLimitOptions = null,
|
||||
TestTokenUsageEventStore? tokenUsageStore = null,
|
||||
TestGlobalSettingsStore? globalSettingsStore = null)
|
||||
@@ -187,7 +187,7 @@ internal static class OperatorAuthHttpHost
|
||||
builder.Services.AddSingleton(TestHashers.New());
|
||||
builder.Services.AddSingleton<ITenantContext, TenantContext>();
|
||||
builder.Services.AddSingleton<IAuthStore>(userStore);
|
||||
builder.Services.AddSingleton<IOperatorAuthStore>(operatorStore);
|
||||
builder.Services.AddSingleton<IOperatorAuthStore>(operatorStore.Store);
|
||||
builder.Services.AddSingleton<IAuditLogStore>(effectiveAuditStore.Store);
|
||||
builder.Services.AddSingleton<IInviteStore>(effectiveInviteStore.Store);
|
||||
builder.Services.AddSingleton<ITenantRepository>(effectiveTenantStore.Repository);
|
||||
|
||||
@@ -92,14 +92,14 @@ public sealed class OperatorHealthEndpointsHttpTests
|
||||
}
|
||||
|
||||
// Прогоняет сценарий на хосте с активным оператором operator/operator (реестр/лимиты пустые).
|
||||
private static Task RunAsync(Func<string, FakeOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, Task> scenario) =>
|
||||
private static Task RunAsync(Func<string, TestOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, Task> scenario) =>
|
||||
OperatorAuthHttpHost.RunAsync(NewOperatorStore(), new FakeAuthStore(), scenario);
|
||||
|
||||
// Фейк-хранилище оператора с активным оператором operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -262,10 +262,10 @@ public sealed class OperatorInvitesEndpointsHttpTests
|
||||
}
|
||||
|
||||
// Фейк-хранилище оператора с активным оператором operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -284,7 +284,7 @@ public sealed class OperatorLimitsEndpointsHttpTests
|
||||
$"{baseAddress}/api/operator/tenants/{tenantId}/limit";
|
||||
|
||||
// Прогоняет сценарий на хосте с двумя активными тенантами и предзаполненными строками лимитов.
|
||||
private static Task RunAsync(Func<string, FakeOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, Task> scenario, TestAuditLogStore? auditStore = null)
|
||||
private static Task RunAsync(Func<string, TestOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, FakeTenantLimitStore, Task> scenario, TestAuditLogStore? auditStore = null)
|
||||
{
|
||||
// Первый тенант: расход 80% месячного бюджета (8 000 000 / 10 000 000) + флаг Warned80;
|
||||
// второй: небольшой бюджет без расхода. Дата начала периода — текущая (reset не срабатывает).
|
||||
@@ -326,10 +326,10 @@ public sealed class OperatorLimitsEndpointsHttpTests
|
||||
}
|
||||
|
||||
// Фейк-хранилище оператора с активным оператором operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -72,14 +72,14 @@ public sealed class OperatorMaintenanceEndpointsHttpTests
|
||||
}
|
||||
|
||||
// Прогоняет сценарий на хосте с активным оператором operator/operator и заданным реестром тенантов.
|
||||
private static Task RunAsync(TestTenantStore tenantStore, Func<string, FakeOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, Task> scenario) =>
|
||||
private static Task RunAsync(TestTenantStore tenantStore, Func<string, TestOperatorAuthStore, FakeAuthStore, TestTenantStore, TestInviteStore, TestAuditLogStore, Task> scenario) =>
|
||||
OperatorAuthHttpHost.RunAsync(NewOperatorStore(), new FakeAuthStore(), scenario, tenantStore: tenantStore);
|
||||
|
||||
// Фейк-хранилище оператора с активным оператором operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -257,7 +257,7 @@ public sealed class OperatorSettingsEndpointsHttpTests
|
||||
$"{baseAddress}/api/operator/settings/telegram-keys";
|
||||
|
||||
// Прогоняет сценарий на хосте с фейком глобального хранилища и аудита.
|
||||
private static Task RunAsync(Func<string, FakeOperatorAuthStore, FakeAuthStore, TestGlobalSettingsStore, TestAuditLogStore, Task> scenario, TestAuditLogStore? auditStore = null) =>
|
||||
private static Task RunAsync(Func<string, TestOperatorAuthStore, FakeAuthStore, TestGlobalSettingsStore, TestAuditLogStore, Task> scenario, TestAuditLogStore? auditStore = null) =>
|
||||
OperatorAuthHttpHost.RunWithGlobalSettingsAsync(
|
||||
NewOperatorStore(),
|
||||
new FakeAuthStore(),
|
||||
@@ -273,10 +273,10 @@ public sealed class OperatorSettingsEndpointsHttpTests
|
||||
}
|
||||
|
||||
// Фейк-хранилище оператора с активным оператором operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -486,7 +486,7 @@ public sealed class OperatorTenantsEndpointsHttpTests
|
||||
|
||||
// Прогоняет сценарий на хосте с одним активным тенантом и одним пользователем в нём.
|
||||
private static Task RunAsync(
|
||||
Func<string, FakeOperatorAuthStore, FakeAuthStore, TestTenantStore, TestAuditLogStore, Task> scenario,
|
||||
Func<string, TestOperatorAuthStore, FakeAuthStore, TestTenantStore, TestAuditLogStore, Task> scenario,
|
||||
TestAuditLogStore? auditStore = null,
|
||||
TestTenantStore? tenantStore = null)
|
||||
{
|
||||
@@ -504,7 +504,7 @@ public sealed class OperatorTenantsEndpointsHttpTests
|
||||
// Проверяет запись tenant_status_changed с заданным статусом и актором-оператором.
|
||||
private static void AssertStatusChangedAudit(
|
||||
TestAuditLogStore auditStore,
|
||||
FakeOperatorAuthStore operatorStore,
|
||||
TestOperatorAuthStore operatorStore,
|
||||
string status)
|
||||
{
|
||||
// В ленте может быть несколько tenant_status_changed этого тенанта (suspend → unsuspend) — ищем по статусу.
|
||||
@@ -569,10 +569,10 @@ public sealed class OperatorTenantsEndpointsHttpTests
|
||||
}
|
||||
|
||||
// Фейк-хранилище оператора с активным оператором operator/operator.
|
||||
private static FakeOperatorAuthStore NewOperatorStore()
|
||||
private static TestOperatorAuthStore NewOperatorStore()
|
||||
{
|
||||
var passwordHasher = TestHashers.New();
|
||||
var store = new FakeOperatorAuthStore();
|
||||
var store = new TestOperatorAuthStore();
|
||||
store.AddOperator(new StoredOperatorDto(
|
||||
Guid.NewGuid(),
|
||||
OperatorLogin,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using Deal.Modules.Tenants.Application.Abstractions;
|
||||
using Deal.Modules.Tenants.Application.Models;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Deal.Tests.Unit.Support;
|
||||
|
||||
/// <summary>
|
||||
/// Подставка <see cref="IOperatorAuthStore"/> на списках: сервисы получают NSubstitute-подставку
|
||||
/// (<see cref="Store"/>), тесты сеют/проверяют операторов и сессии через
|
||||
/// <see cref="AddOperator"/>, <see cref="AddSession"/>, <see cref="Operators"/>, <see cref="Sessions"/>,
|
||||
/// журнал мутирующих вызовов — <see cref="Calls"/>.
|
||||
/// </summary>
|
||||
public sealed class TestOperatorAuthStore
|
||||
{
|
||||
private readonly List<StoredOperatorDto> _operators = [];
|
||||
private readonly List<OperatorSessionDto> _sessions = [];
|
||||
private readonly List<string> _calls = [];
|
||||
|
||||
/// <summary>
|
||||
/// Подставка порта операторской аутентификации (создаётся в конструкторе).
|
||||
/// </summary>
|
||||
public IOperatorAuthStore Store { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Операторы хранилища.
|
||||
/// </summary>
|
||||
public IReadOnlyList<StoredOperatorDto> Operators => _operators;
|
||||
|
||||
/// <summary>
|
||||
/// Сессии операторов хранилища.
|
||||
/// </summary>
|
||||
public IReadOnlyList<OperatorSessionDto> Sessions => _sessions;
|
||||
|
||||
/// <summary>
|
||||
/// Журнал мутирующих вызовов в порядке их совершения.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Calls => _calls;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт подставку с пустыми списками.
|
||||
/// </summary>
|
||||
public TestOperatorAuthStore()
|
||||
{
|
||||
Store = Substitute.For<IOperatorAuthStore>();
|
||||
Store.FindByLoginAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(ci => _operators.SingleOrDefault(o => o.Login == ci.ArgAt<string>(0)));
|
||||
Store.FindSessionByTokenHashAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(ci => _sessions.SingleOrDefault(s => s.TokenHash == ci.ArgAt<string>(0)));
|
||||
Store.When(s => s.CreateAsync(Arg.Any<StoredOperatorDto>(), Arg.Any<CancellationToken>()))
|
||||
.Do(ci =>
|
||||
{
|
||||
StoredOperatorDto operatorRecord = ci.Arg<StoredOperatorDto>();
|
||||
_operators.Add(operatorRecord);
|
||||
_calls.Add($"create-operator:{operatorRecord.Id}");
|
||||
});
|
||||
Store.When(s => s.CreateSessionAsync(Arg.Any<OperatorSessionDto>(), Arg.Any<CancellationToken>()))
|
||||
.Do(ci =>
|
||||
{
|
||||
OperatorSessionDto session = ci.Arg<OperatorSessionDto>();
|
||||
_sessions.Add(session);
|
||||
_calls.Add($"create-session:{session.OperatorId}");
|
||||
});
|
||||
Store.When(s => s.DeleteSessionAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()))
|
||||
.Do(ci =>
|
||||
{
|
||||
_sessions.RemoveAll(s => s.TokenHash == ci.ArgAt<string>(0));
|
||||
_calls.Add($"delete-session:{ci.ArgAt<string>(0)}");
|
||||
});
|
||||
Store.When(s => s.DeleteExpiredSessionsAsync(Arg.Any<CancellationToken>()))
|
||||
.Do(_ =>
|
||||
{
|
||||
_sessions.RemoveAll(s => s.ExpiresAt <= DateTimeOffset.UtcNow);
|
||||
_calls.Add("delete-expired");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет оператора
|
||||
/// </summary>
|
||||
/// <param name="operatorRecord">Оператор.</param>
|
||||
public void AddOperator(StoredOperatorDto operatorRecord) => _operators.Add(operatorRecord);
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет сессию
|
||||
/// </summary>
|
||||
/// <param name="session">Сессия.</param>
|
||||
public void AddSession(OperatorSessionDto session) => _sessions.Add(session);
|
||||
}
|
||||
Reference in New Issue
Block a user