This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using System.Text.Json;
|
||||
using Deal.Api.Middleware;
|
||||
using Deal.SharedKernel.Errors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Deal.Tests.Unit.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Тесты обработчика необработанных исключений HTTP.
|
||||
/// </summary>
|
||||
public sealed class DealExceptionHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task NotFound_MapsTo404WithCodeAndRussianDetail()
|
||||
{
|
||||
DefaultHttpContext context = CreateContext();
|
||||
DealExceptionHandler handler = new(Substitute.For<ILogger<DealExceptionHandler>>());
|
||||
|
||||
bool handled = await handler.TryHandleAsync(
|
||||
context,
|
||||
new NotFoundException("Карточка", "c_1"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.True(handled);
|
||||
Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode);
|
||||
(string detail, string code) = await ReadBodyAsync(context);
|
||||
Assert.Equal(DealErrorCodes.NotFound, code);
|
||||
Assert.Contains("Карточка", detail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Unavailable_MapsTo503()
|
||||
{
|
||||
DefaultHttpContext context = CreateContext();
|
||||
DealExceptionHandler handler = new(Substitute.For<ILogger<DealExceptionHandler>>());
|
||||
|
||||
await handler.TryHandleAsync(
|
||||
context,
|
||||
new ServiceUnavailableException("ИИ"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Equal(StatusCodes.Status503ServiceUnavailable, context.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnexpectedException_MapsToGeneric500WithoutStackOrDetails()
|
||||
{
|
||||
DefaultHttpContext context = CreateContext();
|
||||
DealExceptionHandler handler = new(Substitute.For<ILogger<DealExceptionHandler>>());
|
||||
|
||||
bool handled = await handler.TryHandleAsync(
|
||||
context,
|
||||
new InvalidOperationException("секретная внутренняя деталь"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.True(handled);
|
||||
Assert.Equal(StatusCodes.Status500InternalServerError, context.Response.StatusCode);
|
||||
(string detail, string code) = await ReadBodyAsync(context);
|
||||
Assert.Equal(DealErrorCodes.Internal, code);
|
||||
Assert.DoesNotContain("секретная внутренняя деталь", detail);
|
||||
Assert.DoesNotContain("at ", detail);
|
||||
}
|
||||
|
||||
private static DefaultHttpContext CreateContext()
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
context.Response.Body = new MemoryStream();
|
||||
return context;
|
||||
}
|
||||
|
||||
private static async Task<(string Detail, string Code)> ReadBodyAsync(HttpContext context)
|
||||
{
|
||||
context.Response.Body.Seek(0, SeekOrigin.Begin);
|
||||
using var reader = new StreamReader(context.Response.Body);
|
||||
string json = await reader.ReadToEndAsync();
|
||||
using JsonDocument document = JsonDocument.Parse(json);
|
||||
return (
|
||||
document.RootElement.GetProperty("detail").GetString() ?? string.Empty,
|
||||
document.RootElement.GetProperty("code").GetString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public sealed class DiscoveryWorkerSchedulerTests
|
||||
TestDiscoveryStore StoreB,
|
||||
TestDiscoveryGateway GatewayA,
|
||||
TestDiscoveryGateway GatewayB,
|
||||
TenantContext TenantContext,
|
||||
ITenantContext TenantContext,
|
||||
ListLogger Logs);
|
||||
|
||||
[Fact]
|
||||
@@ -91,7 +91,7 @@ public sealed class DiscoveryWorkerSchedulerTests
|
||||
private static Context CreateContext()
|
||||
{
|
||||
var tenants = new TestTenantRepository(Tenant(TenantA), Tenant(TenantB));
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
var storeA = new TestDiscoveryStore();
|
||||
var storeB = new TestDiscoveryStore();
|
||||
var settingsA = new TestSettingsStore();
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class MlOutboxFlushSchedulerTests
|
||||
{
|
||||
var store = new TestMlLearningStore();
|
||||
SeedRows(store, count: 25, prefix: "a");
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
port,
|
||||
new TestTenantRepository(Tenant(TenantA)),
|
||||
@@ -67,7 +67,7 @@ public sealed class MlOutboxFlushSchedulerTests
|
||||
service.TrainUnavailable = true;
|
||||
var store = new TestMlLearningStore();
|
||||
SeedRows(store, count: 5, prefix: "a");
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
port,
|
||||
new TestTenantRepository(Tenant(TenantA)),
|
||||
@@ -95,7 +95,7 @@ public sealed class MlOutboxFlushSchedulerTests
|
||||
SeedRows(storeA, count: 12, prefix: "a");
|
||||
var storeB = new TestMlLearningStore();
|
||||
SeedRows(storeB, count: 3, prefix: "b");
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
port,
|
||||
new TestTenantRepository(Tenant(TenantA), Tenant(TenantB)),
|
||||
@@ -121,7 +121,7 @@ public sealed class MlOutboxFlushSchedulerTests
|
||||
{
|
||||
var store = new TestMlLearningStore();
|
||||
SeedRows(store, count: 105, prefix: "a");
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
port,
|
||||
new TestTenantRepository(Tenant(TenantA)),
|
||||
@@ -148,7 +148,7 @@ public sealed class MlOutboxFlushSchedulerTests
|
||||
private static ServiceProvider BuildProvider(
|
||||
int port,
|
||||
TestTenantRepository tenants,
|
||||
TenantContext tenantContext,
|
||||
ITenantContext tenantContext,
|
||||
Dictionary<Guid, TestMlLearningStore> storesByTenant)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Deal.Contracts.Integrations.Models;
|
||||
using Deal.Tests.Unit.Support;
|
||||
using Deal.Modules.Cards.Application.Sources;
|
||||
using Deal.Modules.Kanban.Application.Models;
|
||||
using Deal.Modules.Kanban.Application.Services;
|
||||
using Deal.SharedKernel.Errors;
|
||||
using Deal.Tests.Unit.Modules.Settings;
|
||||
using Deal.Tests.Unit.Support;
|
||||
|
||||
namespace Deal.Tests.Unit.Contracts;
|
||||
|
||||
@@ -343,13 +344,13 @@ public sealed class CardsServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Trash_CardMissing_ReturnsNull()
|
||||
public async Task Trash_CardMissing_ThrowsNotFound()
|
||||
{
|
||||
(CardsService service, _, _, TestMlClient ml) = Create();
|
||||
|
||||
CardDto? result = await service.TrashCardAsync("l_ghost", CancellationToken.None);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.TrashCardAsync("l_ghost", CancellationToken.None));
|
||||
|
||||
Assert.Null(result); // эндпоинт отвечает 404 «Карточка не найдена»
|
||||
Assert.Empty(ml.Pushed);
|
||||
}
|
||||
|
||||
@@ -425,13 +426,12 @@ public sealed class CardsServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Restore_CardMissing_ReturnsNull()
|
||||
public async Task Restore_CardMissing_ThrowsNotFound()
|
||||
{
|
||||
(CardsService service, _, _, _) = Create();
|
||||
|
||||
string? back = await service.RestoreCardAsync("l_ghost", CancellationToken.None);
|
||||
|
||||
Assert.Null(back); // эндпоинт отвечает 404 «Карточка не найдена»
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.RestoreCardAsync("l_ghost", CancellationToken.None));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Deal.Modules.Discovery.Application.Exceptions;
|
||||
using Deal.Modules.Discovery.Application.Models;
|
||||
using Deal.Tests.Unit.Support;
|
||||
using Deal.Modules.Discovery.Application.Services;
|
||||
using Deal.SharedKernel.Errors;
|
||||
using Deal.Tests.Unit.Modules.Settings;
|
||||
using Deal.Tests.Unit.Support;
|
||||
|
||||
namespace Deal.Tests.Unit.Modules.Discovery;
|
||||
|
||||
@@ -131,14 +132,13 @@ public sealed class DiscoveryTasksServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_MissingTask_ReturnsNull()
|
||||
public async Task Patch_MissingTask_ThrowsNotFound()
|
||||
{
|
||||
(DiscoveryTasksService service, _, _) = Create();
|
||||
|
||||
DiscoveryTaskDto? patched = await service.PatchAsync(
|
||||
"dt_missing", new DiscoveryTaskPatch { Name = "Новое" }, CancellationToken.None);
|
||||
|
||||
Assert.Null(patched);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.PatchAsync(
|
||||
"dt_missing", new DiscoveryTaskPatch { Name = "Новое" }, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -201,13 +201,12 @@ public sealed class DiscoveryTasksServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Start_MissingTask_ReturnsNull()
|
||||
public async Task Start_MissingTask_ThrowsNotFound()
|
||||
{
|
||||
(DiscoveryTasksService service, _, _) = Create();
|
||||
|
||||
DiscoveryTaskDto? task = await service.StartAsync("dt_missing", CancellationToken.None);
|
||||
|
||||
Assert.Null(task);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.StartAsync("dt_missing", CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -281,9 +280,8 @@ public sealed class DiscoveryTasksServiceTests
|
||||
store.SeedCandidate(Candidate("c_2", "dt_2"));
|
||||
await store.Store.UpsertBlacklistAsync("c_1", "Источник", "причина", CancellationToken.None);
|
||||
|
||||
bool deleted = await service.DeleteAsync("dt_1", CancellationToken.None);
|
||||
await service.DeleteAsync("dt_1", CancellationToken.None);
|
||||
|
||||
Assert.True(deleted);
|
||||
Assert.Single(store.Tasks); // dt_2 осталась
|
||||
Assert.Equal("dt_2", Assert.Single(store.Tasks).Id);
|
||||
Assert.Single(store.Candidates); // кандидат dt_2 остался
|
||||
@@ -292,13 +290,12 @@ public sealed class DiscoveryTasksServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_MissingTask_ReturnsFalse()
|
||||
public async Task Delete_MissingTask_ThrowsNotFound()
|
||||
{
|
||||
(DiscoveryTasksService service, _, _) = Create();
|
||||
|
||||
bool deleted = await service.DeleteAsync("dt_missing", CancellationToken.None);
|
||||
|
||||
Assert.False(deleted);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.DeleteAsync("dt_missing", CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text;
|
||||
using Deal.Modules.Kanban.Application.Models;
|
||||
using Deal.Modules.Kanban.Application.Services;
|
||||
using Deal.SharedKernel.Errors;
|
||||
using Deal.Tests.Unit.Contracts;
|
||||
using Deal.Tests.Unit.Modules.Settings;
|
||||
|
||||
@@ -73,14 +74,13 @@ public sealed class CardsServiceFilesTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Add_CardMissing_ReturnsNullAndDoesNotWriteObject()
|
||||
public async Task Add_CardMissing_ThrowsAndDoesNotWriteObject()
|
||||
{
|
||||
(CardsService service, TestKanjStore store, TestFileStorage storage) = Create();
|
||||
|
||||
CardFileDto? entry = await service.AddFileAsync(
|
||||
"c_missing", "photo.png", "image/png", new MemoryStream("x"u8.ToArray()), 1, CancellationToken.None);
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => service.AddFileAsync(
|
||||
"c_missing", "photo.png", "image/png", new MemoryStream("x"u8.ToArray()), 1, CancellationToken.None));
|
||||
|
||||
Assert.Null(entry); // эндпоинт отвечает 404 «Карточка не найдена»
|
||||
Assert.Empty(storage.StoredObjectKeys); // «add на несуществующей карточке не пишет объект»
|
||||
Assert.Empty(store.CardDtos);
|
||||
}
|
||||
@@ -151,25 +151,23 @@ public sealed class CardsServiceFilesTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEntry_CardMissing_ReturnsNull()
|
||||
public async Task GetEntry_CardMissing_ThrowsNotFound()
|
||||
{
|
||||
(CardsService service, _, _) = Create();
|
||||
|
||||
CardFileDto? entry = await service.GetFileEntryAsync("c_missing", "pf_1", CancellationToken.None);
|
||||
|
||||
Assert.Null(entry); // 404 «Карточка не найдена» у эндпоинта
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.GetFileEntryAsync("c_missing", "pf_1", CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEntry_FileNotInMetadata_ReturnsNull()
|
||||
public async Task GetEntry_FileNotInMetadata_ThrowsNotFound()
|
||||
{
|
||||
(CardsService service, TestKanjStore store, _) = Create();
|
||||
store.SeedCard(Card("c_1")
|
||||
with { Files = new[] { new CardFileDto("pf_1", "tz.pdf", 100, "document", "Документ", "k") } });
|
||||
|
||||
CardFileDto? entry = await service.GetFileEntryAsync("c_1", "pf_ghost", CancellationToken.None);
|
||||
|
||||
Assert.Null(entry); // файла нет в метаданных карточки — 404-семантика
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.GetFileEntryAsync("c_1", "pf_ghost", CancellationToken.None));
|
||||
}
|
||||
|
||||
|
||||
@@ -225,13 +223,13 @@ public sealed class CardsServiceFilesTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Remove_CardMissing_ReturnsNullWithoutStorageDelete()
|
||||
public async Task Remove_CardMissing_ThrowsWithoutStorageDelete()
|
||||
{
|
||||
(CardsService service, _, TestFileStorage storage) = Create();
|
||||
|
||||
CardDto? card = await service.RemoveFileAsync("c_missing", "pf_1", CancellationToken.None);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.RemoveFileAsync("c_missing", "pf_1", CancellationToken.None));
|
||||
|
||||
Assert.Null(card); // эндпоинт отвечает 404 «Карточка не найдена»
|
||||
Assert.Empty(storage.DeletedKeys);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Deal.Modules.Kanban.Application.Models;
|
||||
using Deal.Modules.Kanban.Application.Services;
|
||||
using Deal.SharedKernel.Errors;
|
||||
using Deal.Tests.Unit.Contracts;
|
||||
using Deal.Tests.Unit.Modules.Settings;
|
||||
|
||||
@@ -99,13 +100,13 @@ public sealed class CardsServiceSelectedTests
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task TakeCard_CardMissing_ReturnsNullAndCreatesNothing()
|
||||
public async Task TakeCard_CardMissing_ThrowsAndCreatesNothing()
|
||||
{
|
||||
(CardsService service, TestKanjStore store, _, _) = Create();
|
||||
|
||||
CardDto? card = await service.TakeCardAsync("c_missing", CancellationToken.None);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.TakeCardAsync("c_missing", CancellationToken.None));
|
||||
|
||||
Assert.Null(card);
|
||||
Assert.Empty(store.CardDtos);
|
||||
}
|
||||
|
||||
@@ -122,8 +123,7 @@ public sealed class CardsServiceSelectedTests
|
||||
stack: new[] { "Python", "aiogram" },
|
||||
budget: new CardBudgetDto(From: 1600, To: 2200, Cur: "USD")));
|
||||
|
||||
CardDto card = await service.TakeCardAsync("c_1", CancellationToken.None)
|
||||
?? throw new InvalidOperationException("take вернул null при существующей карточке");
|
||||
CardDto card = await service.TakeCardAsync("c_1", CancellationToken.None);
|
||||
|
||||
Assert.Equal("c_1", card.Id);
|
||||
Assert.Equal("planned", card.Col);
|
||||
@@ -182,8 +182,7 @@ public sealed class CardsServiceSelectedTests
|
||||
("tzText", "ТЗ"),
|
||||
("stack", new[] { "C#", ".NET" }), // стек — полная замена массива
|
||||
("budget", new { from = 500, cur = "EUR" })),
|
||||
CancellationToken.None)
|
||||
?? throw new InvalidOperationException("patch вернул null при существующей карточке");
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Equal("Новый заголовок", card.Title);
|
||||
Assert.Equal(string.Empty, card.Summary); // summary очищена пустой строкой
|
||||
@@ -255,16 +254,14 @@ public sealed class CardsServiceSelectedTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_CardMissing_ReturnsNull()
|
||||
public async Task Patch_CardMissing_ThrowsNotFound()
|
||||
{
|
||||
(CardsService service, _, _, _) = Create();
|
||||
|
||||
CardDto? card = await service.PatchCardAsync(
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => service.PatchCardAsync(
|
||||
"c_missing",
|
||||
PatchBody(("title", "Т")),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Null(card);
|
||||
CancellationToken.None));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Deal.Modules.Kanban.Application.Models;
|
||||
using Deal.Modules.Kanban.Application.Services;
|
||||
using Deal.SharedKernel.Errors;
|
||||
using Deal.Tests.Unit.Modules.Settings;
|
||||
|
||||
namespace Deal.Tests.Unit.Support;
|
||||
@@ -237,14 +238,12 @@ public sealed class ContainersServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_UnknownContainer_ReturnsNull()
|
||||
public async Task Patch_UnknownContainer_ThrowsNotFound()
|
||||
{
|
||||
(ContainersService service, _, _) = Create();
|
||||
|
||||
ContainerDto? result = await service.PatchAsync(
|
||||
"b_missing", Patch(name: "X"), CancellationToken.None);
|
||||
|
||||
Assert.Null(result); // эндпоинт отвечает 404 «Контейнер не найден»
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.PatchAsync("b_missing", Patch(name: "X"), CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Deal.SharedKernel.Resources;
|
||||
|
||||
namespace Deal.Tests.Unit.Support;
|
||||
|
||||
/// <summary>
|
||||
/// Тесты ресурсов текстов ошибок (ErrorMessages.resx).
|
||||
/// </summary>
|
||||
public sealed class ErrorResourcesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Format_KnownKey_ReturnsRussianText()
|
||||
{
|
||||
string text = ErrorResources.Format(ErrorResourceKeys.UnexpectedError);
|
||||
|
||||
Assert.Contains("Внутренняя ошибка", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_TemplateWithArgs_SubstitutesPlaceholders()
|
||||
{
|
||||
string text = ErrorResources.Format(ErrorResourceKeys.NotFoundEntityWithId, "Карточка", "c_1");
|
||||
|
||||
Assert.Contains("Карточка", text);
|
||||
Assert.Contains("c_1", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_UnknownKey_ReturnsKey()
|
||||
{
|
||||
string text = ErrorResources.Format("NoSuchKey");
|
||||
|
||||
Assert.Equal("NoSuchKey", text);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public sealed class PipelineWorkerSchedulerTests
|
||||
TestKanjStore KanjB,
|
||||
SseSubscription SubscriptionB,
|
||||
PipelinePumpGate PumpGate,
|
||||
TenantContext TenantContext,
|
||||
ITenantContext TenantContext,
|
||||
ListLogger Logs);
|
||||
|
||||
// ─── Цикл: pump каждого тенанта в собственном scope + new_card ─────────
|
||||
@@ -149,7 +149,7 @@ public sealed class PipelineWorkerSchedulerTests
|
||||
private static Context CreateContext(bool withThrowingQueueReadA = false)
|
||||
{
|
||||
var tenants = new TestTenantRepository(Tenant(TenantA), Tenant(TenantB));
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
var pipelineA = new TestPipelineStore(throwOnList: withThrowingQueueReadA);
|
||||
var pipelineB = new TestPipelineStore();
|
||||
var kanjA = new TestKanjStore();
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class StorageTickSchedulerTests
|
||||
TestKanjStore storeB = StoreWithExpiredInboxCard("l_b_old");
|
||||
var settings = new TestSettingsStore();
|
||||
var tenants = new TestTenantRepository(Tenant(TenantA), Tenant(TenantB));
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(tenants, tenantContext, storeA, storeB, settings);
|
||||
|
||||
SseBroker broker = provider.GetRequiredService<SseBroker>();
|
||||
@@ -74,7 +74,7 @@ public sealed class StorageTickSchedulerTests
|
||||
var storeB = new TestKanjStore();
|
||||
storeB.SeedCard(Card("l_b_fresh", KanbanColumns.Inbox, ReceivedAtMsAgo(TimeSpan.FromHours(1))));
|
||||
var settings = new TestSettingsStore();
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
new TestTenantRepository(Tenant(TenantA), Tenant(TenantB)),
|
||||
tenantContext,
|
||||
@@ -100,7 +100,7 @@ public sealed class StorageTickSchedulerTests
|
||||
{
|
||||
// У тенанта A настройки падают (имитация сбоя схемы/БД) — тик A логирует ошибку, B обрабатывается.
|
||||
TestKanjStore storeB = StoreWithExpiredInboxCard("l_b_old");
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
var settingsByTenant = new Dictionary<Guid, ISettingsStore>
|
||||
{
|
||||
[TenantA] = new ThrowingSettingsStore(),
|
||||
@@ -133,7 +133,7 @@ public sealed class StorageTickSchedulerTests
|
||||
[Fact]
|
||||
public async Task RunCycle_TenantListFailure_DoesNotThrow()
|
||||
{
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
new ThrowingTenantRepository(),
|
||||
tenantContext,
|
||||
@@ -152,7 +152,7 @@ public sealed class StorageTickSchedulerTests
|
||||
{
|
||||
var kanjStore = new TestKanjStore();
|
||||
var settings = new TestSettingsStore();
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
var pipelineStoreA = new TestPipelineStore();
|
||||
long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
pipelineStoreA.SeedRejected(Rejected("r_old_a", (long)(nowMs - TimeSpan.FromDays(4).TotalMilliseconds)));
|
||||
@@ -186,7 +186,7 @@ public sealed class StorageTickSchedulerTests
|
||||
cardStoreA.SeedCard(HoldCard("c_a_future", title: "Будущий", reminderAtMs: NowMs() + 60_000));
|
||||
cardStoreA.SeedCard(HoldCard("c_a_work", stage: "work", title: "В работе", reminderAtMs: NowMs() - 60_000));
|
||||
var cardStoreB = new TestKanjStore();
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
new TestTenantRepository(Tenant(TenantA), Tenant(TenantB)).Repository,
|
||||
tenantContext,
|
||||
@@ -221,7 +221,7 @@ public sealed class StorageTickSchedulerTests
|
||||
cardStoreA.SeedCard(HoldCard("c_past", title: "Старое", reminderAtMs: NowMs() - 60_000));
|
||||
var settingsA = new TestSettingsStore();
|
||||
settingsA.Preload(SettingsKeys.RemindersEnabled, "false");
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
new TestTenantRepository(Tenant(TenantA)).Repository,
|
||||
tenantContext,
|
||||
@@ -247,7 +247,7 @@ public sealed class StorageTickSchedulerTests
|
||||
// и тик A завершается, тенант B обрабатывается (автоархив + тост) — проход жив.
|
||||
var cardStoreA = new TestKanjStore(throwOnDueReminders: true);
|
||||
TestKanjStore storeB = StoreWithExpiredInboxCard("l_b_old");
|
||||
var tenantContext = new TenantContext();
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
await using ServiceProvider provider = BuildProvider(
|
||||
new TestTenantRepository(Tenant(TenantA), Tenant(TenantB)).Repository,
|
||||
tenantContext,
|
||||
@@ -280,7 +280,7 @@ public sealed class StorageTickSchedulerTests
|
||||
// Возвращает: Провайдер с зарегистрированными сервисами теста.
|
||||
private static ServiceProvider BuildProvider(
|
||||
TestTenantRepository tenants,
|
||||
TenantContext tenantContext,
|
||||
ITenantContext tenantContext,
|
||||
TestKanjStore storeA,
|
||||
TestKanjStore storeB,
|
||||
TestSettingsStore settings)
|
||||
@@ -301,7 +301,7 @@ public sealed class StorageTickSchedulerTests
|
||||
// Возвращает: Провайдер с зарегистрированными сервисами теста.
|
||||
private static ServiceProvider BuildProvider(
|
||||
ITenantRepository tenants,
|
||||
TenantContext tenantContext,
|
||||
ITenantContext tenantContext,
|
||||
Dictionary<Guid, TestKanjStore> storesByTenant,
|
||||
Dictionary<Guid, ISettingsStore> settingsByTenant,
|
||||
Dictionary<Guid, TestPipelineStore>? pipelineStoresByTenant = null)
|
||||
|
||||
Reference in New Issue
Block a user