초기 커밋: 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>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user