Files
mikhail 8afad88241 test: per-test очистка документов через IntegrationTestBase (CloseAllAsync)
Базовый класс IntegrationTestBase (IAsyncLifetime) закрывает все открытые
документы КОМПАС после КАЖДОГО теста (DocumentService.CloseAllAsync) — чтобы не
копились вкладки-пустышки и упавший Assert не оставлял открытый документ. Все
интеграционные тест-классы переведены на него (: base(fx)); индивидуальные
finally CloseAsync сохранены как первичная очистка.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:38:48 +03:00

250 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;
using Kompas.Mcp.Core.Vision;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>Интеграция: построение 3D (эскиз → выдавливание) и снимок.</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class ModelingTests : IntegrationTestBase
{
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly SnapshotService _snap;
private readonly QueryService _query;
public ModelingTests(KompasFixture fx) : base(fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
_snap = new SnapshotService(fx.Session, fx.Dispatcher);
_query = new QueryService(fx.Session, fx.Dispatcher);
}
[Fact]
public async Task Build_cylinder_then_cut_hole_and_snapshot()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Эскиз 1: окружность R20 на XOY → выдавить boss на 40 (цилиндр).
var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s1, 0, 0, 20);
await _modeler.CloseSketchAsync(s1);
var boss = await _modeler.ExtrudeAsync(s1, depth: 40);
Assert.True(boss > 0);
// Эскиз 2: окружность R8 на XOY → вырезать насквозь (отверстие).
var s2 = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s2, 0, 0, 8);
await _modeler.CloseSketchAsync(s2);
var cut = await _modeler.ExtrudeAsync(s2, depth: 40, cut: true, throughAll: true);
Assert.True(cut > 0);
await _modeler.RebuildAsync();
var path = TestPaths.NewFile(".png");
var snap = await _snap.CaptureActiveModelAsync(saveToFile: path);
Assert.True(snap.Bytes.Length > 1000, "Снимок цилиндра подозрительно мал.");
Assert.True(File.Exists(path));
}
finally
{
await _docs.CloseAsync(save: false);
}
}
[Fact]
public async Task Sketch_on_face_adds_boss_on_top()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Цилиндр R10×H20 на XOY → верхняя грань на Z=20.
var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s1, 0, 0, 10);
await _modeler.CloseSketchAsync(s1);
await _modeler.ExtrudeAsync(s1, depth: 20);
// Запрос ДО достройки — проверяем, что повторный запрос увидит изменения
// (МЦХ не должна «залипнуть» на устаревшем значении).
var before = await _query.GetPartInfoAsync();
// Эскиз на верхней грани (точка (0,0,20)) → штифт R5×H10 наружу.
var s2 = await _modeler.OpenSketchOnFaceAsync(0, 0, 20);
await _modeler.AddCircleAsync(s2, 0, 0, 5);
await _modeler.CloseSketchAsync(s2);
var boss = await _modeler.ExtrudeAsync(s2, depth: 10);
Assert.True(boss > 0);
await _modeler.RebuildAsync();
var after = await _query.GetPartInfoAsync();
// Прирост объёма ≈ объём штифта π·5²·10 ≈ 785.4 мм³.
var deltaExpected = Math.PI * 25 * 10;
Assert.InRange(after.Volume - before.Volume, deltaExpected * 0.9, deltaExpected * 1.1);
}
finally
{
await _docs.CloseAsync(save: false);
}
}
[Fact]
public async Task Revolve_rectangle_makes_tube()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Профиль-прямоугольник [10..15]×[0..20] + осевая по локальной оси Y (x=0).
// Вращение 360° вокруг оси → труба: внеш. R15, внутр. R10, высота 20.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddAxisAsync(s, 0, 0, 0, 20);
await _modeler.AddRectangleAsync(s, 10, 0, 15, 20);
await _modeler.CloseSketchAsync(s);
var rev = await _modeler.RevolveAsync(s, angle: 360);
Assert.True(rev > 0);
await _modeler.RebuildAsync();
var info = await _query.GetPartInfoAsync();
// Объём трубы = π·(15² − 10²)·20 = π·2500 ≈ 7853.98 мм³.
var expected = Math.PI * (15 * 15 - 10 * 10) * 20;
Assert.InRange(info.Volume, expected * 0.97, expected * 1.03);
}
finally
{
await _docs.CloseAsync(save: false);
}
}
[Fact]
public async Task Fillet_and_chamfer_edges()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Цилиндр R10×H20 → верхнее ребро на (10,0,20), нижнее на (10,0,0).
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s, 0, 0, 10);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 20);
var cylinderVolume = (await _query.GetPartInfoAsync()).Volume;
// Скругление верхнего ребра R3 и фаска нижнего 2×2.
var fil = await _modeler.FilletEdgeAsync(10, 0, 20, 3);
Assert.True(fil > 0);
var cha = await _modeler.ChamferEdgeAsync(10, 0, 0, 2);
Assert.True(cha > 0);
await _modeler.RebuildAsync();
var path = TestPaths.NewFile("_fillet.png");
var snap = await _snap.CaptureActiveModelAsync(saveToFile: path);
Assert.True(snap.Bytes.Length > 1000);
// Скругление и фаска снимают материал с рёбер → объём уменьшается, но незначительно.
var after = (await _query.GetPartInfoAsync()).Volume;
Assert.True(after < cylinderVolume, "После скругления/фаски объём должен уменьшиться.");
Assert.InRange(after, cylinderVolume * 0.9, cylinderVolume);
}
finally
{
await _docs.CloseAsync(save: false);
}
}
[Fact]
public async Task List_faces_and_sketch_by_index()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Цилиндр R10×H20: 3 грани — 2 плоские (торцы) + 1 цилиндрическая (бок).
var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s1, 0, 0, 10);
await _modeler.CloseSketchAsync(s1);
await _modeler.ExtrudeAsync(s1, depth: 20);
var faces = await _query.ListFacesAsync();
Assert.Equal(3, faces.Count);
Assert.Equal(2, faces.Count(f => f.Type == "plane"));
Assert.Equal(1, faces.Count(f => f.Type == "cylinder"));
// Площадь торца ≈ π·10² ≈ 314 мм²; боковой ≈ 2π·10·20 ≈ 1257 мм².
var planar = faces.First(f => f.Type == "plane");
Assert.InRange(planar.Area, Math.PI * 100 * 0.95, Math.PI * 100 * 1.05);
var side = faces.First(f => f.Type == "cylinder");
Assert.InRange(side.Area, 2 * Math.PI * 10 * 20 * 0.95, 2 * Math.PI * 10 * 20 * 1.05);
// Эскиз на плоской грани по индексу → бобышка (проверяем, что выбор по индексу работает).
var s2 = await _modeler.OpenSketchOnFaceIndexAsync(planar.Index);
await _modeler.AddCircleAsync(s2, 0, 0, 4);
await _modeler.CloseSketchAsync(s2);
var boss = await _modeler.ExtrudeAsync(s2, depth: 8, forward: false);
Assert.True(boss > 0);
await _modeler.RebuildAsync();
// Модель осталась валидной и опрашиваемой.
Assert.True((await _query.GetPartInfoAsync()).Volume > 0);
}
finally
{
await _docs.CloseAsync(save: false);
}
}
[Fact]
public async Task List_edges_and_fillet_by_index()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Цилиндр R10×H20: торцевые рёбра — две окружности длиной 2π·10 ≈ 62.83 мм.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s, 0, 0, 10);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 20);
var edges = await _query.ListEdgesAsync();
var circles = edges.Where(e => e.Type == "circle").ToList();
Assert.True(circles.Count >= 2, "У цилиндра ожидались минимум два круговых ребра (торцы).");
foreach (var c in circles)
Assert.InRange(c.Length, 2 * Math.PI * 10 * 0.95, 2 * Math.PI * 10 * 1.05);
// Скругление кругового ребра по индексу → объём уменьшается.
var before = (await _query.GetPartInfoAsync()).Volume;
var fil = await _modeler.FilletEdgeIndexAsync(circles[0].Index, 3);
Assert.True(fil > 0);
await _modeler.RebuildAsync();
var after = (await _query.GetPartInfoAsync()).Volume;
Assert.True(after < before, "После скругления ребра объём должен уменьшиться.");
Assert.InRange(after, before * 0.9, before);
}
finally
{
await _docs.CloseAsync(save: false);
}
}
[Fact]
public async Task Reset_invalidates_sketch_ids()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.ResetAsync();
// После сброса реестра старый id недействителен.
await Assert.ThrowsAsync<KeyNotFoundException>(
() => _modeler.AddCircleAsync(s, 0, 0, 5));
}
finally
{
await _docs.CloseAsync(save: false);
}
}
}