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