Files
kompas3d-mcp/tests/Kompas.Mcp.Tests/SketchEntityInputsTests.cs
T
mikhail 4e3d367637
ci / build (push) Successful in 28s
Каталог инструментов сжат под агента: 84 → 56
Инструменты были нарезаны по способу вызова, а не по смыслу: четыре отверстия,
двойники *_index, одиннадцать sketch_add_*. Агент платил за это дважды — 40 КБ
описаний в каждой сессии и лишние round-trip'ы, а каждый вызов это ещё и шанс
сбиться. Теперь инструмент называет ОПЕРАЦИЮ, вариант задаётся параметром,
объект выбирается индексом или точкой одним и тем же инструментом.

Проверка построения приходит сама. Каждая мутирующая операция дописывает к ответу
итог validate_part (AutoValidation): Create()/Update()==true не значит успех, а
правило навыка «проверяй после каждого шага» удваивало число вызовов. Выключается
через set_auto_validate или KOMPAS_MCP_AUTOVALIDATE=0 — сбой самой проверки уходит
в примечание и никогда не превращает удачную операцию в ошибку.

Эскиз: 16 инструментов → 3. sketch_create(plane|faceIndex|x,y,z, entities[],
autoClose) строит контур целиком; пакет выполняется за ОДИН заход на STA-поток
(PartModeler.AddEntitiesAsync), ошибка называет позицию примитива в списке.

Слияния: hole(type=simple|counterbore|countersink|conic), extrude/revolve(mode),
pattern(kind), mirror (без featureIds — всё тело), document_save(path?),
set_variable как upsert (разведочный вызов «есть ли такая» больше не нужен),
list_faces/list_edges(index?) вместо отдельных describe_*, get_part_info и
get_bounding_box — в describe_model(sections), где незапрошенные разделы вообще
не читаются из модели.

Селектор index|point: fillet_edge/chamfer_edge принимают edgeIndices списком —
одна операция дерева на все рёбра; для операций API7, умеющих только точку,
индекс переводится в точку через ModelInspectionService.FaceCenterPointAsync
(середина параметрической области грани).

Схема слитого инструмента не запрещает неверную комбинацию полей — это делает
валидация, и её сообщение называет type и недостающий параметр.

Тесты: 291 unit (+41) и 136 integration (+5), интеграционные — на живом КОМПАС.
Новые проверяют ровно рискованные места: точка-из-индекса лежит на грани и по ней
создаётся эскиз, мульти-ребёрное скругление даёт один узел дерева и убыль объёма,
upsert создаёт и затем меняет переменную с формулой, пакет сообщает позицию сбоя.
PluginSkillsTests теперь падает, если в публикуемом навыке всплывёт слитое имя.
2026-07-31 11:52:30 +03:00

122 lines
4.4 KiB
C#

using Kompas.Mcp.Core.Modeling;
namespace Kompas.Mcp.Tests;
/// <summary>
/// Разбор пакета примитивов эскиза. Схема слитого инструмента не может запретить неверную
/// комбинацию полей — её ловит этот разбор, и сообщение должно называть недостающий параметр
/// и позицию в списке, иначе агент не исправится с первой попытки.
/// </summary>
[Trait("Category", "Unit")]
public sealed class SketchEntityInputsTests
{
private static SketchEntityInput Circle() => new()
{
Type = "circle", CenterX = 0, CenterY = 0, Radius = 5,
};
[Fact]
public void Empty_input_gives_empty_list()
{
Assert.Empty(SketchEntityInputs.Parse(null));
Assert.Empty(SketchEntityInputs.Parse([]));
}
[Theory]
[InlineData("line", SketchEntityKind.Line)]
[InlineData("LINE", SketchEntityKind.Line)]
[InlineData(" rect ", SketchEntityKind.Rectangle)]
[InlineData("rectangle", SketchEntityKind.Rectangle)]
[InlineData("arc3", SketchEntityKind.Arc3Points)]
[InlineData("arc3points", SketchEntityKind.Arc3Points)]
[InlineData("axis", SketchEntityKind.Axis)]
public void Type_names_and_aliases_are_recognized(string type, SketchEntityKind expected)
{
var e = new SketchEntityInput
{
Type = type,
Points = [new SketchPoint(0, 0), new SketchPoint(10, 10), new SketchPoint(20, 0)],
};
Assert.Equal(expected, SketchEntityInputs.Parse([e])[0].Kind);
}
[Fact]
public void Unknown_type_lists_the_allowed_ones()
{
var e = new SketchEntityInput { Type = "spiral" };
var ex = Assert.Throws<ArgumentException>(() => SketchEntityInputs.Parse([e]));
Assert.Contains("spiral", ex.Message, StringComparison.Ordinal);
Assert.Contains("polyline", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void Error_names_the_position_in_the_batch()
{
var bad = new SketchEntityInput { Type = "circle", CenterX = 0, CenterY = 0 }; // без radius
var ex = Assert.Throws<ArgumentException>(() => SketchEntityInputs.Parse([Circle(), Circle(), bad]));
Assert.Contains("entities[2]", ex.Message, StringComparison.Ordinal);
Assert.Contains("radius", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void Line_requires_two_points()
{
var e = new SketchEntityInput { Type = "line", Points = [new SketchPoint(0, 0)] };
var ex = Assert.Throws<ArgumentException>(() => SketchEntityInputs.Parse([e]));
Assert.Contains("points", ex.Message, StringComparison.Ordinal);
Assert.Contains("минимум 2", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void Arc_requires_center_radius_and_angles()
{
var e = new SketchEntityInput { Type = "arc", CenterX = 0, CenterY = 0, Radius = 10, StartAngle = 0 };
var ex = Assert.Throws<ArgumentException>(() => SketchEntityInputs.Parse([e]));
Assert.Contains("endAngle", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void Polygon_requires_vertex_count()
{
var e = new SketchEntityInput { Type = "polygon", CenterX = 0, CenterY = 0, Radius = 10 };
var ex = Assert.Throws<ArgumentException>(() => SketchEntityInputs.Parse([e]));
Assert.Contains("vertexCount", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void Defaults_are_applied_for_omitted_flags()
{
var e = new SketchEntityInput { Type = "arc", CenterX = 1, CenterY = 2, Radius = 3, StartAngle = 0, EndAngle = 90 };
var parsed = SketchEntityInputs.Parse([e])[0];
Assert.True(parsed.CounterClockwise); // умолчание — против часовой
Assert.True(parsed.Inscribed);
Assert.False(parsed.Closed);
Assert.Equal(0, parsed.Angle);
}
[Fact]
public void Points_keep_their_order()
{
var e = new SketchEntityInput
{
Type = "polyline",
Points = [new SketchPoint(0, 0), new SketchPoint(10, 0), new SketchPoint(10, 5)],
Closed = true,
};
var parsed = SketchEntityInputs.Parse([e])[0];
Assert.True(parsed.Closed);
Assert.Equal(3, parsed.Points.Count);
Assert.Equal((10.0, 5.0), parsed.Points[2]);
}
}