diff --git a/src/Kompas.Mcp.Core/Documents/DocumentService.cs b/src/Kompas.Mcp.Core/Documents/DocumentService.cs index c3f1363..f409288 100644 --- a/src/Kompas.Mcp.Core/Documents/DocumentService.cs +++ b/src/Kompas.Mcp.Core/Documents/DocumentService.cs @@ -64,6 +64,26 @@ public sealed class DocumentService => _dispatcher.InvokeAsync(() => WithActive(d => d.Close(save ? DocumentCloseOptions.kdSaveChanges : DocumentCloseOptions.kdDoNotSaveChanges)), ct); + /// + /// Закрыть ВСЕ открытые документы без сохранения; возвращает число закрытых. Применяется для + /// очистки между тестами, чтобы не накапливались вкладки-пустышки. Закрываем активный документ + /// в цикле (после закрытия активным становится следующий) с защитой от зацикливания: если очередной + /// документ закрыть не удалось, прекращаем, чтобы не крутить впустую на одном и том же. + /// + public Task CloseAllAsync(CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + { + int closed = 0; + for (int guard = 0; guard < 256; guard++) + { + var doc = _session.Application.ActiveDocument; + if (doc is null) break; + try { doc.Close(DocumentCloseOptions.kdDoNotSaveChanges); closed++; } + finally { ComHelper.Release(doc); } + } + return closed; + }, ct); + private void WithActive(Action action) => WithActive(d => { action(d); return true; }); diff --git a/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs b/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs index 0c28a0b..945ce61 100644 --- a/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: вставка компонента в сборку (API7 IParts7.AddFromFile + Placement). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class AssemblyTests +public sealed class AssemblyTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly AssemblyService _assembly; private readonly QueryService _query; - public AssemblyTests(KompasFixture fx) + public AssemblyTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs b/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs index 2d0da18..99d91d0 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs @@ -3,11 +3,11 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: подключение к КОМПАС. Требует установленный КОМПАС-3D. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ConnectionTests +public sealed class ConnectionTests : IntegrationTestBase { private readonly KompasFixture _fx; - public ConnectionTests(KompasFixture fx) => _fx = fx; + public ConnectionTests(KompasFixture fx) : base(fx) => _fx = fx; [Fact] public void Connects_to_kompas() diff --git a/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs b/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs index e49ffb2..a7db39b 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: импорт/экспорт STEP и обход компонентов сборки. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ConversionTests +public sealed class ConversionTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly ConversionService _conv; private readonly QueryService _query; - public ConversionTests(KompasFixture fx) + public ConversionTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs b/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs index dc803ab..0d0d688 100644 --- a/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs @@ -5,11 +5,11 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: жизненный цикл документов. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class DocumentTests +public sealed class DocumentTests : IntegrationTestBase { private readonly DocumentService _docs; - public DocumentTests(KompasFixture fx) + public DocumentTests(KompasFixture fx) : base(fx) => _docs = new DocumentService(fx.Session, fx.Dispatcher); [Fact] diff --git a/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs b/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs index 94cb163..87aa6b1 100644 --- a/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: прямое редактирование — перемещение грани (FaceMover). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class FaceEditTests +public sealed class FaceEditTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly FaceEditService _faceEdit; private readonly QueryService _query; - public FaceEditTests(KompasFixture fx) + public FaceEditTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs b/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs index 4d01397..805398f 100644 --- a/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: формообразующие операции пакетов B и C (массивы, зеркало). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class FeatureOpsTests +public sealed class FeatureOpsTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly QueryService _query; - public FeatureOpsTests(KompasFixture fx) + public FeatureOpsTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs b/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs index c2eac0c..3cedbff 100644 --- a/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs @@ -7,14 +7,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: операция «Отверстие» (API7 IHoles3D/IHole3D). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class HoleTests +public sealed class HoleTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly HoleService _holes; private readonly QueryService _query; - public HoleTests(KompasFixture fx) + public HoleTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs b/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs index 3eaa16f..30eabcf 100644 --- a/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: структурное «зрение» — дерево, топология, drill-down, измерения. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class InspectionTests +public sealed class InspectionTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly ModelInspectionService _inspect; - public InspectionTests(KompasFixture fx) + public InspectionTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/IntegrationTestBase.cs b/tests/Kompas.Mcp.Tests/Integration/IntegrationTestBase.cs new file mode 100644 index 0000000..cc9e527 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/IntegrationTestBase.cs @@ -0,0 +1,21 @@ +using Kompas.Mcp.Core.Documents; + +namespace Kompas.Mcp.Tests.Integration; + +/// +/// Базовый класс интеграционных тестов: после КАЖДОГО теста закрывает все открытые документы +/// КОМПАС без сохранения, чтобы не копились вкладки-пустышки (и чтобы упавший Assert не оставлял +/// открытый документ). xUnit создаёт новый экземпляр тест-класса на каждый тест-метод, поэтому +/// вызывается per-test — это и есть точка очистки. +/// +public abstract class IntegrationTestBase : IAsyncLifetime +{ + private readonly DocumentService _cleanupDocs; + + protected IntegrationTestBase(KompasFixture fx) + => _cleanupDocs = new DocumentService(fx.Session, fx.Dispatcher); + + public Task InitializeAsync() => Task.CompletedTask; + + public Task DisposeAsync() => _cleanupDocs.CloseAllAsync(); +} diff --git a/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs b/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs index 4f5d3b4..914ba0c 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: построение 3D (эскиз → выдавливание) и снимок. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ModelingTests +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) + public ModelingTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs b/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs index b390c1c..0ad095c 100644 --- a/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: массово-центровочные характеристики (get_part_info). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class QueryTests +public sealed class QueryTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly QueryService _query; - public QueryTests(KompasFixture fx) + public QueryTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs b/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs index e108e53..cb85ac0 100644 --- a/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: новые примитивы эскиза (профиль → выдавливание → объём). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class SketchPrimitivesTests +public sealed class SketchPrimitivesTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly QueryService _query; - public SketchPrimitivesTests(KompasFixture fx) + public SketchPrimitivesTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs b/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs index 1b8d00a..69d8b73 100644 --- a/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs @@ -6,12 +6,12 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: снимок модели в растр («зрение агента»). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class SnapshotTests +public sealed class SnapshotTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly SnapshotService _snap; - public SnapshotTests(KompasFixture fx) + public SnapshotTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _snap = new SnapshotService(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs b/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs index a66e46f..4f0d374 100644 --- a/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs @@ -9,7 +9,7 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: рассечение тела, перемещение тела, объединение (cut→spread→union). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class SolidEditTests +public sealed class SolidEditTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; @@ -18,7 +18,7 @@ public sealed class SolidEditTests private readonly QueryService _query; private readonly ValidationService _validation; - public SolidEditTests(KompasFixture fx) + public SolidEditTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs b/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs index 39cb0f0..c15f8b6 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: валидация построения (ошибки/перестроение операций). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ValidationTests +public sealed class ValidationTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly ValidationService _validation; - public ValidationTests(KompasFixture fx) + public ValidationTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs index 3e192dc..c13228a 100644 --- a/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs @@ -7,14 +7,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: переменные модели (параметрика, API5 ksVariable). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class VariableTests +public sealed class VariableTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly VariableService _vars; private readonly ModelInspectionService _inspect; - public VariableTests(KompasFixture fx) + public VariableTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher);