ci / build-test (push) Canceled after 0s
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 зелёные.
99 lines
3.1 KiB
C#
99 lines
3.1 KiB
C#
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(" "));
|
|
}
|
|
}
|