Files
SheetMe/tests/SheetMe.Core.Tests/LegacyXmlRoundTripTests.cs
MsystechandClaude Fable 5 16c07f48dc 초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)
레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너.
기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49.

이 커밋에 함께 포함된 자격증명 분리:
- appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환
- 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외,
  csproj Debug 조건부 복사라 Release 산출물에 실리지 않음)
- ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경,
  미치환 플레이스홀더는 '미설정'으로 간주

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:20:22 +09:00

166 lines
7.6 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 SheetMe.Core.Models;
using SheetMe.Core.Serialization;
namespace SheetMe.Core.Tests;
/// <summary>
/// 레거시 XML 왕복 무결성 테스트 — P0 게이트.
/// 실서식 샘플(sheetdesign_sample.xml)을 Read→Write 했을 때 의미론 diff 가 0 이어야 한다.
/// </summary>
[TestClass]
public sealed class LegacyXmlRoundTripTests
{
private static string SamplePath
=> Path.Combine(AppContext.BaseDirectory, "Assets", "sheetdesign_sample.xml");
/// <summary>실서식 샘플 왕복 — 의미론 diff 0</summary>
[TestMethod]
public void RoundTrip_RealSample_NoSemanticDiff()
{
var original = File.ReadAllText(SamplePath);
var serializer = new LegacyXmlSerializer();
var document = serializer.Read(original);
var rewritten = serializer.Write(document);
var diffs = XmlSemanticDiff.Compare(original, rewritten);
Assert.AreEqual(0, diffs.Count,
"왕복 의미론 diff 발생:\n" + string.Join("\n", diffs));
}
/// <summary>실서식 샘플 이중 왕복 — Write 출력이 안정(고정점)이어야 한다</summary>
[TestMethod]
public void RoundTrip_RealSample_SecondPassIsStable()
{
var original = File.ReadAllText(SamplePath);
var serializer = new LegacyXmlSerializer();
var first = serializer.Write(serializer.Read(original));
var second = serializer.Write(serializer.Read(first));
Assert.AreEqual(first, second, "이중 왕복 결과가 문자열 수준에서 안정적이지 않습니다.");
}
/// <summary>실서식 샘플 구조 확인 — 페이지/컨트롤 수·타입 매핑</summary>
[TestMethod]
public void Read_RealSample_ParsesStructure()
{
var serializer = new LegacyXmlSerializer();
var document = serializer.Read(File.ReadAllText(SamplePath));
Assert.AreEqual(1, document.Pages.Count, "샘플은 단일 페이지여야 합니다.");
var page = document.Pages[0];
Assert.AreEqual("MDesignerHost1", page.Root.Id);
Assert.AreEqual(823, page.Width, "루트 Size 리프트(823×1250) 실패");
Assert.AreEqual(1250, page.Height);
// 샘플 실측: MLine 34, TextBox 33, Label 27, RadioButton 15, Panel 6, MSequence 1, MPictureBox 1
var allControls = Flatten(page.Controls).ToList();
Assert.AreEqual(117, allControls.Count, "전체 컨트롤 수(루트 제외) 불일치");
Assert.AreEqual(34, allControls.Count(c => c.Type == "Line"));
Assert.AreEqual(33, allControls.Count(c => c.Type == "TextBox"));
Assert.AreEqual(28, allControls.Count(c => c.Type == "Label"), "Label 27 + MSequence 1 = 28");
Assert.AreEqual(15, allControls.Count(c => c.Type == "RadioButton"));
Assert.AreEqual(6, allControls.Count(c => c.Type == "Panel"));
Assert.AreEqual(1, allControls.Count(c => c.Type == "PictureBox"));
Assert.AreEqual(0, allControls.Count(c => c.Type == LegacyTypeCatalog.PlaceholderType),
"샘플에는 미지원 타입이 없어야 합니다.");
}
/// <summary>다중 페이지 병합 규약 — Sheet 루트 1개에 MDesignerHost Object N개</summary>
[TestMethod]
public void RoundTrip_MultiPage_PreservesPages()
{
const string xml =
"<Sheet name=\"SheetDesignInfo\">" +
"<Object type=\"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"MDesignerHost1\" children=\"Controls\" DisplaySequence=\"0\">" +
"<Property name=\"Size\">720, 856</Property>" +
"</Object>" +
"<Object type=\"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"MDesignerHost1\" children=\"Controls\" DisplaySequence=\"0\">" +
"<Property name=\"Size\">720, 856</Property>" +
"</Object>" +
"</Sheet>";
var serializer = new LegacyXmlSerializer();
var document = serializer.Read(xml);
Assert.AreEqual(2, document.Pages.Count);
var rewritten = serializer.Write(document);
var diffs = XmlSemanticDiff.Compare(xml, rewritten);
Assert.AreEqual(0, diffs.Count, string.Join("\n", diffs));
}
/// <summary>신규 컨트롤 추가 후 쓰기 — Location/Size/AQN 이 레거시 형식으로 생성</summary>
[TestMethod]
public void Write_NewControl_EmitsLegacyFormat()
{
var serializer = new LegacyXmlSerializer();
var document = new FormDocument();
var page = LegacyXmlSerializer.CreateEmptyPage(1);
document.Pages.Add(page);
var textBox = new ControlElement
{
Type = "TextBox",
Id = "TextBox1",
Bounds = new LayoutRect { X = 120, Y = 88, W = 200, H = 24 },
};
textBox.Props.SetText("Text", "환자명");
page.Controls.Add(textBox);
var xml = serializer.Write(document);
StringAssert.Contains(xml, "M.EMR.UserControl.TextBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
StringAssert.Contains(xml, "<Property name=\"Location\">120, 88</Property>");
StringAssert.Contains(xml, "<Property name=\"Size\">200, 24</Property>");
StringAssert.Contains(xml, "<Property name=\"Text\">환자명</Property>");
// 재읽기 시 동일 구조
var reread = serializer.Read(xml);
Assert.AreEqual(1, reread.Pages[0].Controls.Count);
Assert.AreEqual("TextBox", reread.Pages[0].Controls[0].Type);
Assert.AreEqual(120, reread.Pages[0].Controls[0].Bounds.X);
}
/// <summary>컨테이너 중첩 시 LocationOnBase 재계산 — 절대좌표(부모 누적) 규약</summary>
[TestMethod]
public void Write_NestedControl_RecomputesLocationOnBase()
{
const string xml =
"<Sheet name=\"SheetDesignInfo\">" +
"<Object type=\"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"MDesignerHost1\" children=\"Controls\" DisplaySequence=\"0\">" +
"<Object type=\"M.EMR.UserControl.Panel, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"Panel1\" children=\"Controls\" DisplaySequence=\"0\">" +
"<Object type=\"M.EMR.UserControl.TextBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"TextBox1\" children=\"Controls\" DisplaySequence=\"0\">" +
"<Property name=\"LocationOnBase\">0, 0</Property>" +
"<Property name=\"Location\">10, 20</Property>" +
"<Property name=\"Size\">100, 22</Property>" +
"</Object>" +
"<Property name=\"Location\">50, 60</Property>" +
"<Property name=\"Size\">200, 100</Property>" +
"</Object>" +
"<Property name=\"Size\">720, 856</Property>" +
"</Object>" +
"</Sheet>";
var serializer = new LegacyXmlSerializer();
var document = serializer.Read(xml);
var rewritten = serializer.Write(document);
// Panel(50,60) 안의 TextBox(10,20) → LocationOnBase = 60, 80 (레거시 writer 재계산 규약)
StringAssert.Contains(rewritten, "<Property name=\"LocationOnBase\">60, 80</Property>");
}
private static IEnumerable<ControlElement> Flatten(IEnumerable<ControlElement> controls)
{
foreach (var control in controls)
{
yield return control;
foreach (var child in Flatten(control.Children))
{
yield return child;
}
}
}
}