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
}