using System.Text.RegularExpressions; using System.Xml; using Infrastructure.StockData; namespace Application.Tests; [TestClass] public sealed class SeibroXmlTests { [TestMethod] public void Parse_All37Fixtures_SmokeTest_RowsMatchActualDataElements() { // 샘플 37종 전수 스모크 — 전부 예외 없이 파싱되고, rows 수는 파일에 실제 존재하는 수와 일치해야 한다 // (vector@result 는 원문 행수라 truncate 된 샘플에선 rows 보다 클 수 있음 → ResultCount 는 음수 아님만 확인) var files = SeibroFixtures.AllXmlFiles(); Assert.AreEqual(37, files.Length, "SEIBro fixture 는 37종 전부 복사되어야 한다"); foreach (var file in files) { var xml = File.ReadAllText(file); var envelope = SeibroXml.Parse(xml); // 파서와 독립적인 검증용 카운트 — 시작 태그 수 (셀프클로징 없음, 래퍼/필드 element 는 data 로 시작하지 않음) var actualDataCount = Regex.Matches(xml, @"]", RegexOptions.IgnoreCase).Count; Assert.AreEqual(actualDataCount, envelope.Rows.Count, $"{Path.GetFileName(file)}: rows 수는 실제 data element 수와 일치"); Assert.IsTrue(envelope.ResultCount >= 0, $"{Path.GetFileName(file)}: result 행수 속성 파싱"); Assert.IsTrue(envelope.ResultCount >= envelope.Rows.Count, $"{Path.GetFileName(file)}: result 는 원문 행수 (truncate 샘플은 rows 이상)"); } } [TestMethod] public void Parse_UppercaseResultWrapper_GetAssetInfo_MapsFields() { // 파생결합 getAssetInfo 는 래퍼가 대문자 — 대소문자 무시 파싱 검증 (docs/SEIBro/readme.md 규칙 4) var envelope = SeibroXml.Parse(SeibroFixtures.Read("getAssetInfo.xml")); Assert.AreEqual(3, envelope.ResultCount); var row = envelope.Rows[0]; Assert.AreEqual("KR6539335879", SeibroXml.GetOrNull(row, "ISIN")); Assert.AreEqual("1", SeibroXml.GetOrNull(row, "ASSET_SEQ")); Assert.AreEqual("Hang Seng China Enterprises Index", SeibroXml.GetOrNull(row, "STND_BASSET_NM")); Assert.AreEqual("6090.15", SeibroXml.GetOrNull(row, "XRC_STOP_LOLMT_PRICE")); } [TestMethod] public void Parse_LowercaseResultWrapper_GetShotnByMart_MapsFields() { // 주식 getShotnByMart 는 래퍼가 소문자 — 필드값 = value 속성 var envelope = SeibroXml.Parse(SeibroFixtures.Read("getShotnByMart.xml")); Assert.AreEqual(126, envelope.ResultCount, "vector@result = 원문 행수"); var row = envelope.Rows[0]; Assert.AreEqual("000800", SeibroXml.GetOrNull(row, "SHOTN_ISIN")); Assert.AreEqual("경남기업", SeibroXml.GetOrNull(row, "KOR_SECN_NM")); Assert.AreEqual("80", SeibroXml.GetOrNull(row, "ISSUCO_CUSTNO")); } [TestMethod] public void Parse_EmptyResult_ZeroRows() { // result="0" + data 없음 = 정상 빈 결과 (getXrcStkStatInfo 샘플이 실제 이 형태) var envelope = SeibroXml.Parse(SeibroFixtures.Read("getXrcStkStatInfo.xml")); Assert.AreEqual(0, envelope.ResultCount); Assert.AreEqual(0, envelope.Rows.Count); } [TestMethod] public void Parse_EmptyValueAttribute_TreatedAsNull() { // value="" 는 null 취급 — 행 딕셔너리에서 키 자체가 빠진다 (element 생략과 동일 의미) const string xml = """ """; var envelope = SeibroXml.Parse(xml); Assert.AreEqual(1, envelope.Rows.Count); Assert.AreEqual("KR7005930003", SeibroXml.GetOrNull(envelope.Rows[0], "ISIN")); Assert.IsNull(SeibroXml.GetOrNull(envelope.Rows[0], "EMPTY_FIELD"), "value=\"\" → null"); Assert.IsNull(SeibroXml.GetOrNull(envelope.Rows[0], "OMITTED_FIELD"), "element 생략 → null"); } [TestMethod] public void Parse_FieldLookup_IsCaseInsensitive() { const string xml = """ """; var envelope = SeibroXml.Parse(xml); Assert.AreEqual("593", SeibroXml.GetOrNull(envelope.Rows[0], "issuco_custno"), "필드명 조회는 대소문자 무시"); } [TestMethod] public void Parse_NonXmlInput_Throws() { // 비XML 응답(에러 페이지 등)은 명시적 예외 — silent 빈 결과 금지 Assert.ThrowsExactly(() => SeibroXml.Parse("Unauthorized Key")); } [TestMethod] public void Parse_NonVectorRoot_Throws() { Assert.ThrowsExactly(() => SeibroXml.Parse("")); } [TestMethod] public void Parse_MissingResultAttribute_Throws() { Assert.ThrowsExactly(() => SeibroXml.Parse("")); } }