using SheetMe.Core.Catalog; namespace SheetMe.Core.Tests; /// /// DataTableField 배선 문자열의 파싱·조립·검증 테스트. /// /// 표본은 지어내지 않고 운영 DB 실측(--db-dtf)과 레거시 소스에서 그대로 가져왔다. /// 이 형식은 어겨도 런타임이 예외를 빈 catch 로 삼켜 아무 표시 없이 값이 안 채워지므로, /// 여기서 막지 못하면 잘못된 배선이 임상 화면까지 간다. /// [TestClass] public sealed class DataTableFieldSpecTests { /// 레거시 전용 편집기가 만드는 정본 형태 — 공백이 하나도 없다 [TestMethod] public void Parse_SelectForm_FromLegacyEditorOutput() { var spec = DataTableFieldSpec.Parse("mdt_CodDepUid.Select(\"\")(0).Item(\"CODSTRDTM\")"); Assert.IsNotNull(spec); Assert.AreEqual(DataTableFieldForm.Select, spec.Form); Assert.AreEqual("mdt_CodDepUid", spec.TableName); Assert.AreEqual(string.Empty, spec.Filter); Assert.AreEqual(0, spec.RowIndex); Assert.AreEqual("CODSTRDTM", spec.Field); } /// 운영 다수파(1,624건, 76%) — 손으로 쓴 Rows 형도 읽어야 한다 [TestMethod] public void Parse_RowsForm_TheMajorityInProduction() { var spec = DataTableFieldSpec.Parse("MDataTable2.Rows(0).Item(\"ALGALGTYP\")"); Assert.IsNotNull(spec); Assert.AreEqual(DataTableFieldForm.Rows, spec.Form); Assert.AreEqual("MDataTable2", spec.TableName); Assert.AreEqual(0, spec.RowIndex); Assert.AreEqual("ALGALGTYP", spec.Field); } /// /// 필드에는 SQL 별칭 표현식이 들어온다 — 식별자 화이트리스트로 막으면 실서식을 못 연다. /// 실측 출처: [900]CGCH [349]퇴원요약지. /// [TestMethod] public void Parse_FieldMayBeSqlExpression() { var spec = DataTableFieldSpec.Parse("MDataTable4.Select(\"\")(0).Item(\"EMDOPRDTE||'-'||EMDOPNAME\")"); Assert.IsNotNull(spec); Assert.AreEqual("EMDOPRDTE||'-'||EMDOPNAME", spec.Field); Assert.IsNull(spec.Validate()); } /// 필터가 있는 형태 [TestMethod] public void Parse_SelectForm_WithFilterAndRow() { var spec = DataTableFieldSpec.Parse("MDataTable1.Select(\"CODNAM='A'\")(2).Item(\"CODCOD\")"); Assert.IsNotNull(spec); Assert.AreEqual("CODNAM='A'", spec.Filter); Assert.AreEqual(2, spec.RowIndex); } /// /// 알아볼 수 없는 값은 null 이어야 한다 — 억지로 해석해 되쓰면 원문이 손상된다. /// "MDataTable1.Rows" 는 실제로 운영에 234건 있는 미완성 값이고, /// ")(" 는 레거시 편집기에서 아무것도 안 고르고 확인을 눌렀을 때 남는 찌꺼기다. /// [DataTestMethod] [DataRow("MDataTable1.Rows")] [DataRow(")(")] [DataRow("MDataTable1")] [DataRow("MDataTable1.Select(\"\")(0).Item(\"F\") ")] // 뒤 공백은 Trim 되지만 [DataRow("MDataTable1.Select(\"\")( 0 ).Item(\"F\")")] // 내부 공백은 파서가 못 읽는다 [DataRow("MDataTable1.select(\"\")(0).Item(\"F\")")] // 소문자 select — 런타임이 다른 가지로 샌다 public void Parse_ReturnsNull_ForShapesTheRuntimeCannotRead(string value) { var spec = DataTableFieldSpec.Parse(value); // 뒤 공백만 있는 경우는 Trim 후 정상이다 — 그 한 건만 예외로 허용한다 if (value.Trim() == "MDataTable1.Select(\"\")(0).Item(\"F\")") { Assert.IsNotNull(spec); return; } Assert.IsNull(spec, $"해석돼서는 안 되는 값이 해석됐습니다: {value}"); } /// 조립 결과가 다시 같은 값으로 읽혀야 한다 [DataTestMethod] [DataRow(DataTableFieldForm.Select)] [DataRow(DataTableFieldForm.Rows)] public void Compose_RoundTrips(DataTableFieldForm form) { var spec = new DataTableFieldSpec { Form = form, TableName = "MDataTable3", Filter = "CODCOD='X'", RowIndex = 1, Field = "CODNAM", }; Assert.IsTrue(spec.RoundTrips(), spec.Compose()); } /// 조립에는 공백이 들어가지 않는다 — 레거시 파서가 공백을 못 넘긴다 [TestMethod] public void Compose_ContainsNoSpaces_AroundSyntax() { var spec = new DataTableFieldSpec { TableName = "MDataTable1", RowIndex = 0, Field = "F" }; Assert.AreEqual("MDataTable1.Select(\"\")(0).Item(\"F\")", spec.Compose()); } /// Rows 형은 필터를 쓰지 않는다 — 값에 섞여 나가면 안 된다 [TestMethod] public void Compose_RowsForm_DropsFilter() { var spec = new DataTableFieldSpec { Form = DataTableFieldForm.Rows, TableName = "MDataTable1", Filter = "무시돼야 한다", RowIndex = 3, Field = "F", }; Assert.AreEqual("MDataTable1.Rows(3).Item(\"F\")", spec.Compose()); } /// 런타임을 조용히 깨뜨리는 값은 저장 전에 막는다 [TestMethod] public void Validate_RejectsQuoteInFilter() { var spec = new DataTableFieldSpec { TableName = "MDataTable1", Filter = "CODNAM=\"A\"", RowIndex = 0, Field = "F", }; Assert.IsNotNull(spec.Validate()); } /// 이름에 마침표가 있으면 런타임이 첫 조각만 떼어내 영영 못 찾는다 [TestMethod] public void Validate_RejectsDotInTableName() { var spec = new DataTableFieldSpec { TableName = "a.b", RowIndex = 0, Field = "F" }; Assert.IsNotNull(spec.Validate()); } /// /// 같은 서식에 없는 데이터소스를 가리키면 막는다 — 대소문자까지 맞아야 한다. /// 실측에서 이런 값이 20건 있었고 전부 조용히 실패한다. /// [TestMethod] public void Validate_RejectsTableMissingFromThisSheet() { var spec = new DataTableFieldSpec { TableName = "MDataTable2", RowIndex = 0, Field = "F" }; Assert.IsNotNull(spec.Validate(new[] { "MDataTable3" })); Assert.IsNull(spec.Validate(new[] { "MDataTable2" })); // 대소문자가 다르면 런타임 Hashtable 조회가 실패한다 Assert.IsNotNull(spec.Validate(new[] { "mdatatable2" })); } /// 필드가 비면 저장할 수 없다 [TestMethod] public void Validate_RequiresField() { var spec = new DataTableFieldSpec { TableName = "MDataTable1", RowIndex = 0, Field = string.Empty }; Assert.IsNotNull(spec.Validate()); } }