Files
SheetMe/tests/SheetMe.Core.Tests/SqlFormatterTests.cs
T
MsystechandClaude Opus 5 68fc01c6ba 쿼리 편집기 레이아웃 — 카드 두 장, 갈래 칩, 그리고 《컨트롤명》 치환
목업대로 다시 짰다. 색·모서리·간격은 SheetMe 토큰을 그대로 쓴다.

■ 레이아웃

머리말(제목 + 대상 칩) / SQL 편집기 카드 / 치환 변수 카드 / 아래 버튼 줄.
편집기 카드에는 툴바(포맷·주석·자동완성 · 전체 지우기)와 상태 줄(연결 상태 · 글자·줄 수 · 줄,열)을 뒀다.
변수 카드에는 검색 + 갈래 칩 + 접이식 그룹 + 행마다 + 버튼을 뒀다.

+ 버튼을 넣은 이유는 더블클릭이 유일한 삽입 수단이었기 때문이다 — 화면에 드러나지 않는 조작이다.

■ 《컨트롤명》 치환 — 아예 없던 기능

레거시 화면에는 Controls 목록이 따로 있었다. 우리에겐 그 문법 자체가 없었다.
clsMDataTable.ConvertQuery 는 <<…>> 를 모두 치환한 뒤 《 로 다시 쪼개
poAllControlOnSheet(이름).GETVALUE 를 박는다(:78-94). 즉 같은 서식의 다른 컨트롤 값을
쿼리 조건에 넣을 수 있다.

이제 이 서식의 컨트롤이 '컨트롤' 갈래로 목록에 뜨고, 토크나이저도 《》 를 변수로 잡아
색이 붙고 검증에 걸린다. 다만 디자인 시점에는 컨트롤 목록이 없어 런타임이 무조건 "0" 으로
치환한다(:86) — 검증 실행이 통과해도 운영에서는 다른 값이 들어간다는 뜻이라 코드에 적어 뒀다.

■ 형식(String/Int32/DataRow) 표시

레거시가 '목록/형식' 두 열로 보여 주던 정보다. DataRow 인지 아닌지가 곧
'컬럼명을 더 채워야 하는가'를 뜻해서 고를 때 필요하다.

■ 갈래 칩

그룹이 일곱이라 칩으로 다 늘어놓으면 그것대로 목록이 된다.
'값이 어디서 오는가'로 네 갈래(환자·서식·컨트롤·작업자)로 묶고, 같은 칩을 다시 누르면 해제된다.

■ 포맷(Ctrl+Shift+F 상당 — 툴바 버튼)

예약어·함수를 대문자로 바꾸고 주요 절 앞에서 줄을 나누며 괄호 깊이만큼 들여쓴다.
문자열·주석·치환 변수는 손대지 않는다 — 특히 치환 변수는 대소문자가 맞아야 런타임이 찾으므로
대문자로 바꾸면 그 칸이 조용히 빈다. 이 포매터가 저지를 수 있는 가장 나쁜 일이라 테스트로 막았다.
두 번 눌러도 결과가 같다(안정성도 테스트로 고정).

단위 테스트 14건 추가(포매터 11 · 컨트롤 치환 3).
회귀: 테스트 230/230, 편집 스모크 실패 0, 팝업 점검 7/7, 검증 실행 점검 10/10,
DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 15:53:56 +09:00

115 lines
4.0 KiB
C#

using SheetMe.Core.Catalog;
namespace SheetMe.Core.Tests;
/// <summary>
/// SQL 정리 테스트.
///
/// 가장 중요한 불변식은 <b>뜻이 바뀌지 않는 것</b>이다. 특히 치환 변수는 대소문자가 맞아야
/// 런타임이 찾으므로, 정리하면서 건드리면 그 서식은 조용히 값을 못 받는다.
/// </summary>
[TestClass]
public sealed class SqlFormatterTests
{
[TestMethod]
public void Format_BreaksBeforeMajorClauses()
{
var formatted = SqlFormatter.Format("select a from t where b=1 and c=2");
var lines = formatted.Split('\n');
StringAssert.StartsWith(lines[0], "SELECT");
Assert.IsTrue(lines.Any(l => l.StartsWith("FROM", StringComparison.Ordinal)));
Assert.IsTrue(lines.Any(l => l.StartsWith("WHERE", StringComparison.Ordinal)));
Assert.IsTrue(lines.Any(l => l.StartsWith("AND", StringComparison.Ordinal)));
}
[TestMethod]
public void Format_UppercasesKeywordsAndFunctions()
{
var formatted = SqlFormatter.Format("select nvl(a,0) from dual");
StringAssert.Contains(formatted, "SELECT");
StringAssert.Contains(formatted, "NVL");
StringAssert.Contains(formatted, "DUAL");
}
/// <summary>식별자는 건드리지 않는다 — 대소문자를 가리는 곳에서 깨진다</summary>
[TestMethod]
public void Format_LeavesIdentifiersAlone()
{
var formatted = SqlFormatter.Format("select ChtNum, PatNam from P_PatMst");
StringAssert.Contains(formatted, "ChtNum");
StringAssert.Contains(formatted, "PatNam");
StringAssert.Contains(formatted, "P_PatMst");
}
/// <summary>
/// 치환 변수는 원문 그대로여야 한다. 대문자로 바꾸면 런타임이 클래스 이름을 못 찾아
/// 그 칸이 조용히 빈다 — 이 포매터가 저지를 수 있는 가장 나쁜 일이다.
/// </summary>
[TestMethod]
public void Format_NeverTouchesSubstitutionVariables()
{
const string token = "<<M.CMM.HISOperatingInfo.bzPatientInfo.ChtNum>>";
var formatted = SqlFormatter.Format($"select * from t where a = '{token}'");
StringAssert.Contains(formatted, token);
}
/// <summary>《컨트롤명》도 마찬가지다</summary>
[TestMethod]
public void Format_NeverTouchesControlSubstitution()
{
var formatted = SqlFormatter.Format("select * from t where a = 《TextBox1》");
StringAssert.Contains(formatted, "《TextBox1》");
}
/// <summary>문자열 안은 손대지 않는다 — 예약어와 같은 낱말이 들어 있어도</summary>
[TestMethod]
public void Format_LeavesStringLiteralsAlone()
{
var formatted = SqlFormatter.Format("select 'from where select' from t");
StringAssert.Contains(formatted, "'from where select'");
}
[TestMethod]
public void Format_IndentsByParenthesisDepth()
{
var formatted = SqlFormatter.Format("select * from (select a from t)");
Assert.IsTrue(formatted.Split('\n').Any(l => l.StartsWith(" ", StringComparison.Ordinal)),
formatted);
}
[TestMethod]
public void Format_LeavesNoTrailingWhitespace()
{
var formatted = SqlFormatter.Format("select a , b from t where c = 1");
foreach (var line in formatted.Split('\n'))
{
Assert.AreEqual(line.TrimEnd(), line, $"줄 끝 공백이 남았습니다: '{line}'");
}
}
[DataTestMethod]
[DataRow("")]
[DataRow(" ")]
public void Format_ReturnsEmptyInputUnchanged(string input)
=> Assert.AreEqual(input, SqlFormatter.Format(input));
/// <summary>정리해도 다시 정리하면 같아야 한다 — 누를 때마다 모양이 흔들리면 못 쓴다</summary>
[TestMethod]
public void Format_IsStableWhenAppliedTwice()
{
var once = SqlFormatter.Format("select a,b from t where c=1 and d=2");
var twice = SqlFormatter.Format(once);
Assert.AreEqual(once, twice);
}
}