Почистить комментарии от упоминаний процесса

Удалены <remarks>, <summary> сжаты до короткой фразы, вырезаны
ссылки на Task/Ruling/этап/python/прототип; //-комментарии со ссылками
на процесс удалены; то же в .proto. Правила обновлены в
docs/spec/Код-стайл-Дейл.md. Строк комментариев 27210 -> ~19100.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:39:39 +03:00
parent 5f5538d33b
commit b053d58335
902 changed files with 3902 additions and 12074 deletions
+20 -32
View File
@@ -7,11 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Deal.Ai.Tests.Ai;
/// <summary>
/// In-proc gRPC-тесты AiService поверх фейк-провайдера (план Task 8, Acceptance): все 4 RPC
/// (Filter/Classify/GenerateKeywords/EvaluateFit) с подменой LLM-фасада (без сети) через
/// реальный хост (Kestrel HTTP/2, интерцептор service-token). Проверяются: разбор решений и
/// usage в ответах, собранные сервисом промпты, недоступность провайдера → UNAVAILABLE с текстом
/// 1:1 Ruling 5, ответ без JSON в Classify → ok=false (не ошибка), INVALID_ARGUMENT конфига.
/// In-proc gRPC-тесты AiService поверх фейк-провайдера
/// </summary>
public sealed class AiRpcTests
{
@@ -21,7 +17,6 @@ public sealed class AiRpcTests
// Usage API-ответа сценариев (проверка проброса в reply).
private static readonly ProviderUsage SampleUsage = new(11, 5, 16);
// Текст ошибки UNAVAILABLE 1:1 Ruling 5 / ai.py L115117.
private const string UnavailableDetail =
"ИИ (DeepSeek) не ответил корректно — повторите попытку через несколько секунд";
@@ -49,7 +44,6 @@ public sealed class AiRpcTests
Assert.Equal("похоже на заявку", reply.Reason);
AssertUsage(reply.Usage, SampleUsage);
// Сервис передаёт промпт system-сообщением и оборачивает текст как ai.py L193.
FakeProviderCall call = Assert.Single(fake.Calls);
Assert.Equal("Фильтр: {domain}", call.SystemPrompt);
Assert.Equal("Сообщение:\nИщем разработчика на проект", call.UserText);
@@ -77,7 +71,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Filter: модель не вернула pass — по умолчанию пропуск (1:1 ai.py L195: bool(get(pass, true))).
/// Filter: модель не вернула pass — по умолчанию пропуск
/// </summary>
[Fact]
public async Task Filter_MissingPassField_DefaultsToPass()
@@ -97,8 +91,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Filter: провайдер недоступен после ретраев (3 попытки) → UNAVAILABLE с текстом 1:1 Ruling 5
/// (ядро трактует как «ИИ недоступен» и пропускает сообщение локальным путём).
/// Filter: провайдер недоступен после ретраев
/// </summary>
[Fact]
public async Task Filter_ProviderUnavailable_ThrowsUnavailableWithDetail()
@@ -120,7 +113,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Filter: ответ модели без JSON после ретраев — тоже UNAVAILABLE (у метода нет ok-поля).
/// Filter: ответ модели без JSON после ретраев — тоже UNAVAILABLE
/// </summary>
[Fact]
public async Task Filter_AnswerWithoutJson_ThrowsUnavailable()
@@ -141,8 +134,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Classify: модель вернула JSON — ok=true, json = извлечённый ответ строкой (маппинг в ядре),
/// usage пробрасывается.
/// Classify: модель вернула JSON — ok=true, json = извлечённый ответ строкой
/// </summary>
[Fact]
public async Task Classify_ModelAnsweredJson_ReturnsOkAndJson()
@@ -178,8 +170,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Classify: модель отвечала, но без разбираемого JSON после ретраев → ok=false, НЕ RPC-ошибка
/// (README ai.proto L201204); usage последней попытки в ответе (оценка по символам).
/// Classify: модель отвечала, но без разбираемого JSON после ретраев → ok=false, НЕ RPC-ошибка; usage последней попытки в ответе
/// </summary>
[Fact]
public async Task Classify_AnswerWithoutJson_ReturnsOkFalseWithUsage()
@@ -211,7 +202,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Classify: провайдер недоступен — UNAVAILABLE (ядро падает в локальный разбор, aiFail).
/// Classify: провайдер недоступен — UNAVAILABLE
/// </summary>
[Fact]
public async Task Classify_ProviderUnavailable_ThrowsUnavailable()
@@ -232,7 +223,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// GenerateKeywords: ключи из JSON-ответа + фиксированный промпт с описанием задачи.
/// GenerateKeywords
/// </summary>
[Fact]
public async Task GenerateKeywords_ModelReturnedKeywords_ReturnsList()
@@ -253,7 +244,6 @@ public sealed class AiRpcTests
Assert.Equal(["стройка", "ремонт квартир", "подряды"], reply.Keywords);
AssertUsage(reply.Usage, SampleUsage);
// Фиксированный промпт (routes L36–47) и пользовательское сообщение с описанием.
FakeProviderCall call = Assert.Single(fake.Calls);
Assert.Contains("эксперт по поиску Telegram-каналов", call.SystemPrompt, StringComparison.Ordinal);
Assert.Contains("Верни строго JSON", call.SystemPrompt, StringComparison.Ordinal);
@@ -262,7 +252,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// GenerateKeywords: не-строковые элементы списка пропускаются (чистку делает ядро).
/// GenerateKeywords
/// </summary>
[Fact]
public async Task GenerateKeywords_NonStringItems_Skipped()
@@ -281,7 +271,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// GenerateKeywords: модель не вернула ключи — пустой список (не ошибка).
/// GenerateKeywords
/// </summary>
[Fact]
public async Task GenerateKeywords_NoKeywordsField_ReturnsEmpty()
@@ -300,8 +290,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// EvaluateFit: fit=1 + причина; промпт собран сервисом из описания и ключей (discovery_eval
/// L50–54), сообщение — как «Сообщение:\n…».
/// EvaluateFit: fit=1 + причина; промпт собран сервисом из описания и ключей, сообщение — как «Сообщение:\n…».
/// </summary>
[Fact]
public async Task EvaluateFit_ModelFits_ReturnsFitAndReason()
@@ -332,7 +321,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// EvaluateFit: ключи задачи подставляются в промпт строкой через запятую (1:1 _ai_prompt).
/// EvaluateFit: ключи задачи подставляются в промпт строкой через запятую.
/// </summary>
[Fact]
public async Task EvaluateFit_KeywordsJoinedIntoPrompt()
@@ -358,8 +347,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// EvaluateFit: fit=0 без причины модели — причина по умолчанию «не подходит» (1:1 _ai_reason
/// discovery_eval L167171); строковое «нет» трактуется как ложь (1:1 _ai_fit L158164).
/// EvaluateFit: fit=0 без причины модели — причина по умолчанию «не подходит»; строковое «нет» трактуется как ложь.
/// </summary>
[Fact]
public async Task EvaluateFit_ModelNoFit_ReturnsDefaultReason()
@@ -379,7 +367,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// EvaluateFit: fit строкой «нет» — false (паритет _ai_fit), причина из модели.
/// EvaluateFit: fit строкой «нет» — false, причина из модели.
/// </summary>
[Fact]
public async Task EvaluateFit_StringFalsyFit_ReturnsFalse()
@@ -399,7 +387,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// EvaluateFit: длинная причина модели усекается до 200 символов (1:1 _AI_REASON_LIMIT).
/// EvaluateFit: длинная причина модели усекается до 200 символов.
/// </summary>
[Fact]
public async Task EvaluateFit_LongReason_IsTruncatedTo200()
@@ -420,7 +408,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Конфиг-валидация: запрос без конфига провайдера (пустой base_url) → INVALID_ARGUMENT.
/// Конфиг-валидация
/// </summary>
[Fact]
public async Task Classify_WithoutProviderConfig_IsInvalidArgument()
@@ -441,7 +429,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Конфиг-валидация: пустая model конфига → INVALID_ARGUMENT (вызов модели невозможен).
/// Конфиг-валидация
/// </summary>
[Fact]
public async Task GenerateKeywords_EmptyModel_IsInvalidArgument()
@@ -466,7 +454,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Серверный лимит text (ai.proto Filter.text: core обрезает до 4000): превышение → INVALID_ARGUMENT.
/// Серверный лимит text
/// </summary>
[Fact]
public async Task Filter_TooLongText_IsInvalidArgument()
@@ -492,7 +480,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Серверный лимит description (ai.proto GenerateKeywords.description: core обрезает до 4000).
/// Серверный лимит description
/// </summary>
[Fact]
public async Task GenerateKeywords_TooLongDescription_IsInvalidArgument()
@@ -517,7 +505,7 @@ public sealed class AiRpcTests
}
/// <summary>
/// Обязательный tenant-id в metadata (Ruling 1): отсутствует → UNAUTHENTICATED до вызова.
/// Обязательный tenant-id в metadata
/// </summary>
[Fact]
public async Task Classify_WithoutTenantId_IsUnauthenticated()