Files
kompas3d-mcp/tests/Kompas.Mcp.Tests/ToolErrorTextTests.cs
T
mikhail 4f4dd082d9 Документы и диагностика: имя детали, честное сохранение, причина ошибки
set_part_info задаёт наименование и обозначение детали: без него в дереве
стоит безликая «Деталь», и она же уходит в штамп чертежа и в спецификацию.
describe_model показывает наименование в шапке, а безымянную деталь помечает
явно. Имя файла этого не заменяет — это разные свойства.

document_save рапортовал «Сохранено», ничего не записав: SaveAs возвращает
void и молча отказывает, если файл с этим именем уже открыт в КОМПАС другим
документом. Теперь путь и наличие файла проверяются после записи, а Save
сверяет флаг Changed. Освободить занятое имя нечем не было — document_close
получил режим all.

Текст исключения доходил до клиента только у McpException, всё остальное
подменялось на «An error occurred invoking». То есть подсказки в наших
сообщениях агент не видел вовсе. Фильтр CallTool в Program.cs возвращает
IsError с реальной причиной (ToolErrorText разворачивает AggregateException
и склеивает вложенные причины). Там же — регистрация PrimitiveService.
2026-07-31 14:51:12 +03:00

61 lines
2.4 KiB
C#

using Kompas.Mcp.Core.Startup;
namespace Kompas.Mcp.Tests;
/// <summary>
/// Текст ошибки инструмента. SDK показывает клиенту сообщение только у McpException, поэтому
/// причину собирает фильтр — и она обязана называть инструмент и суть, а не тип исключения.
/// </summary>
[Trait("Category", "Unit")]
public sealed class ToolErrorTextTests
{
[Fact]
public void Message_names_the_tool_and_the_reason()
{
var text = ToolErrorText.Describe("extrude", new InvalidOperationException("Эскиз id=7 уже закрыт."));
Assert.Contains("extrude", text, StringComparison.Ordinal);
Assert.Contains("Эскиз id=7 уже закрыт.", text, StringComparison.Ordinal);
}
[Fact]
public void Inner_exception_is_appended_as_the_underlying_cause()
{
var inner = new InvalidOperationException("ksTextEx вернул 0.");
var outer = new InvalidOperationException("Примитив [0] (text) не построен.", inner);
var text = ToolErrorText.Describe("sketch_add", outer);
Assert.Contains("Примитив [0] (text) не построен.", text, StringComparison.Ordinal);
Assert.Contains("ksTextEx вернул 0.", text, StringComparison.Ordinal);
}
[Fact]
public void Single_wrapped_aggregate_is_unwrapped_to_the_real_cause()
{
var aggregate = new AggregateException(new KeyNotFoundException("Эскиз с id=42 не найден."));
var text = ToolErrorText.Describe("extrude", aggregate);
Assert.Contains("Эскиз с id=42 не найден.", text, StringComparison.Ordinal);
Assert.DoesNotContain("One or more errors", text, StringComparison.Ordinal);
}
[Fact]
public void Empty_message_falls_back_to_the_exception_type()
{
var text = ToolErrorText.Describe("hole", new TimeoutException(""));
Assert.Contains("TimeoutException", text, StringComparison.Ordinal);
}
[Fact]
public void Missing_tool_name_still_yields_a_readable_message()
{
var text = ToolErrorText.Describe(null, new ArgumentException("Радиус должен быть > 0."));
Assert.Contains("Радиус должен быть > 0.", text, StringComparison.Ordinal);
Assert.DoesNotContain("«»", text, StringComparison.Ordinal);
}
}