using SheetMe.Core.Catalog; namespace SheetMe.Core.Tests; /// /// MDataTable 쿼리 치환 고정 — 레거시 clsMDataTable.ConvertQuery 와 같은 SQL 이 나와야 한다. /// /// 한 칸이라도 다르면 미리보기가 운영과 다른 SQL을 돌린다. 그러면 "미리보기에서는 /// 나왔는데 실제로는 안 나온다"가 되고, 그건 미리보기가 없는 것보다 나쁘다 — /// 사람이 확인했다고 믿고 넘어간다. /// [TestClass] public sealed class QuerySubstitutionTests { #region Nested Types private sealed class Source : IQueryVariableSource { public string? Scalar(string className, string property) => (className, property) switch { (QuerySubstitution.PatientClass, "ChtNum") => "0012345", (QuerySubstitution.PatientClass, "ComNum") => "1566794", (QuerySubstitution.PatientClass, "AdpDtm") => "202608181500", (QuerySubstitution.PatientClass, "PatTyp") => "O", // 안다, 값이 비었다 (QuerySubstitution.PatientClass, "EmgKTSGrd") => string.Empty, (QuerySubstitution.WorkClass, "WrkUid") => "011825", _ => null, }; public IReadOnlyDictionary? Row(string className, string property) => (className, property) switch { (QuerySubstitution.PatientClass, "PatInfDR") => new Dictionary(StringComparer.OrdinalIgnoreCase) { ["PatNam"] = "가상환자", ["PatResNum"] = "8801011234567", ["PatMblPhn"] = string.Empty, }, _ => null, }; } #endregion #region Methods private static QuerySubstitutionResult Run(string sql) => QuerySubstitution.Apply(sql, new Source()); [TestMethod] public void 스칼라를_원문_그대로_박는다() { // 따옴표를 붙여 주지 않는다 — SQL 쪽에서 감싸야 한다(clsMDataTable.vb:113) var r = Run("SELECT * FROM P_ComInf WHERE ComNum = <>"); Assert.AreEqual("SELECT * FROM P_ComInf WHERE ComNum = 1566794", r.Sql); Assert.AreEqual(QuerySubstitutionKind.Value, r.Hits[0].Kind); } [TestMethod] public void DataRow_두_형태를_모두_받는다() { var withItem = Run("X <>"); Assert.AreEqual("X 가상환자", withItem.Sql); // 괄호만 쓰는 형태도 정규식이 받는다 var bare = Run("X <>"); Assert.AreEqual("X 가상환자", bare.Sql); } [TestMethod] public void item_은_대문자면_값이_되지_않고_지워진다() { // 정규식이 대소문자를 구분한다(clsMDataTable.vb:112). 이걸 관대하게 만들면 // 레거시에서 안 되는 서식이 미리보기에서만 되고, 현장에서 빈칸으로 나온다. // // 지워지는 것이 맞다 — GetPropertyInfo 가 정규식 실패에 ""를 돌려주고(:118) // 호출부가 그것을 빈 문자열로 치환한다(:66-70). 접두어 불일치와는 다른 갈래다: // 그쪽은 oBaseObj 가 Nothing 이라 Replace 를 아예 하지 않아 토큰이 남는다. var r = Run("X <>"); Assert.AreEqual("X ", r.Sql); Assert.AreEqual(QuerySubstitutionKind.Unknown, r.Hits[0].Kind, "SQL 에서는 사라지지만 사람에게는 '규약 밖 경로'라고 말해야 한다"); Assert.AreEqual(1, r.UnknownTokens.Count); } [TestMethod] public void 축약형_접두어는_토큰이_그대로_남는다() { // 레거시는 클래스 전체 이름으로만 StartsWith 한다 — 축약형은 SQL 에 남아 ORA 구문오류가 된다. // 조용히 지우면 조건이 사라진 SQL 이 돌아 엉뚱한 행이 나온다. 남기는 쪽이 안전하다. var r = Run("SELECT 1 FROM DUAL WHERE X = <>"); StringAssert.Contains(r.Sql, "<>"); Assert.IsTrue(r.HasLeftover); Assert.AreEqual(QuerySubstitutionKind.NotAVariable, r.Hits[0].Kind); } [TestMethod] public void 모르는_속성과_빈_값을_구분한다() { // SQL 에서는 둘 다 빈 문자열이다(레거시와 같게) — 사람에게는 달라야 한다. var unknown = Run("A<>B"); Assert.AreEqual("AB", unknown.Sql); Assert.AreEqual(QuerySubstitutionKind.Unknown, unknown.Hits[0].Kind); Assert.AreEqual(1, unknown.UnknownTokens.Count); var empty = Run("A<>B"); Assert.AreEqual("AB", empty.Sql); Assert.AreEqual(QuerySubstitutionKind.Empty, empty.Hits[0].Kind); Assert.AreEqual(0, empty.UnknownTokens.Count); } [TestMethod] public void 행은_있고_컬럼이_없으면_오타로_본다() { // 이 경우는 서식이 컬럼명을 틀린 것이다 — 아직 안 옮긴 것과 구분해야 고칠 곳을 안다. var r = Run("A<>B"); Assert.AreEqual("AB", r.Sql); Assert.AreEqual(QuerySubstitutionKind.Empty, r.Hits[0].Kind); } [TestMethod] public void 여러_토큰과_사이_글자를_보존한다() { var r = Run("WHERE A = '<>'" + " AND B = <>" + " AND C = '<>'"); Assert.AreEqual("WHERE A = 'O' AND B = 1566794 AND C = '011825'", r.Sql); Assert.AreEqual(3, r.Hits.Count); } [TestMethod] public void 닫히지_않은_토큰은_손대지_않는다() { var r = Run("SELECT 1 FROM DUAL WHERE X = < "O'Brien"; public IReadOnlyDictionary? Row(string className, string property) => null; } #endregion }