레거시 런타임 bzDesignSheetLoader.ConvertQuery 는 토큰 접두어를 PatientInfo/SheetInfo/WorkInfo 의 GetType.FullName 과 Select Case 완전일치로 비교한다. 기존 목록은 <<PatientInfo.ChtNum>> 축약형이라 어떤 토큰도 치환되지 않고 <<...>> 가 SQL 에 리터럴로 남아 ORA 구문오류를 냈다 — 그 데이터소스를 참조하는 컨트롤이 전부 공백이 되는 조용한 실패다. 기존 서식의 쿼리는 원문 보존이라 영향 없고, 신규 작성분만 해당된다. 접두어는 ucLoadSheetBase.vb:7619-7631 이 형을 고정한다: PatientInfo/WorkInfo → M.CMM.HISOperatingInfo.* SheetInfo → M.EMR.SheetLoadOperatingInfo.bzSheetInfo 기존 20개 중 8개는 접두어뿐 아니라 속성명 자체가 실존하지 않았다(리플렉션 null): PatientInfo.OdrNum/OdrSeq, SheetInfo.EmrGbn/PatTyp/OdrNum/OdrSeq, WorkInfo.WrkNam/AdpDep. bzPatientInfo/bzSheetInfo/bzWorkInfo 의 실존 공개 스칼라 속성으로 다시 큐레이션했다. DataRow 접근형(...PatInfDR.item(컬럼))은 LastIndexOf(.) 규칙이 접두어를 깨뜨려 이 경로에서 반드시 실패하므로 목록에서 제외. - LegacyQueryVariableCatalog 신설(그룹/설명 포함 레코드) - 편집기 목록을 그룹 헤더 + 설명/토큰 2줄 + 툴팁으로 — 축약 오해 재발 방지 - 상용구 편집의 QueryEditorWindow 재사용에 showVariables:false 추가 (상용구 문구 화면에 SQL 변수 목록이 노출되던 오조작 여지 제거) 검증: 테스트 70/70(레거시 완전일치 규칙 시뮬레이션 + 운영 실사용 토큰 존재 검사 포함), edit-smoke 에 두 모드 실제 창 생성 검사 추가 — 실패 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
3.5 KiB
C#
75 lines
3.5 KiB
C#
using SheetMe.Core.Catalog;
|
|
|
|
namespace SheetMe.Core.Tests;
|
|
|
|
/// <summary>
|
|
/// 쿼리 치환 변수 카탈로그 테스트 — 레거시 bzDesignSheetLoader.ConvertQuery 의
|
|
/// "접두어를 GetType.FullName 과 완전일치 비교" 규칙을 시뮬레이션한다.
|
|
/// </summary>
|
|
[TestClass]
|
|
public sealed class LegacyQueryVariableCatalogTests
|
|
{
|
|
/// <summary>레거시 ConvertQuery 와 동일한 접두어 추출 — <c>Substring(0, LastIndexOf("."))</c></summary>
|
|
private static string PrefixOf(string token)
|
|
{
|
|
var inner = token[2..^2];
|
|
return inner[..inner.LastIndexOf('.')];
|
|
}
|
|
|
|
/// <summary>모든 토큰이 <<…>> 로 감싸이고 중복이 없어야 한다</summary>
|
|
[TestMethod]
|
|
public void Tokens_AreWellFormedAndUnique()
|
|
{
|
|
var seen = new HashSet<string>(StringComparer.Ordinal);
|
|
foreach (var variable in LegacyQueryVariableCatalog.All)
|
|
{
|
|
StringAssert.StartsWith(variable.Token, "<<", $"{variable.Token}: '<<' 로 시작해야 합니다.");
|
|
StringAssert.EndsWith(variable.Token, ">>", $"{variable.Token}: '>>' 로 끝나야 합니다.");
|
|
Assert.IsTrue(seen.Add(variable.Token), $"중복 토큰: {variable.Token}");
|
|
Assert.IsFalse(string.IsNullOrWhiteSpace(variable.Description), $"{variable.Token}: 설명이 비어 있습니다.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 레거시 완전일치 시뮬레이션 — 접두어가 런타임 비교 대상 3종 중 하나와 정확히 같아야 한다.
|
|
/// 축약형이나 DataRow 접근형(.item("컬럼"))이 섞이면 여기서 걸린다.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void EveryToken_ResolvesUnderLegacyExactMatchRule()
|
|
{
|
|
foreach (var variable in LegacyQueryVariableCatalog.All)
|
|
{
|
|
var prefix = PrefixOf(variable.Token);
|
|
CollectionAssert.Contains(LegacyQueryVariableCatalog.Prefixes.ToList(), prefix,
|
|
$"{variable.Token}: 접두어 '{prefix}' 가 런타임 비교 대상이 아닙니다 → 치환되지 않고 SQL 에 그대로 남습니다.");
|
|
}
|
|
}
|
|
|
|
/// <summary>접두어는 클래스 전체 이름이어야 한다 — 축약형 회귀 방지(이 버그가 실제로 있었다)</summary>
|
|
[TestMethod]
|
|
public void Prefixes_AreFullyQualifiedTypeNames()
|
|
{
|
|
foreach (var prefix in LegacyQueryVariableCatalog.Prefixes)
|
|
{
|
|
StringAssert.StartsWith(prefix, "M.", $"'{prefix}' 가 어셈블리 네임스페이스로 시작하지 않습니다.");
|
|
Assert.IsTrue(prefix.Count(c => c == '.') >= 2, $"'{prefix}' 가 클래스 전체 이름 형태가 아닙니다.");
|
|
}
|
|
}
|
|
|
|
/// <summary>운영에서 실제로 쓰이는 토큰이 목록에 있어야 한다(2026-08-11 census 근거)</summary>
|
|
[TestMethod]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzPatientInfo.ComNum")]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzPatientInfo.ChtNum")]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzPatientInfo.OemNum1")]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzPatientInfo.OemNum2")]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzPatientInfo.OemVar1")]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzPatientInfo.ComNum_Refer")]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzPatientInfo.AdpDtm")]
|
|
[DataRow("M.CMM.HISOperatingInfo.bzWorkInfo.WrkDte")]
|
|
public void ProductionTokens_ArePresent(string inner)
|
|
{
|
|
Assert.IsTrue(LegacyQueryVariableCatalog.All.Any(v => v.Token == $"<<{inner}>>"),
|
|
$"운영에서 쓰이는 토큰이 목록에 없습니다: <<{inner}>>");
|
|
}
|
|
}
|