SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/ Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue, контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер), Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог). Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0, тесты 1340/130/52/38/9 зелёные.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using Deal.Ai.Llm;
|
||||
|
||||
namespace Deal.Ai.Tests.Support;
|
||||
|
||||
/// <summary>
|
||||
/// Unit-тесты извлечения JSON из ответа модели
|
||||
/// </summary>
|
||||
public sealed class JsonExtractorTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Чистая JSON-строка объекта разбирается как есть.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_PlainObject_Parses()
|
||||
{
|
||||
JsonObject? json = JsonExtractor.TryExtractObject("""{"pass": true, "reason": "ок"}""");
|
||||
|
||||
Assert.NotNull(json);
|
||||
Assert.True((bool)json!["pass"]!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Markdown-обёртка ```json снимается
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_JsonFence_Unwraps()
|
||||
{
|
||||
JsonObject? json = JsonExtractor.TryExtractObject("```json\n{\"fit\": 1}\n```");
|
||||
|
||||
Assert.NotNull(json);
|
||||
Assert.Equal(1, (int)json!["fit"]!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обёртка без метки языка и с прозой вокруг — тоже снимается.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_PlainFenceWithProse_Unwraps()
|
||||
{
|
||||
const string answer = "Вот результат:\n```\n{\"keywords\": [\"php\"]}\n```\nНадеюсь, поможет.";
|
||||
JsonObject? json = JsonExtractor.TryExtractObject(answer);
|
||||
|
||||
Assert.NotNull(json);
|
||||
Assert.Single((JsonArray)json!["keywords"]!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON внутри произвольного текста берётся срезом между первой «{» и последней «}».
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_JsonInsideProse_Slices()
|
||||
{
|
||||
JsonObject? json = JsonExtractor.TryExtractObject("модель решила: {\"pass\": false} конец");
|
||||
|
||||
Assert.NotNull(json);
|
||||
Assert.False((bool)json!["pass"]!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Текст без JSON — null
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_NoJson_ReturnsNull()
|
||||
{
|
||||
Assert.Null(JsonExtractor.TryExtractObject("извините, не могу ответить"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Незакрытая обёртка и битый JSON — null, а не исключение.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_BrokenJson_ReturnsNull()
|
||||
{
|
||||
Assert.Null(JsonExtractor.TryExtractObject("```json\n{\"pass\": tru"));
|
||||
Assert.Null(JsonExtractor.TryExtractObject("{\"pass\": "));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON верхнего уровня не объект
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_NonObjectRoot_ReturnsNull()
|
||||
{
|
||||
Assert.Null(JsonExtractor.TryExtractObject("[\"php\", \"c#\"]"));
|
||||
Assert.Null(JsonExtractor.TryExtractObject("\"просто строка\""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Пустой/пробельный текст — null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryExtractObject_Empty_ReturnsNull()
|
||||
{
|
||||
Assert.Null(JsonExtractor.TryExtractObject(string.Empty));
|
||||
Assert.Null(JsonExtractor.TryExtractObject(" "));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user