test: add workbench tests and MVP documentation

This commit is contained in:
codex
2026-07-30 13:19:04 +03:00
parent 86fb8c89d2
commit 3f2fbe9883
12 changed files with 116 additions and 0 deletions
+9
View File
@@ -1,2 +1,11 @@
/link/ /link/
/link /link
# DocumentAutomation generated files and local source data
DocumentAutomation/**/bin/
DocumentAutomation/**/obj/
DocumentAutomation/.vs/
DocumentAutomation/samples/**/*.docx
DocumentAutomation/samples/**/*.pdf
DocumentAutomation/samples/**/*.json
DocumentAutomation/**/*.csproj.user
+15
View File
@@ -0,0 +1,15 @@
# DocumentAutomation
Первый локальный прототип состоит из независимых `DOCX Workbench` и `Standards Workbench` на .NET 8 Windows Forms.
DOCX Workbench анализирует DOCX через Open XML, формирует документ через локальный Microsoft Word COM, сохраняет шаблонные секции/колонтитулы, обновляет поля и экспортирует PDF. Standards Workbench импортирует PDF, сохраняет checksum, извлекает страницы и кандидатные пункты, обращается к Ollama только за предложением candidate-rule и экспортирует переносимый профиль.
## Ограничения MVP
- OCR только обозначен интерфейсом; страницы без текстового слоя получают статус `OCR_REQUIRED`.
- Автоматическое утверждение нормативных правил запрещено.
- Word COM требует установленного Microsoft Word и запускается только интерактивно, не как service.
- Реальный `156-титул-ПД.docx` и нормативные PDF не добавлялись в Git.
- PDF preview использует локальный Word ExportAsFixedFormat.
Подробности: `docs/architecture.md`, `docs/docx-template-contract.md`, `docs/word-com-lifecycle.md`, `docs/standards-rule-model.md`.
+9
View File
@@ -0,0 +1,9 @@
# Architecture
`DocxWorkbench.App` and `StandardsWorkbench.App` are separate WinForms applications. They share only DTOs in `DocumentAutomation.Shared`.
DOCX flow: GUI -> `DocxWorkbench.Core` contracts -> `DocxWorkbench.OpenXml` read-only inspection or `DocxWorkbench.WordInterop` sequential Word COM queue -> DOCX/PDF and technical JSON log.
Standards flow: GUI -> `StandardsWorkbench.Pdf` page/clause extraction and `StandardsWorkbench.Ollama` candidate-only suggestions -> manually approved portable profile -> `StandardsWorkbench.Core.RulesEngine`.
COM is isolated from Core and UI. Standards rules are not hardcoded into document generation.
+9
View File
@@ -0,0 +1,9 @@
# Development
```powershell
dotnet restore .\DocumentAutomation\DocumentAutomation.sln
dotnet build .\DocumentAutomation\DocumentAutomation.sln --configuration Release
dotnet test .\DocumentAutomation\DocumentAutomation.sln --configuration Release
```
Run `DocxWorkbench.App` or `StandardsWorkbench.App` from Visual Studio/VS Code. Microsoft Word is required only for DOCX generation and PDF export. Ollama is configured at `http://192.168.200.112:11434`; the model must be selected from `/api/tags`.
@@ -0,0 +1,5 @@
# DOCX template contract
The preferred insertion point is a Content Control with tag or title `DOC_BODY`, then a bookmark named `DOC_BODY`, then the exact marker `{{DOC_BODY}}`. If none is present, generation fails explicitly; the body is never silently appended.
The template is copied to a temporary working file. Body headers/footers are not imported. Template sections and headers/footers are retained, fields are updated, and Word repaginates before saving.
@@ -0,0 +1,5 @@
# Standards rule model
PDF metadata includes SHA-256, page count, text-layer status and import time. Pages without text are marked `OCR_REQUIRED`; no text is invented. Clause extraction preserves clause number, text, page and confidence.
Ollama receives only a small clause context and may return a candidate JSON rule. Candidates remain `New` until human approval. Only `Approved`, `Implemented` or `Tested` rules are evaluated. Profiles contain manifest, approved rules, references and a signature hash.
@@ -0,0 +1,5 @@
# Word COM lifecycle
Word is started in a dedicated task with `Visible=false` and alerts disabled. The service opens a working copy, performs one operation, updates fields, repaginates, saves, closes the Document, quits the Application, and releases owned COM objects in `finally`.
The prototype does not run as a Windows Service, does not kill unrelated `WINWORD.EXE` processes, and does not execute macros. Production hardening should add an explicit serial queue and PID-scoped orphan check.
View File
+3
View File
@@ -0,0 +1,3 @@
# Samples
Реальные проектные DOCX/PDF и нормативные документы сюда не добавляются. Тесты создают синтетические документы во временном каталоге. Для локальной проверки укажите входные файлы через GUI.
@@ -0,0 +1,10 @@
namespace DocumentAutomation.IntegrationTests;
public sealed class WordComIntegrationTests
{
[Fact(Skip = "Requires an interactive Microsoft Word installation; not run automatically in the repository validation pass.")]
public void BuildsDocxAndExportsPdfThroughWordCom()
{
Assert.Fail("Run this test manually on a Windows workstation with Word installed and a synthetic template/body fixture.");
}
}
@@ -0,0 +1,20 @@
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocxWorkbench.OpenXml;
namespace DocxWorkbench.Tests;
public sealed class DocumentInspectorTests
{
[Fact]
public void InspectsSyntheticDocxStructure()
{
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".docx");
using (var doc = WordprocessingDocument.Create(path, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
{
var main = doc.AddMainDocumentPart(); main.Document = new Document(new Body(new Paragraph(new Run(new Text("{{DOC_BODY}}"))), new Table(new TableRow(new TableCell(new Paragraph(new Run(new Text("x")))))))); main.Document.Save();
}
try { var result = new DocumentInspector().Inspect(path); Assert.Equal(1, result.Tables); Assert.True(result.Paragraphs >= 2); Assert.True(result.HasPageFields is false); }
finally { File.Delete(path); }
}
}
@@ -0,0 +1,26 @@
using DocumentAutomation.Shared;
using StandardsWorkbench.Core;
namespace StandardsWorkbench.Tests;
public sealed class RulesEngineTests
{
[Fact]
public void OnlyApprovedRulesAreEvaluated()
{
var document = new DocumentInspectionResult { FilePath = "synthetic.docx", ContentControls = 1, Sections = 1, Paragraphs = 2, Tables = 0 };
var profile = new ApprovedRulesProfile { StandardId = "TEST", Code = "TEST", Revision = "1", Rules = [
new CandidateRule { CandidateId = "approved", StandardId = "TEST", ClauseId = "1", SourceText = "x", Title = "x", RuleKind = "ContentControlPresence", Status = "Approved" },
new CandidateRule { CandidateId = "new", StandardId = "TEST", ClauseId = "2", SourceText = "x", Title = "x", RuleKind = "SectionCount", Status = "New" }
], SourceFileSha256 = "test"};
var result = new RulesEngine().Evaluate(document, profile); Assert.Single(result); Assert.Equal(RuleEvaluationStatus.Pass, result[0].Status);
}
[Fact]
public void FailedRuleHasClauseReference()
{
var document = new DocumentInspectionResult { FilePath = "synthetic.docx", ContentControls = 0, Sections = 1, Paragraphs = 1, Tables = 0 };
var rule = new CandidateRule { CandidateId = "cc", StandardId = "TEST", ClauseId = "6.3", SourcePage = 4, SourceText = "required", Title = "cc", RuleKind = "ContentControlPresence", Status = "Tested" };
var result = Assert.Single(new RulesEngine().Evaluate(document, new ApprovedRulesProfile { StandardId = "TEST", Code = "TEST", Revision = "1", SourceFileSha256 = "test", Rules = [rule] })); Assert.Equal(RuleEvaluationStatus.Fail, result.Status); Assert.Equal("6.3", result.ClauseId); Assert.Equal(4, result.SourcePage);
}
}