Хелпер Support/TestAiTools: по умолчанию оба метода бросают NotSupportedException (семантика прежнего фейка — неожиданный вызов роняет тест), сценарии через ReturnsFit/ThrowsOnEvaluate, счётчик EvaluateFitCalls -> Received/DidNotReceive. Потребители (4 файла) перетипизированы на IAiTools, фейк удалён, тесты 1340.
This commit is contained in:
@@ -5,6 +5,8 @@ using Deal.Infrastructure.Integrations.Services;
|
||||
using Deal.Modules.Tenants.Application.Models;
|
||||
using Deal.SharedKernel.Tenants.Models;
|
||||
using Deal.Tests.Unit.Modules.Tenants;
|
||||
using Deal.Tests.Unit.Support;
|
||||
using NSubstitute;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Deal.Contracts.Integrations.Abstractions;
|
||||
using Deal.SharedKernel.Tenants.Abstractions;
|
||||
@@ -31,12 +33,12 @@ public sealed class BudgetedAiToolsTests
|
||||
Context ctx = Create(
|
||||
limits => limits.Preload(
|
||||
TenantGuid, budgetTokens: 1000, TenantLimitPeriods.Month, PeriodStart, usedTokens: 1000));
|
||||
ctx.Paid.Fit = new AiEvaluateFitResultDto(Fit: true, Reason: "подходит");
|
||||
ctx.Paid.ReturnsFit(new AiEvaluateFitResultDto(Fit: true, Reason: "подходит"));
|
||||
|
||||
AiUnavailableException exception = await Assert.ThrowsAsync<AiUnavailableException>(
|
||||
() => ctx.Decorator.EvaluateFitAsync("текст", "описание", new[] { "ключ" }, CancellationToken.None));
|
||||
|
||||
Assert.Equal(0, ctx.Paid.EvaluateFitCalls);
|
||||
await ctx.Paid.DidNotReceive().EvaluateFitAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<IReadOnlyCollection<string>>(), Arg.Any<CancellationToken>());
|
||||
Assert.Contains("исчерпан", exception.Message);
|
||||
}
|
||||
|
||||
@@ -49,7 +51,7 @@ public sealed class BudgetedAiToolsTests
|
||||
await Assert.ThrowsAsync<AiUnavailableException>(
|
||||
() => ctx.Decorator.EvaluateFitAsync("текст", "описание", Array.Empty<string>(), CancellationToken.None));
|
||||
|
||||
Assert.Equal(0, ctx.Paid.EvaluateFitCalls);
|
||||
await ctx.Paid.DidNotReceive().EvaluateFitAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<IReadOnlyCollection<string>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -63,7 +65,7 @@ public sealed class BudgetedAiToolsTests
|
||||
AiUnavailableException exception = await Assert.ThrowsAsync<AiUnavailableException>(
|
||||
() => ctx.Decorator.EvaluateFitAsync("текст", "описание", Array.Empty<string>(), CancellationToken.None));
|
||||
|
||||
Assert.Equal(0, ctx.Paid.EvaluateFitCalls);
|
||||
await ctx.Paid.DidNotReceive().EvaluateFitAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<IReadOnlyCollection<string>>(), Arg.Any<CancellationToken>());
|
||||
Assert.Contains("приостановлен", exception.Message);
|
||||
}
|
||||
|
||||
@@ -72,13 +74,13 @@ public sealed class BudgetedAiToolsTests
|
||||
{
|
||||
// Строки лимита нет — ленивый дефолт (10 000 000/месяц, расход 0) → Allowed=true → платный вызов.
|
||||
Context ctx = Create();
|
||||
ctx.Paid.Fit = new AiEvaluateFitResultDto(Fit: false, Reason: "не подходит");
|
||||
ctx.Paid.ReturnsFit(new AiEvaluateFitResultDto(Fit: false, Reason: "не подходит"));
|
||||
|
||||
AiEvaluateFitResultDto result = await ctx.Decorator.EvaluateFitAsync(
|
||||
"текст", "описание", new[] { "ключ" }, CancellationToken.None);
|
||||
|
||||
Assert.False(result.Fit);
|
||||
Assert.Equal(1, ctx.Paid.EvaluateFitCalls);
|
||||
await ctx.Paid.Received(1).EvaluateFitAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<IReadOnlyCollection<string>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -116,7 +118,7 @@ public sealed class BudgetedAiToolsTests
|
||||
public async Task GenerateKeywords_LargeBudget_DelegatesToPaid()
|
||||
{
|
||||
// Строки лимита нет — ленивый дефолт → Allowed=true → декоратор делегирует платному исполнителю.
|
||||
// FakeAiTools.GenerateKeywordsAsync всегда бросает NotSupportedException (вызов не ждали) — само
|
||||
// Подставка бросает NotSupportedException на GenerateKeywordsAsync (вызов не ждали) — само
|
||||
// исключение здесь и есть признак делегирования (упало бы в тесте, если бы декоратор не позвал платный).
|
||||
Context ctx = Create();
|
||||
|
||||
@@ -131,7 +133,7 @@ public sealed class BudgetedAiToolsTests
|
||||
// Paid: Платный фейк-исполнитель (счётчик/ответы сценария).
|
||||
private sealed record Context(
|
||||
IAiTools Decorator,
|
||||
FakeAiTools Paid);
|
||||
IAiTools Paid);
|
||||
|
||||
// Собирает контекст: платный фейк + фейк лимитов и tenant-контекст (как регистрирует
|
||||
// AddDealIntegrations при UseLocal=false).
|
||||
@@ -143,7 +145,7 @@ public sealed class BudgetedAiToolsTests
|
||||
configure?.Invoke(limits);
|
||||
ITenantContext tenantContext = new TenantContext();
|
||||
tenantContext.SetTenant(new TenantId(TenantIdValue));
|
||||
var paid = new FakeAiTools();
|
||||
var paid = TestAiTools.New();
|
||||
IAiTools decorator = new BudgetedAiTools(
|
||||
paid,
|
||||
limits,
|
||||
|
||||
Reference in New Issue
Block a user