#nullable enable using Cysharp.Threading.Tasks; using NUnit.Framework; using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using UnityEngine; using UVC.Data; using UVC.Data.Core; using UVC.Data.Http; namespace UVC.Tests.Data { /// /// HttpDataFetcher 클래스의 테스트를 위한 테스트 클래스입니다. /// [TestFixture] public class HttpDataFetcherTests { // 테스트에 사용할 HttpDataFetcher 인스턴스 private HttpDataFetcher? pipeLine; /// /// 각 테스트 실행 전에 호출되는 설정 메서드입니다. /// [SetUp] public void Setup() { pipeLine = new HttpDataFetcher(); pipeLine.UseMockup = true; // MockHttpRequester 사용 설정 // 테스트를 위한 DataRepository 초기화 ClearDataRepository(); } /// /// 모든 테스트 메서드를 실행하는 메서드입니다. /// /// /// 이 메서드는 클래스의 모든 테스트 메서드를 순차적으로 호출하고 /// 각 테스트의 성공 또는 실패 여부를 로그로 출력합니다. /// public async UniTask TestAll() { Setup(); Debug.Log("===== HttpDataFetcher 테스트 시작 ====="); //RunTest(nameof(Add_NewInfo_AddedSuccessfully), Add_NewInfo_AddedSuccessfully); //RunTest(nameof(Add_ExistingInfo_UpdatesExistingEntry), Add_ExistingInfo_UpdatesExistingEntry); //await RunTestAsync(nameof(Remove_ExistingInfo_RemovedSuccessfullyAsync), Remove_ExistingInfo_RemovedSuccessfullyAsync); //await RunTestAsync(nameof(Remove_NonExistingInfo_DoesNothing), Remove_NonExistingInfo_DoesNothing); //RunTest(nameof(Excute_WithNonExistingKey_DoesNothing), Excute_WithNonExistingKey_DoesNothing); //await RunTestAsync(nameof(Excute_WithJObjectResponse_ProcessesDataCorrectly), Excute_WithJObjectResponse_ProcessesDataCorrectly); //await RunTestAsync(nameof(Excute_WithJArrayResponse_ProcessesDataCorrectly), Excute_WithJArrayResponse_ProcessesDataCorrectly); //await RunTestAsync(nameof(Test_Excute_AgvDataParsing), Test_Excute_AgvDataParsing); //await RunTestAsync(nameof(Test_Excute_AlarmDataParsing), Test_Excute_AlarmDataParsing); //await RunTestAsync(nameof(Test_Excute_MultipleDataTypes), Test_Excute_MultipleDataTypes); //await RunTestAsync(nameof(Test_Excute_CarrierDataParsing), Test_Excute_CarrierDataParsing); //await RunTestAsync(nameof(Test_Excute_BaseInfoDataParsing), Test_Excute_BaseInfoDataParsing); //await RunTestAsync(nameof(Test_Excute_WithRepeatExecution), Test_Excute_WithRepeatExecution); //await RunTestAsync(nameof(Test_StopRepeat_StopsExecutionCorrectly), Test_StopRepeat_StopsExecutionCorrectly); //await RunTestAsync(nameof(Test_MultipleRepeatingRequests_ManagedIndependently), Test_MultipleRepeatingRequests_ManagedIndependently); //await RunTestAsync(nameof(Test_RepeatWithCount_StopsAutomatically), Test_RepeatWithCount_StopsAutomatically); // HttpResponseMask 테스트 추가 //Debug.Log("===== HttpResponseMask 테스트 시작 ====="); //RunTest(nameof(HttpResponseMask_Apply_SuccessfulResponse_ReturnsSuccessWithData), HttpResponseMask_Apply_SuccessfulResponse_ReturnsSuccessWithData); //RunTest(nameof(HttpResponseMask_Apply_FailedResponse_WrongSuccessValue_ReturnsFailWithMessage), HttpResponseMask_Apply_FailedResponse_WrongSuccessValue_ReturnsFailWithMessage); //RunTest(nameof(HttpResponseMask_Apply_FailedResponse_MissingSuccessKey_ReturnsFailWithMessage), HttpResponseMask_Apply_FailedResponse_MissingSuccessKey_ReturnsFailWithMessage); //RunTest(nameof(HttpResponseMask_Apply_FailedResponse_MissingDataKey_ReturnsFailWithMessage), HttpResponseMask_Apply_FailedResponse_MissingDataKey_ReturnsFailWithMessage); //RunTest(nameof(HttpResponseMask_Apply_SuccessfulResponse_CustomKeys_ReturnsSuccessWithData), HttpResponseMask_Apply_SuccessfulResponse_CustomKeys_ReturnsSuccessWithData); //RunTest(nameof(HttpResponseMask_Apply_InvalidJson_ThrowsException), HttpResponseMask_Apply_InvalidJson_ThrowsException); //Debug.Log("===== HttpResponseMask 테스트 완료 ====="); Debug.Log("===== DataValidator 테스트 시작 ====="); await RunTestAsync(nameof(Test_Excute_WithValidData_ValidatorPasses), Test_Excute_WithValidData_ValidatorPasses); await RunTestAsync(nameof(Test_Excute_WithInvalidData_ValidatorFails), Test_Excute_WithInvalidData_ValidatorFails); await RunTestAsync(nameof(Test_Excute_WithArrayAndValidator_FiltersInvalidData), Test_Excute_WithArrayAndValidator_FiltersInvalidData); Debug.Log("===== DataValidator 테스트 완료 ====="); Debug.Log("===== HttpDataFetcher 테스트 완료 ====="); } /// /// 단일 테스트 메서드를 실행하고 결과를 로그로 출력합니다. /// /// 테스트 메서드 이름 /// 실행할 테스트 메서드 private void RunTest(string testName, Action testAction) { try { Debug.Log($"테스트 시작: {testName}"); testAction(); Debug.Log($"테스트 성공: {testName}"); } catch (Exception ex) { Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}"); } } private async UniTask RunTestAsync(string testName, Func testAction) { try { Debug.Log($"테스트 시작: {testName}"); await testAction(); Debug.Log($"테스트 성공: {testName}"); } catch (Exception ex) { Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}"); } } /// /// 새로운 HttpRequestConfig를 추가하는 테스트 /// [Test] public void Add_NewInfo_AddedSuccessfully() { // Arrange var info = new HttpRequestConfig("http://test.com"); // Act pipeLine.Add("test", info); // Assert - Dictionary에 추가되었는지 확인 (reflection 사용) var infoList = GetInfoListField(); Assert.IsTrue(infoList.ContainsKey("test")); Assert.AreEqual(info, infoList["test"]); } /// /// 기존에 존재하는 키로 HttpRequestConfig를 추가할 때 업데이트 테스트 /// [Test] public void Add_ExistingInfo_UpdatesExistingEntry() { // Arrange var info1 = new HttpRequestConfig("http://test1.com"); var info2 = new HttpRequestConfig("http://test2.com"); pipeLine.Add("test", info1); // Act pipeLine.Add("test", info2); // Assert var infoList = GetInfoListField(); Assert.IsTrue(infoList.ContainsKey("test")); Assert.AreEqual(info2, infoList["test"]); Assert.AreNotEqual(info1, infoList["test"]); } /// /// 존재하는 HttpRequestConfig를 제거하는 테스트 /// [Test] public async UniTask Remove_ExistingInfo_RemovedSuccessfullyAsync() { // Arrange var info = new HttpRequestConfig("http://test.com"); pipeLine.Add("test", info); // Act await pipeLine.RemoveAsync("test"); // Assert var infoList = GetInfoListField(); Assert.IsFalse(infoList.ContainsKey("test")); } /// /// 존재하지 않는 키에 대한 Remove 호출 테스트 /// [Test] public async UniTask Remove_NonExistingInfo_DoesNothing() { // Arrange var info = new HttpRequestConfig("http://test.com"); pipeLine.Add("test", info); // Act - 존재하지 않는 키 제거 시도 await pipeLine.RemoveAsync("nonexistent"); // Assert - 기존 항목은 여전히 존재해야 함 var infoList = GetInfoListField(); Assert.IsTrue(infoList.ContainsKey("test")); Assert.AreEqual(info, infoList["test"]); } /// /// HttpDataFetcher의 private configList 필드 가져오기 /// private Dictionary GetInfoListField() { var fieldInfo = typeof(HttpDataFetcher).GetField("configList", BindingFlags.NonPublic | BindingFlags.Instance); return (Dictionary)fieldInfo.GetValue(pipeLine); } /// /// DataRepository를 테스트를 위해 초기화 (리플렉션 사용) /// private void ClearDataRepository() { var repositoryType = typeof(DataRepository); var instanceField = repositoryType.GetField("instance", BindingFlags.NonPublic | BindingFlags.Static); if (instanceField != null) { // Lazy 필드 값을 가져옴 var lazyInstance = instanceField.GetValue(null); // Lazy의 Value 속성을 통해 인스턴스 접근 var instance = lazyInstance.GetType().GetProperty("Value").GetValue(lazyInstance); // dataObjects 딕셔너리 필드 가져오기 var dataObjectsField = repositoryType.GetField("dataObjects", BindingFlags.NonPublic | BindingFlags.Instance); // 딕셔너리 접근하여 Clear 메서드 호출 var dataObjects = dataObjectsField.GetValue(instance) as Dictionary; dataObjects.Clear(); } } /// /// Excute 메소드에서 JObject 응답을 처리하는 기능 테스트 /// [Test] public async UniTask Excute_WithJObjectResponse_ProcessesDataCorrectly() { // Arrange bool handlerCalled = false; DataObject? receivedData = null; var mockResponse = @"{""message"": ""Success"", ""data"": {""name"": ""테스트"", ""value"": 123}}"; // DataMask와 DataMapper 설정 var dataMask = new DataMask(); dataMask["name"] = "이름"; dataMask["value"] = 0; var dataMapper = new DataMapper(dataMask); // HttpRequestConfig 설정 var info = new HttpRequestConfig("http://test.com") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { Debug.Log("핸들러 호출됨"); handlerCalled = true; if (data is DataObject dataObject) { receivedData = dataObject; } }); pipeLine.Add("testKey", info); try { // MockHttpRequester에 테스트용 응답 설정 MockHttpRequester.SetResponse("http://test.com", mockResponse); // Act await pipeLine.Excute("testKey"); // Assert Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다."); Assert.IsNotNull(receivedData, "핸들러에 전달된 데이터가 null입니다."); Assert.AreEqual("테스트", receivedData?.GetString("name")); Assert.AreEqual(123, receivedData?.GetInt("value")); } finally { // 설정한 응답 정리 MockHttpRequester.ClearResponses("http://test.com"); await pipeLine.RemoveAsync("testKey"); } } /// /// Excute 메소드에서 JArray 응답을 처리하는 기능 테스트 /// [Test] public async UniTask Excute_WithJArrayResponse_ProcessesDataCorrectly() { // Arrange bool handlerCalled = false; IDataObject? receivedData = null; var mockResponse = @"{""message"": ""Success"", ""data"": [{""name"": ""항목1"", ""value"": 10}, {""name"": ""항목2"", ""value"": 20}]}"; // 배열용 DataMask 설정 var dataMask = new DataMask { ["name"] = "이름", ["value"] = 0 }; var dataMapper = new DataMapper(dataMask); // HttpRequestConfig 설정 var info = new HttpRequestConfig("http://test.com") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCalled = true; receivedData = data; }); pipeLine.Add("testArrayKey", info); try { // MockHttpRequester에 테스트용 응답 설정 MockHttpRequester.SetResponse("http://test.com", mockResponse); // Act await pipeLine.Excute("testArrayKey"); // Assert Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다."); Assert.IsNotNull(receivedData, "핸들러에 전달된 데이터가 null입니다."); Assert.IsTrue(receivedData is DataArray, "결과가 DataArray가 아닙니다."); var dataArray = receivedData as DataArray; Assert.AreEqual(2, dataArray?.Count); Assert.AreEqual("항목1", dataArray?[0].GetString("name")); Assert.AreEqual(10, dataArray?[0].GetInt("value")); Assert.AreEqual("항목2", dataArray?[1].GetString("name")); Assert.AreEqual(20, dataArray?[1].GetInt("value")); } finally { // 설정한 응답 정리 MockHttpRequester.ClearResponses("http://test.com"); await pipeLine.RemoveAsync("testArrayKey"); } } /// /// Excute 메소드에서 존재하지 않는 키를 호출했을 때의 동작 테스트 /// [Test] public void Excute_WithNonExistingKey_DoesNothing() { // Arrange - 의도적으로 아무 것도 설정하지 않음 // Act & Assert - 예외가 발생하지 않아야 함 Assert.DoesNotThrow(() => pipeLine.Excute("nonExistingKey")); } /// /// UVC.Tests.MockHttpRequester를 사용하여 AGV 데이터 파싱 테스트 /// [Test] public async UniTask Test_Excute_AgvDataParsing() { // Arrange bool handlerCalled = false; IDataObject? receivedData = null; string agvUrl = "http://api.example.com/agv"; // MockHttpRequester는 url에 "agv"가 포함된 경우 AGV 관련 응답 반환 // 배열용 DataMask 설정 (AGV 데이터 구조에 맞춤) var dataMask = new DataMask { ["VHL_NAME"] = "차량명", ["AGV_IDX"] = "인덱스", ["B_INSTALL"] = "설치여부", ["NODE_ID"] = "노드ID", ["REAL_ID"] = "실제ID", ["VHL_STATE"] = "상태", ["BAY_LIST"] = "베이리스트" }; var dataMapper = new DataMapper(dataMask); // HttpRequestConfig 설정 var info = new HttpRequestConfig(agvUrl, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCalled = true; receivedData = data; }); pipeLine.Add("agvData", info); // Act await pipeLine.Excute("agvData"); // Assert Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다."); Assert.IsNotNull(receivedData, "데이터가 null입니다."); Assert.IsTrue(receivedData is DataArray, "결과가 DataArray가 아닙니다."); var dataArray = receivedData as DataArray; Assert.IsTrue(dataArray?.Count > 0, "데이터 배열이 비어 있습니다."); // 첫 번째 항목 확인 Assert.IsNotNull(dataArray?[0].GetString("VHL_NAME")); Assert.IsNotNull(dataArray?[0].GetString("AGV_IDX")); Assert.IsNotNull(dataArray?[0].GetString("B_INSTALL")); } /// /// UVC.Tests.MockHttpRequester를 사용하여 알람 데이터 파싱 테스트 /// [Test] public async UniTask Test_Excute_AlarmDataParsing() { // Arrange bool handlerCalled = false; IDataObject? receivedData = null; string alarmUrl = "http://api.example.com/alarm"; // MockHttpRequester는 url에 "alarm"이 포함된 경우 알람 관련 응답 반환 // 배열용 DataMask 설정 (알람 데이터 구조에 맞춤) var dataMask = new DataMask { ["ID"] = "알람ID", ["ALARM_TYPE"] = "알람타입", ["LEVEL"] = "심각도", ["STATE"] = "상태", ["MESSAGE"] = "메시지", ["CODE"] = "코드", ["ICON"] = "아이콘", ["SET_TIME"] = "발생시간" }; var dataMapper = new DataMapper(dataMask); // HttpRequestConfig 설정 var info = new HttpRequestConfig(alarmUrl, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCalled = true; receivedData = data; }); pipeLine.Add("alarmData", info); try { // Act await pipeLine.Excute("alarmData"); // Assert Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다."); Assert.IsNotNull(receivedData, "데이터가 null입니다."); Assert.IsTrue(receivedData is DataArray, "결과가 DataArray가 아닙니다."); var dataArray = receivedData as DataArray; Assert.IsTrue(dataArray?.Count > 0, "데이터 배열이 비어 있습니다."); // 첫 번째 항목 확인 Assert.IsNotNull(dataArray?[0].GetString("ID")); Assert.IsNotNull(dataArray?[0].GetString("ALARM_TYPE")); Assert.IsNotNull(dataArray?[0].GetString("LEVEL")); Assert.IsNotNull(dataArray?[0].GetString("MESSAGE")); Assert.IsNotNull(dataArray?[0].GetString("CODE")); Assert.IsNotNull(dataArray?[0].GetString("ICON")); Assert.IsNotNull(dataArray?[0].GetString("SET_TIME")); } finally { await pipeLine.RemoveAsync("alarmData"); } } /// /// UVC.Tests.MockHttpRequester를 사용하여 여러 URL 유형에 대한 동시 테스트 /// [Test] public async UniTask Test_Excute_MultipleDataTypes() { // Arrange int handlerCallCount = 0; Dictionary results = new Dictionary(); // 각 데이터 타입별 URL Dictionary urls = new Dictionary { { "agv", "http://api.example.com/agv" }, { "equipment", "http://api.example.com/equipment" }, { "alarm", "http://api.example.com/alarm" } }; // 각 데이터 타입에 대한 DataMask 설정 Dictionary dataMasks = new Dictionary { { "agv", new DataMask { ["0"] = new DataMask { ["VHL_NAME"] = "차량명", ["AGV_IDX"] = "인덱스" } } }, { "equipment", new DataMask { ["0"] = new DataMask { ["EQP_ID"] = "장비ID", ["KOR_EQP_NAME"] = "장비명" } } }, { "alarm", new DataMask { ["0"] = new DataMask { ["ID"] = "알람ID", ["ALARM_TYPE"] = "알람타입" } } } }; // 각 데이터 타입별 HttpRequestConfig 설정 및 등록 foreach (var item in urls) { string key = item.Key; var info = new HttpRequestConfig(item.Value, "get") .SetDataMapper(new DataMapper(dataMasks[key])) .SetSuccessHandler((data) => { handlerCallCount++; results[key] = data; }); pipeLine.Add(key, info); await pipeLine.Excute(key); } // Assert Assert.AreEqual(urls.Count, handlerCallCount, "모든 핸들러가 호출되지 않았습니다."); Assert.AreEqual(urls.Count, results.Count, "모든 결과가 수집되지 않았습니다."); // 각 데이터 타입별 결과 확인 foreach (var result in results) { Assert.IsNotNull(result.Value, $"{result.Key} 데이터가 null입니다."); Assert.IsTrue(result.Value is DataArray, $"{result.Key} 결과가 DataArray가 아닙니다."); var dataArray = result.Value as DataArray; Assert.IsTrue(dataArray?.Count > 0, $"{result.Key} 데이터 배열이 비어 있습니다."); } } /// /// UVC.Tests.MockHttpRequester를 사용한 운반대(Carrier) 데이터 테스트 /// [Test] public async UniTask Test_Excute_CarrierDataParsing() { // Arrange bool handlerCalled = false; IDataObject? receivedData = null; string testUrl = "http://api.example.com/carrier"; // url에 "carrier"가 포함된 경우 캐리어 관련 응답 반환 // DataMask와 DataMapper 설정 var dataMask = new DataMask { ["MAIN_CARR_ID"] = "캐리어ID", ["SUB_CARR_ID"] = "서브ID", ["CARR_SEQ"] = "순번", ["CARR_USE"] = "사용상태" }; var dataMapper = new DataMapper(dataMask); // HttpRequestConfig 설정 var info = new HttpRequestConfig(testUrl, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCalled = true; receivedData = data; }); pipeLine.Add("testCarrierData", info); // Act await pipeLine.Excute("testCarrierData"); // Assert Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다."); Assert.IsNotNull(receivedData, "응답 데이터가 null입니다."); Assert.IsTrue(receivedData is DataArray, "응답이 DataArray가 아닙니다."); var dataArray = receivedData as DataArray; Assert.IsTrue(dataArray?.Count > 0, "데이터 배열이 비어있습니다."); // 첫 번째 캐리어 데이터 확인 Assert.IsNotNull(dataArray?[0].GetString("MAIN_CARR_ID"), "캐리어ID가 null입니다."); Assert.IsNotNull(dataArray?[0].GetString("SUB_CARR_ID"), "서브ID가 null입니다."); Assert.IsNotNull(dataArray?[0].GetString("CARR_SEQ"), "순번이 null입니다."); Assert.IsNotNull(dataArray?[0].GetString("CARR_USE"), "사용상태가 null입니다."); } /// /// UVC.Tests.MockHttpRequester를 사용하여 BaseInfo 데이터 파싱 테스트 /// [Test] public async UniTask Test_Excute_BaseInfoDataParsing() { // Arrange bool handlerCalled = false; IDataObject? receivedData = null; string baseInfoUrl = "http://api.example.com/baseinfo"; // url에 "baseinfo"가 포함된 경우 기본 정보 응답 반환 // BaseInfo에서 테스트할 주요 섹션 (AGV, EQUIPMENT, ALARM 등)에 대한 DataMask 설정 var dataMask = new DataMask { ["AGV"] = new DataMask { ["0"] = new DataMask { ["VHL_NAME"] = "차량명", ["AGV_IDX"] = "인덱스", ["B_INSTALL"] = "설치여부", ["NODE_ID"] = "노드ID", ["VHL_STATE"] = "상태" } }, ["EQUIPMENT"] = new DataMask { ["0"] = new DataMask { ["EQP_ID"] = "장비ID", ["KOR_EQP_NAME"] = "장비명", ["STATE_ID"] = "상태", ["NTW_STS"] = "네트워크상태" } }, ["ALARM"] = new DataMask { ["0"] = new DataMask { ["ID"] = "알람ID", ["ALARM_TYPE"] = "알람타입", ["LEVEL"] = "심각도", ["MESSAGE"] = "메시지" } } }; var dataMapper = new DataMapper(dataMask); // HttpRequestConfig 설정 var info = new HttpRequestConfig(baseInfoUrl, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCalled = true; receivedData = data; }); pipeLine.Add("baseInfoData", info); try { // Act await pipeLine.Excute("baseInfoData"); // Assert Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다."); Assert.IsNotNull(receivedData, "데이터가 null입니다."); Assert.IsTrue(receivedData is DataObject, "결과가 DataObject가 아닙니다."); var dataObject = receivedData as DataObject; // AGV 데이터 검증 Assert.IsNotNull(dataObject?.Get("AGV"), "AGV 데이터가 없습니다."); var agvData = dataObject?.Get("AGV") as DataArray; Assert.IsTrue(agvData?.Count > 0, "AGV 데이터 배열이 비어 있습니다."); Assert.IsNotNull(agvData?[0].GetString("VHL_NAME"), "VHL_NAME 필드가 없습니다."); Assert.IsNotNull(agvData?[0].GetString("AGV_IDX"), "AGV_IDX 필드가 없습니다."); Assert.IsNotNull(agvData?[0].GetString("B_INSTALL"), "B_INSTALL 필드가 없습니다."); // EQUIPMENT 데이터 검증 Assert.IsNotNull(dataObject?.Get("EQUIPMENT"), "EQUIPMENT 데이터가 없습니다."); var equipmentData = dataObject?.Get("EQUIPMENT") as DataArray; Assert.IsTrue(equipmentData?.Count > 0, "EQUIPMENT 데이터 배열이 비어 있습니다."); Assert.IsNotNull(equipmentData?[0].GetString("EQP_ID"), "EQP_ID 필드가 없습니다."); Assert.IsNotNull(equipmentData?[0].GetString("KOR_EQP_NAME"), "KOR_EQP_NAME 필드가 없습니다."); Assert.IsNotNull(equipmentData?[0].GetString("STATE_ID"), "STATE_ID 필드가 없습니다."); // ALARM 데이터 검증 Assert.IsNotNull(dataObject?.Get("ALARM"), "ALARM 데이터가 없습니다."); var alarmData = dataObject?.Get("ALARM") as DataArray; Assert.IsTrue(alarmData?.Count > 0, "ALARM 데이터 배열이 비어 있습니다."); Assert.IsNotNull(alarmData?[0].GetString("ID"), "ID 필드가 없습니다."); Assert.IsNotNull(alarmData?[0].GetString("ALARM_TYPE"), "ALARM_TYPE 필드가 없습니다."); Assert.IsNotNull(alarmData?[0].GetString("LEVEL"), "LEVEL 필드가 없습니다."); Assert.IsNotNull(alarmData?[0].GetString("MESSAGE"), "MESSAGE 필드가 없습니다."); } finally { await pipeLine.RemoveAsync("baseInfoData"); } } /// /// 반복 실행 기능을 테스트합니다. /// [Test] public async UniTask Test_Excute_WithRepeatExecution() { // Arrange int handlerCallCount = 0; List receivedResponses = new List(); string testUrl = "http://test.com/repeat"; int expectedCallCount = 3; int repeatInterval = 100; // 테스트를 빠르게 진행하기 위해 간격을 짧게 설정 // 여러 응답을 순차적으로 반환하기 위한 응답 데이터 설정 string[] mockResponses = new string[] { @"{""message"": ""Success"", ""data"": {""Id"": 1, ""status"": ""pending"", ""timestamp"": ""2025-06-09T10:00:00Z""}}", @"{""message"": ""Success"", ""data"": {""Id"": 1, ""status"": ""processing"", ""timestamp"": ""2025-06-09T10:00:10Z""}}", @"{""message"": ""Success"", ""data"": {""Id"": 1, ""status"": ""completed"", ""timestamp"": ""2025-06-09T10:00:20Z""}}" }; // Mock 응답 설정 MockHttpRequester.SetResponse(testUrl, mockResponses[0]); // DataMask와 DataMapper 설정 var dataMask = new DataMask(); dataMask["Id"] = 0; dataMask["status"] = ""; dataMask["timestamp"] = ""; var dataMapper = new DataMapper(dataMask); // 반복 실행 설정을 포함한 HttpRequestConfig 생성 var info = new HttpRequestConfig(testUrl, "get") .SetDataMapper(dataMapper) .SetSuccessHandler(async (data) => { handlerCallCount++; if (data is DataObject dataObject) { receivedResponses.Add(dataObject.GetString("status")); } // 반복 실행 중단 if (handlerCallCount >= expectedCallCount) { await pipeLine.StopRepeat("repeatTest"); } else { MockHttpRequester.SetResponse(testUrl, mockResponses[handlerCallCount]); } }) .SetRepeat(true, expectedCallCount, repeatInterval, false); pipeLine.UseMockup = true; pipeLine.Add("repeatTest", info); try { // Act await pipeLine.Excute("repeatTest"); // 반복 작업이 완료될 때까지 대기 // (실제 상황에서는 이렇게 기다리지 않지만 테스트를 위해 필요) await UniTask.Delay((repeatInterval * expectedCallCount) + 1000 * expectedCallCount); // Assert Assert.AreEqual(expectedCallCount, handlerCallCount, "핸들러 호출 횟수가 예상과 다릅니다"); // 응답이 순차적으로 처리되었는지 확인 Assert.AreEqual("pending", receivedResponses[0], "첫 번째 응답이 올바르지 않습니다"); Assert.AreEqual("processing", receivedResponses[1], "두 번째 응답이 올바르지 않습니다"); Assert.AreEqual("completed", receivedResponses[2], "세 번째 응답이 올바르지 않습니다"); } finally { // 테스트 정리 await pipeLine.RemoveAsync("repeatTest"); MockHttpRequester.ClearResponses(testUrl); } } /// /// 실행 중인 반복 요청을 중지하는 기능을 테스트합니다. /// [Test] public async UniTask Test_StopRepeat_StopsExecutionCorrectly() { // Arrange int handlerCallCount = 0; string testUrl = "http://test.com/repeat-stop"; int repeatInterval = 100; // Mock 응답 설정 string mockResponse = @"{""message"": ""Success"", ""data"": {""Id"": 2, ""status"": ""running"", ""timestamp"": ""2025-06-09T11:00:00Z""}}"; MockHttpRequester.SetResponse(testUrl, mockResponse); // DataMask와 DataMapper 설정 var dataMask = new DataMask(); dataMask["Id"] = 0; dataMask["status"] = ""; dataMask["timestamp"] = ""; var dataMapper = new DataMapper(dataMask); // 무한 반복 설정을 포함한 HttpRequestConfig 생성 var info = new HttpRequestConfig(testUrl, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCallCount++; }) .SetRepeat(true, 0, repeatInterval, false); // 무한 반복 (repeatCount = 0) pipeLine.UseMockup = true; pipeLine.Add("infiniteRepeatTest", info); try { // Act await pipeLine.Excute("infiniteRepeatTest"); // 몇 번의 반복 실행이 될 때까지 대기 await UniTask.Delay(repeatInterval * 2 + 50); // 반복 중지 await pipeLine.StopRepeat("infiniteRepeatTest"); // 현재 호출 횟수 기록 int callCountBeforeStop = handlerCallCount; // 더 이상 호출이 발생하지 않는지 확인하기 위해 추가 대기 await UniTask.Delay(repeatInterval * 2 + 50); // Assert Assert.Greater(callCountBeforeStop, 0, "반복 호출이 발생하지 않았습니다"); Assert.AreEqual(callCountBeforeStop, handlerCallCount, "반복 중지 후에도 호출이 계속 발생했습니다"); } finally { // 테스트 정리 await pipeLine.RemoveAsync("infiniteRepeatTest"); MockHttpRequester.ClearResponses(testUrl); } } /// /// 여러 개의 반복 요청을 동시에 실행하고 개별적으로 중지하는 기능을 테스트합니다. /// [Test] public async UniTask Test_MultipleRepeatingRequests_ManagedIndependently() { // Arrange int handlerCallCount1 = 0; int handlerCallCount2 = 0; string testUrl1 = "http://test.com/repeat1"; string testUrl2 = "http://test.com/repeat2"; int repeatInterval1 = 100; int repeatInterval2 = 150; // Mock 응답 설정 string mockResponse1 = @"{""message"": ""Success"", ""data"": {""Id"": 3, ""name"": ""작업1""}}"; string mockResponse2 = @"{""message"": ""Success"", ""data"": {""Id"": 4, ""name"": ""작업2""}}"; MockHttpRequester.SetResponse(testUrl1, mockResponse1); MockHttpRequester.SetResponse(testUrl2, mockResponse2); // DataMask 설정 var dataMask = new DataMask(); dataMask["Id"] = 0; dataMask["name"] = ""; var dataMapper = new DataMapper(dataMask); // 두 개의 반복 요청 설정 var info1 = new HttpRequestConfig(testUrl1, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCallCount1++; }) .SetRepeat(true, 0, repeatInterval1, false); var info2 = new HttpRequestConfig(testUrl2, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCallCount2++; }) .SetRepeat(true, 0, repeatInterval2, false); pipeLine.UseMockup = true; pipeLine.Add("repeatTest1", info1); pipeLine.Add("repeatTest2", info2); try { // Act await pipeLine.Excute("repeatTest1"); await pipeLine.Excute("repeatTest2"); // 두 요청 모두 몇 번 실행되도록 대기 await UniTask.Delay(Math.Max(repeatInterval1, repeatInterval2) * 3 + 100 * 3 + 50);//100 네트워크 지연, 50 추가 여유 // 첫 번째 반복만 중지 await pipeLine.StopRepeat("repeatTest1"); // 호출 횟수 기록 int callCount1AfterStop = handlerCallCount1; int callCount2BeforeSecondStop = handlerCallCount2; // 두 번째 반복이 계속 실행되는지 확인하기 위해 대기 await UniTask.Delay(repeatInterval2 * 2 + 100 * 2 + 50); // Assert Assert.Greater(callCount1AfterStop, 0, "첫 번째 반복 요청 실행이 발생하지 않았습니다"); Assert.Greater(handlerCallCount2, callCount2BeforeSecondStop, "두 번째 반복이 계속 실행되지 않았습니다"); Assert.AreEqual(callCount1AfterStop, handlerCallCount1, "첫 번째 반복이 중지되지 않았습니다"); // 두 번째 반복도 중지 await pipeLine.StopRepeat("repeatTest2"); // 호출 횟수 다시 기록 int callCount2AfterStop = handlerCallCount2; // 더 이상 호출이 없는지 확인하기 위해 대기 await UniTask.Delay(Math.Max(repeatInterval1, repeatInterval2) * 2 + 100 * 2 + 50); // 모든 호출이 중지되었는지 확인 Assert.AreEqual(callCount1AfterStop, handlerCallCount1, "첫 번째 반복이 계속되었습니다"); Assert.AreEqual(callCount2AfterStop, handlerCallCount2, "두 번째 반복이 계속되었습니다"); } finally { // 테스트 정리 await pipeLine.RemoveAsync("repeatTest1"); await pipeLine.RemoveAsync("repeatTest2"); MockHttpRequester.ClearResponses(testUrl1); MockHttpRequester.ClearResponses(testUrl2); } } /// /// 지정된 횟수만큼 반복 실행 후 자동 중단되는 기능을 테스트합니다. /// [Test] public async UniTask Test_RepeatWithCount_StopsAutomatically() { // Arrange int handlerCallCount = 0; List receivedData = new List(); string testUrl = "http://test.com/repeat-count"; int repeatCount = 3; int repeatInterval = 100; // Mock 응답 설정 string mockResponse = @"{""message"": ""Success"", ""data"": {""Id"": 5, ""message"": ""자동 중단 테스트""}}"; MockHttpRequester.SetResponse(testUrl, mockResponse); // DataMask 설정 var dataMask = new DataMask(); dataMask["Id"] = 0; dataMask["message"] = ""; var dataMapper = new DataMapper(dataMask); // 반복 횟수가 지정된 HttpRequestConfig 생성 var info = new HttpRequestConfig(testUrl, "get") .SetDataMapper(dataMapper) .SetSuccessHandler((data) => { handlerCallCount++; receivedData.Add(data); }) .SetRepeat(true, repeatCount, repeatInterval, false); pipeLine.UseMockup = true; pipeLine.Add("countedRepeatTest", info); try { // Act await pipeLine.Excute("countedRepeatTest"); // 지정된 횟수의 반복이 완료될 때까지 충분히 대기 await UniTask.Delay(repeatInterval * (repeatCount + 1) + 50 + 100 * (repeatCount + 1)); // Assert Assert.AreEqual(repeatCount, handlerCallCount, "지정된 횟수만큼 반복 실행되지 않았습니다"); Assert.AreEqual(repeatCount, receivedData.Count, "기대한 데이터 수와 다릅니다"); // 각 응답이 올바르게 처리되었는지 확인 var data = receivedData[0]; Assert.IsNotNull(data, "데이터가 null입니다"); var dataObject = data as DataObject; Assert.AreEqual(5, dataObject?.GetInt("Id"), "ID가 올바르지 않습니다"); Assert.AreEqual("자동 중단 테스트", dataObject?.GetString("message"), "메시지가 올바르지 않습니다"); // 자동으로 제거되었는지 확인 var repeatTokenSources = GetRepeatTokenSourcesField(); Assert.IsFalse(repeatTokenSources.ContainsKey("countedRepeatTest"), "실행 완료 후 반복 토큰이 제거되지 않았습니다"); } finally { // 테스트 정리 await pipeLine.RemoveAsync("countedRepeatTest"); MockHttpRequester.ClearResponses(testUrl); } } /// /// HttpDataFetcher의 private repeatTokenSources 필드 가져오기 /// private Dictionary GetRepeatTokenSourcesField() { var fieldInfo = typeof(HttpDataFetcher).GetField("repeatTokenSources", BindingFlags.NonPublic | BindingFlags.Instance); return (Dictionary)fieldInfo.GetValue(pipeLine); } #region HttpResponseMask Tests /// /// HttpResponseMask.Apply가 성공적인 응답을 올바르게 처리하는지 테스트합니다. /// [Test] public void HttpResponseMask_Apply_SuccessfulResponse_ReturnsSuccessWithData() { // Arrange var responseMask = new HttpResponseMask(); // 기본값 사용 (successKey="message", successValue="success", dataKey="data") var jsonResponse = @"{""message"": ""success"", ""data"": {""key"":""value""}}"; var expectedData = @"{""key"":""value""}"; // Act var result = responseMask.Apply(jsonResponse); // Assert Assert.IsTrue(result.IsSuccess, "결과가 성공이어야 합니다."); Assert.AreEqual(expectedData, result.Data, "추출된 데이터가 예상과 다릅니다."); Assert.IsNull(result.Message, "성공 시 메시지는 null이어야 합니다."); } /// /// HttpResponseMask.Apply가 잘못된 성공 값으로 실패 응답을 올바르게 처리하는지 테스트합니다. /// [Test] public void HttpResponseMask_Apply_FailedResponse_WrongSuccessValue_ReturnsFailWithMessage() { // Arrange var responseMask = new HttpResponseMask(); var jsonResponse = @"{""message"": ""failed"", ""data"": {""key"": ""value""}}"; // successValue가 "success"가 아님 // Act var result = responseMask.Apply(jsonResponse); // Assert Assert.IsFalse(result.IsSuccess, "결과가 실패여야 합니다."); Assert.IsNull(result.Data, "실패 시 데이터는 null이어야 합니다."); Assert.AreEqual(jsonResponse, result.Message, "실패 시 메시지는 원본 응답이어야 합니다."); } /// /// HttpResponseMask.Apply가 성공 키가 없는 실패 응답을 올바르게 처리하는지 테스트합니다. /// [Test] public void HttpResponseMask_Apply_FailedResponse_MissingSuccessKey_ReturnsFailWithMessage() { // Arrange var responseMask = new HttpResponseMask(); var jsonResponse = @"{""error"": ""some error"", ""data"": {""key"": ""value""}}"; // "message" 키가 없음 // Act var result = responseMask.Apply(jsonResponse); // Assert Assert.IsFalse(result.IsSuccess, "결과가 실패여야 합니다."); Assert.IsNull(result.Data, "실패 시 데이터는 null이어야 합니다."); Assert.AreEqual(jsonResponse, result.Message, "실패 시 메시지는 원본 응답이어야 합니다."); } /// /// HttpResponseMask.Apply가 데이터 키가 없는 실패 응답을 올바르게 처리하는지 테스트합니다. /// (현재 구현상 successKey 조건만 만족하면 dataKey가 없어도 성공으로 간주하고 Data를 null로 반환할 수 있으므로, 이 테스트는 실패할 수 있습니다. /// 요구사항에 따라 이 부분의 동작을 명확히 하고 테스트를 조정해야 합니다.) /// [Test] public void HttpResponseMask_Apply_FailedResponse_MissingDataKey_ReturnsFailWithMessage() { // Arrange var responseMask = new HttpResponseMask(); // 성공 키는 있지만 데이터 키가 없는 경우 var jsonResponse = @"{""message"": ""success"", ""payload"": {""key"": ""value""}}"; // "data" 키가 없음 // Act var result = responseMask.Apply(jsonResponse); // Assert // 현재 Apply 메서드 구현에 따르면, successKey/successValue가 일치하고 dataKey가 없으면 // IsSuccess = false, Data = null 로 반환됩니다. // 만약 dataKey가 필수라면 Apply 메서드 수정 또는 이 테스트의 기대 결과 수정이 필요합니다. // 여기서는 현재 구현을 기준으로 테스트합니다. Assert.IsFalse(result.IsSuccess, "data 키가 없으면 success 조건 만족 시 실패여야 합니다 (현재 로직 기준)."); Assert.IsNull(result.Data, "data 키가 없는 경우 Data는 null이어야 합니다."); Assert.IsNotNull(result.Message, "실패 시 메시지는 null이 아니어야 합니다."); } /// /// HttpResponseMask.Apply가 사용자 정의 키를 사용하여 성공적인 응답을 올바르게 처리하는지 테스트합니다. /// [Test] public void HttpResponseMask_Apply_SuccessfulResponse_CustomKeys_ReturnsSuccessWithData() { // Arrange var responseMask = new HttpResponseMask("status", "ok", "payload"); var jsonResponse = @"{""status"": ""ok"", ""payload"": {""info"":""custom data""}}"; var expectedData = @"{""info"":""custom data""}"; // Act var result = responseMask.Apply(jsonResponse); // Assert Assert.IsTrue(result.IsSuccess, "결과가 성공이어야 합니다."); Assert.AreEqual(expectedData, result.Data, "추출된 데이터가 예상과 다릅니다."); Assert.IsNull(result.Message, "성공 시 메시지는 null이어야 합니다."); } /// /// HttpResponseMask.Apply가 잘못된 JSON 형식의 응답을 처리할 때 예외를 발생하는지 테스트합니다. /// [Test] public void HttpResponseMask_Apply_InvalidJson_ThrowsException() { // Arrange var responseMask = new HttpResponseMask(); var invalidJsonResponse = @"{""message"": ""success"", ""data"": {""key"": ""value"""; // 닫는 중괄호 누락 // Act & Assert Assert.Throws(() => responseMask.Apply(invalidJsonResponse), "잘못된 JSON 형식에 대해 JsonReaderException이 발생해야 합니다."); } #endregion #region DataValidator Tests /// /// DataValidator를 사용하여 유효한 데이터를 성공적으로 처리하는지 테스트합니다. /// [Test] public async UniTask Test_Excute_WithValidData_ValidatorPasses() { // Arrange bool handlerCalled = false; IDataObject? receivedData = null; string testUrl = "http://test.com/validator-pass"; var mockResponse = @"{""message"": ""Success"", ""data"": {""Id"": 1, ""status"": ""active""}}"; MockHttpRequester.SetResponse(testUrl, mockResponse); var dataMapper = new DataMapper(new DataMask { ["Id"] = 0, ["status"] = "" }); // "status" 필드가 "active"인 경우에만 유효하도록 설정 var validator = new DataValidator(); validator.AddValidator("status", value => { return value is string s && s == "active"; }); var info = new HttpRequestConfig(testUrl) .SetDataMapper(dataMapper) .SetValidator(validator) .SetSuccessHandler(data => { handlerCalled = true; receivedData = data; }) .SetFailHandler((message) => { Debug.LogError("Fail message: " + message); }); pipeLine.Add("validatorPassTest", info); try { // Act await pipeLine.Excute("validatorPassTest"); // Assert Assert.IsTrue(handlerCalled, "유효성 검사를 통과했으므로 핸들러가 호출되어야 합니다."); Assert.IsNotNull(receivedData, "데이터가 핸들러로 전달되어야 합니다."); Assert.AreEqual("active", (receivedData as DataObject)?.GetString("status")); } finally { await pipeLine.RemoveAsync("validatorPassTest"); MockHttpRequester.ClearResponses(testUrl); } } /// /// DataValidator를 사용하여 유효하지 않은 데이터를 필터링하는지 테스트합니다. /// [Test] public async UniTask Test_Excute_WithInvalidData_ValidatorFails() { // Arrange bool handlerCalled = false; string testUrl = "http://test.com/validator-fail"; var mockResponse = @"{""message"": ""Success"", ""data"": {""Id"": 2, ""status"": ""inactive""}}"; MockHttpRequester.SetResponse(testUrl, mockResponse); var dataMapper = new DataMapper(new DataMask { ["Id"] = 0, ["status"] = "" }); // "status" 필드가 "active"인 경우에만 유효하도록 설정 var validator = new DataValidator(); validator.AddValidator("status", value => value is string s && s == "active"); var info = new HttpRequestConfig(testUrl) .SetDataMapper(dataMapper) .SetValidator(validator) .SetSuccessHandler(data => { handlerCalled = true; // 이 핸들러는 호출되지 않아야 함 }); pipeLine.Add("validatorFailTest", info); try { // Act await pipeLine.Excute("validatorFailTest"); // Assert Assert.IsFalse(handlerCalled, "유효성 검사에 실패했으므로 핸들러가 호출되지 않아야 합니다."); } finally { await pipeLine.RemoveAsync("validatorFailTest"); MockHttpRequester.ClearResponses(testUrl); } } /// /// DataValidator가 배열 데이터에서 유효한 항목만 필터링하는지 테스트합니다. /// [Test] public async UniTask Test_Excute_WithArrayAndValidator_FiltersInvalidData() { // Arrange bool handlerCalled = false; IDataObject? receivedData = null; string testUrl = "http://test.com/validator-array"; var mockResponse = @"{ ""message"": ""Success"", ""data"": [ {""Id"": 1, ""value"": 10}, {""Id"": 2, ""value"": 20}, {""Id"": 3, ""value"": 5} ] }"; MockHttpRequester.SetResponse(testUrl, mockResponse); var dataMapper = new DataMapper(new DataMask { ["Id"] = 0, ["value"] = 0 }); // "value"가 15보다 큰 항목만 유효하도록 설정 var validator = new DataValidator(); validator.AddValidator("value", value => { return value is int v && v > 15; }); var info = new HttpRequestConfig(testUrl) .SetDataMapper(dataMapper) .SetValidator(validator) .SetSuccessHandler(data => { handlerCalled = true; receivedData = data; }); pipeLine.Add("validatorArrayTest", info); try { // Act await pipeLine.Excute("validatorArrayTest"); // Assert Assert.IsTrue(handlerCalled, "핸들러가 호출되어야 합니다."); Assert.IsNotNull(receivedData, "데이터가 핸들러로 전달되어야 합니다."); Assert.IsTrue(receivedData is DataArray, "결과는 DataArray여야 합니다."); var dataArray = receivedData as DataArray; Assert.AreEqual(1, dataArray.Count, "유효한 항목은 1개여야 합니다."); Assert.AreEqual(20, dataArray[0].GetInt("value"), "필터링된 데이터의 값이 올바르지 않습니다."); } finally { await pipeLine.RemoveAsync("validatorArrayTest"); MockHttpRequester.ClearResponses(testUrl); } } #endregion } }