using SheetMe.Core.Catalog; namespace SheetMe.Core.Tests; /// /// 치환 변수 형태 판정 테스트 — 실제 엔진 clsMDataTable.ConvertQuery 규약을 옮긴 것. /// /// 규약을 어기면 <<...>> 가 SQL 에 그대로 남아 ORA 오류가 나거나 /// 빈 문자열로 조용히 치환된다. 둘 다 화면에는 아무 표시가 없어 배포 후에야 드러난다. /// [TestClass] public sealed class QueryVariableResolveTests { private const string Patient = "M.CMM.HISOperatingInfo.bzPatientInfo"; private const string Sheet = "M.EMR.SheetLoadOperatingInfo.bzSheetInfo"; private const string Work = "M.CMM.HISOperatingInfo.bzWorkInfo"; /// 카탈로그에 실린 토큰은 전부 런타임이 해석할 수 있어야 한다 — 자기모순 방지 [TestMethod] public void EveryCatalogTokenIsResolvable() { foreach (var variable in LegacyQueryVariableCatalog.All) { Assert.IsTrue(LegacyQueryVariableCatalog.IsResolvable(variable.Token), $"카탈로그 토큰이 규약에 안 맞습니다: {variable.Token}"); } } /// 컬럼 채우기 틀은 NeedsColumn 표시가 있어야 편집기가 그 자리를 선택해 준다 [TestMethod] public void ColumnTemplatesAreFlagged() { foreach (var variable in LegacyQueryVariableCatalog.All) { var hasPlaceholder = variable.Token.Contains("컬럼명", StringComparison.Ordinal); Assert.AreEqual(hasPlaceholder, variable.NeedsColumn, $"NeedsColumn 표시가 토큰과 어긋납니다: {variable.Token}"); } } [DataTestMethod] [DataRow("ChtNum")] [DataRow("ComNum_Refer")] [DataRow("OemVar5")] public void ScalarPropertyResolves(string property) => Assert.IsTrue(LegacyQueryVariableCatalog.IsResolvable($"<<{Patient}.{property}>>")); /// /// DataRow 접근형 2가지가 모두 유효하다 — clsMDataTable.vb:113 정규식이 둘 다 받는다. /// 이 파일의 예전 주석은 '반드시 실패한다'고 했으나 그 근거였던 LastIndexOf(".") 줄은 /// 실제 엔진에서 주석 처리되어 있다(clsMDataTable.vb:32). /// [DataTestMethod] [DataRow("PatInfDR.item(\"PatNam\")")] [DataRow("PatInfDR(\"PatNam\")")] [DataRow("CowInfDR.item(\"CowWadCod\")")] public void DataRowAccessResolves(string path) => Assert.IsTrue(LegacyQueryVariableCatalog.IsResolvable($"<<{Patient}.{path}>>")); /// item 은 반드시 소문자 — 정규식에 IgnoreCase 가 없다 [DataTestMethod] [DataRow("PatInfDR.Item(\"PatNam\")")] [DataRow("PatInfDR.ITEM(\"PatNam\")")] public void UpperCaseItemDoesNotResolve(string path) => Assert.IsFalse(LegacyQueryVariableCatalog.IsResolvable($"<<{Patient}.{path}>>"), "item 을 대문자로 쓰면 런타임이 값을 못 찾고 빈 문자열로 치환한다"); /// 접두어를 줄여 쓰면 치환되지 않는다 — 가장 흔한 실수다 [DataTestMethod] [DataRow("<>")] [DataRow("<>")] [DataRow("<>")] public void ShortenedPrefixDoesNotResolve(string token) => Assert.IsFalse(LegacyQueryVariableCatalog.IsResolvable(token)); /// 클래스명 뒤에 점이 없으면 안 된다(접두어가 더 긴 이름의 앞부분인 경우 방지) [TestMethod] public void PrefixMustBeFollowedByDot() => Assert.IsFalse(LegacyQueryVariableCatalog.IsResolvable($"<<{Patient}Extra.ChtNum>>")); /// 중첩 경로는 정규식이 받지 않는다 — 한 단계만 허용한다 [TestMethod] public void NestedPathDoesNotResolve() => Assert.IsFalse(LegacyQueryVariableCatalog.IsResolvable($"<<{Patient}.PatientInfoBiz_Refer.ComNum>>")); [TestMethod] public void SheetAndWorkPrefixesResolve() { Assert.IsTrue(LegacyQueryVariableCatalog.IsResolvable($"<<{Sheet}.ShtCod>>")); Assert.IsTrue(LegacyQueryVariableCatalog.IsResolvable($"<<{Work}.WrkUid>>")); } [DataTestMethod] [DataRow("ChtNum")] [DataRow("< Assert.IsFalse(LegacyQueryVariableCatalog.IsResolvable(token)); /// /// 편집기가 실제로 쓰는 흐름 — 쿼리에서 뽑은 변수를 그대로 판정에 넘긴다. /// [TestMethod] public void TokenizerOutputFeedsResolverDirectly() { var sql = $"SELECT * FROM P_PatMst WHERE ChtNum = '<<{Patient}.ChtNum>>' " + $"AND Nam = '<<{Patient}.PatInfDR.item(\"PatNam\")>>' AND X = '<>'"; var bad = SqlTokenizer.VariablesIn(sql) .Where(v => !LegacyQueryVariableCatalog.IsResolvable(v)) .ToList(); CollectionAssert.AreEqual(new[] { "<>" }, bad.ToArray()); } }