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);