using Newtonsoft.Json.Linq; using NUnit.Framework; using System; using System.Collections.Generic; using UnityEngine; using UVC.Data.Core; namespace UVC.Tests.Data { /// /// DataMapper 클래스의 테스트를 위한 테스트 클래스입니다. /// [TestFixture] public class DataMapperTests { /// /// 모든 테스트 메서드를 실행하는 메서드입니다. /// /// /// 이 메서드는 클래스의 모든 테스트 메서드를 순차적으로 호출하고 /// 각 테스트의 성공 또는 실패 여부를 로그로 출력합니다. /// public void TestAll() { Debug.Log("===== DataMapper 테스트 시작 ====="); RunTest(nameof(Map_StringProperty_MapsCorrectly), Map_StringProperty_MapsCorrectly); RunTest(nameof(Map_IntProperty_MapsCorrectly), Map_IntProperty_MapsCorrectly); RunTest(nameof(Map_DoubleProperty_MapsCorrectly), Map_DoubleProperty_MapsCorrectly); RunTest(nameof(Map_BoolProperty_MapsCorrectly), Map_BoolProperty_MapsCorrectly); RunTest(nameof(Map_DateTimeProperty_MapsCorrectly), Map_DateTimeProperty_MapsCorrectly); RunTest(nameof(Map_DataMapProperty_MapsCorrectly), Map_DataMapProperty_MapsCorrectly); RunTest(nameof(Map_DataMapWithUnmappedValue_ReturnsOriginal), Map_DataMapWithUnmappedValue_ReturnsOriginal); RunTest(nameof(Map_EnumProperty_MapsCorrectly), Map_EnumProperty_MapsCorrectly); RunTest(nameof(Map_AdditionalProperty_AddsToResult), Map_AdditionalProperty_AddsToResult); RunTest(nameof(Map_InvalidDateTimeString_ReturnsNull), Map_InvalidDateTimeString_ReturnsNull); RunTest(nameof(Map_ComplexObject_MapsAllProperties), Map_ComplexObject_MapsAllProperties); RunTest(nameof(Map_NestedObject_MapsCorrectly), Map_NestedObject_MapsCorrectly); RunTest(nameof(Map_ArrayMapping_MapsCorrectly), Map_ArrayMapping_MapsCorrectly); RunTest(nameof(Map_EmptyMaskArray_CopiesSourceArray), Map_EmptyMaskArray_CopiesSourceArray); RunTest(nameof(Map_ComplexNestedStructure_MapsCorrectly), Map_ComplexNestedStructure_MapsCorrectly); RunTest(nameof(Map_MixedArrayTypes_HandlesCorrectly), Map_MixedArrayTypes_HandlesCorrectly); RunTest(nameof(Map_WithNamesForReplace_ShouldRenameProperties), Map_WithNamesForReplace_ShouldRenameProperties); RunTest(nameof(Map_DeepClone_CopiesAllProperties), Map_DeepClone_CopiesAllProperties); RunTest(nameof(Map_ToJObject_ConvertsCorrectly), Map_ToJObject_ConvertsCorrectly); RunTest(nameof(Map_ParallelArrayProcessing_WorksCorrectly), Map_ParallelArrayProcessing_WorksCorrectly); RunTest(nameof(Map_RecursionDepthLimit_HandlesCorrectly), Map_RecursionDepthLimit_HandlesCorrectly); Debug.Log("===== DataMapper 테스트 완료 ====="); } /// /// 단일 테스트 메서드를 실행하고 결과를 로그로 출력합니다. /// /// 테스트 메서드 이름 /// 실행할 테스트 메서드 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}"); } } /// /// 문자열 타입 매핑 테스트 /// [Test] public void Map_StringProperty_MapsCorrectly() { // Arrange var mask = new DataMask(); mask["Name"] = "홍길동"; mask.ObjectIdKey = "Name"; // Id로 사용할 키 설정 var source = JObject.Parse(@"{ ""Name"": ""김철수"" }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Name")); Assert.AreEqual("김철수", result.GetString("Name")); Assert.AreEqual("김철수", result.Id); } /// /// 정수 타입 매핑 테스트 /// [Test] public void Map_IntProperty_MapsCorrectly() { // Arrange var mask = new DataMask(); mask["Age"] = 25; mask.ObjectIdKey = "Age"; // Id로 사용할 키 설정 var source = JObject.Parse(@"{ ""Age"": 30 }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Age")); Assert.AreEqual(30, result.GetInt("Age")); Assert.AreEqual("30", result.Id); } /// /// 부동소수점 타입 매핑 테스트 /// [Test] public void Map_DoubleProperty_MapsCorrectly() { // Arrange var mask = new DataMask(); mask["Height"] = 175.5; mask.ObjectIdKey = "Height"; // Id로 사용할 키 설정 var source = JObject.Parse(@"{ ""Height"": 180.5 }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Height")); Assert.AreEqual(180.5, result.GetDouble("Height")); Assert.AreEqual("180.5", result.Id); } /// /// Boolean 타입 매핑 테스트 /// [Test] public void Map_BoolProperty_MapsCorrectly() { // Arrange var mask = new DataMask(); mask["IsActive"] = false; var source = JObject.Parse(@"{ ""IsActive"": true }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("IsActive")); Assert.IsTrue(result.GetBool("IsActive")); Assert.AreEqual("True", result.Id);// Id는 Boolean 값의 문자열 표현으로 설정됨(첫글자 대문자) } /// /// DateTime 타입 매핑 테스트 /// [Test] public void Map_DateTimeProperty_MapsCorrectly() { // Arrange var mask = new DataMask(); mask["BirthDate"] = JToken.FromObject(DateTime.Now); var expectedDate = new DateTime(1990, 1, 1); var source = JObject.Parse(@"{ ""BirthDate"": ""1990-01-01T00:00:00.000"" }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("BirthDate")); Assert.AreEqual(expectedDate, result.GetDateTime("BirthDate")); } /// /// DataMap 타입 매핑 테스트 /// [Test] public void Map_DataMapProperty_MapsCorrectly() { // Arrange var mask = new DataMask(); var valueMapper = new DataMap(); valueMapper["ON"] = "활성화"; valueMapper["OFF"] = "비활성화"; mask["Status"] = JToken.FromObject(valueMapper); var source = JObject.Parse(@"{ ""Status"": ""ON"" }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Status")); Assert.AreEqual("활성화", result.GetString("Status")); } /// /// DataMap에 매핑되지 않는 값 처리 테스트 /// [Test] public void Map_DataMapWithUnmappedValue_ReturnsOriginal() { // Arrange var mask = new DataMask(); var valueMapper = new DataMap(); valueMapper["ON"] = "활성화"; valueMapper["OFF"] = "비활성화"; mask["Status"] = JToken.FromObject(valueMapper); var source = JObject.Parse(@"{ ""Status"": ""UNKNOWN"" }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Status")); Assert.AreEqual("UNKNOWN", result.GetString("Status")); } /// /// 열거형 타입 매핑 테스트 /// [Test] public void Map_EnumProperty_MapsCorrectly() { // Arrange var mask = new DataMask(); mask["Status"] = JToken.FromObject(UserStatus.Inactive); var source = JObject.Parse(@"{ ""Status"": ""Active"" }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Status")); Assert.AreEqual(UserStatus.Active, result.GetEnum("Status")); } /// /// 타겟에 없는 추가 속성 매핑 테스트 /// [Test] public void Map_AdditionalProperty_AddsToResult() { // Arrange var mask = new DataMask(); mask["Name"] = "홍길동"; var source = JObject.Parse(@"{ ""Name"": ""김철수"", ""Email"": ""kim@example.com"" }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsFalse(result.ContainsKey("Email")); Assert.AreEqual(null, result.GetString("Email")); } /// /// 유효하지 않은 DateTime 문자열 매핑 테스트 /// [Test] public void Map_InvalidDateTimeString_ReturnsNull() { // Arrange var mask = new DataMask(); mask["BirthDate"] = JToken.FromObject(DateTime.Now); var source = JObject.Parse(@"{ ""BirthDate"": ""InvalidDateTime"" }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("BirthDate")); Assert.IsNull(result["BirthDate"]); } /// /// 복합 객체 매핑 테스트 /// [Test] public void Map_ComplexObject_MapsAllProperties() { // Arrange var mask = new DataMask(); mask["Id"] = 1; mask["Name"] = "홍길동"; mask["IsActive"] = false; mask["JoinDate"] = JToken.FromObject(DateTime.Now); mask["Height"] = 175.5; mask["Status"] = JToken.FromObject(UserStatus.Inactive); string json = @"{ ""Id"": 100, ""Name"": ""김철수"", ""IsActive"": true, ""JoinDate"": ""2023-01-01T00:00:00"", ""Height"": 180.0, ""Status"": ""Active"", ""Email"": ""kim@example.com"" }"; var source = JObject.Parse(json); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.AreEqual(6, result.Count); Assert.AreEqual(100, result.GetInt("Id")); Assert.AreEqual("김철수", result.GetString("Name")); Assert.IsTrue(result.GetBool("IsActive")); Assert.AreEqual(new DateTime(2023, 1, 1), result.GetDateTime("JoinDate")); Assert.AreEqual(180.0, result.GetDouble("Height")); Assert.AreEqual(UserStatus.Active, result.GetEnum("Status")); Assert.AreEqual(null, result.GetString("Email")); } /// /// 중첩된 객체 매핑 테스트 /// [Test] public void Map_NestedObject_MapsCorrectly() { // Arrange var mask = new DataMask { ["User"] = new DataMask { ["Name"] = "홍길동", ["Address"] = new DataMask { ["City"] = "서울", ["ZipCode"] = "12345" } } }; var source = JObject.Parse(@"{ ""User"": { ""Name"": ""김철수"", ""Address"": { ""City"": ""부산"", ""ZipCode"": ""67890"", ""Country"": ""대한민국"" } } }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("User")); var user = result.GetDataObject("User"); Assert.IsNotNull(user); Assert.AreEqual("김철수", user.GetString("Name")); var address = user.GetDataObject("Address"); Assert.IsNotNull(address); Assert.AreEqual("부산", address.GetString("City")); Assert.AreEqual("67890", address.GetString("ZipCode")); Assert.AreEqual(null, address.GetString("Country")); } /// /// 배열 매핑 테스트 /// [Test] public void Map_ArrayMapping_MapsCorrectly() { // Arrange var mask = new DataMask { ["Contacts"] = new List { new DataMask { ["Type"] = "mobile", ["Number"] = "010-0000-0000" } } }; var source = JObject.Parse(@"{ ""Contacts"": [ { ""Type"": ""mobile"", ""Number"": ""010-1234-5678"" }, { ""Type"": ""home"", ""Number"": ""02-123-4567"", ""Extension"": ""123"" } ] }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Contacts")); var contacts = result.GetDataArray("Contacts"); Assert.IsNotNull(contacts); Assert.AreEqual(2, contacts.Count); var contact1 = contacts[0]; Assert.IsNotNull(contact1); Assert.AreEqual("mobile", contact1.GetString("Type")); Assert.AreEqual("010-1234-5678", contact1.GetString("Number")); var contact2 = contacts[1]; Assert.IsNotNull(contact2); Assert.AreEqual("home", contact2.GetString("Type")); Assert.AreEqual("02-123-4567", contact2.GetString("Number")); Assert.AreEqual(null, contact2.GetString("Extension")); } /// /// 빈 가이드 배열 테스트 /// [Test] public void Map_EmptyMaskArray_CopiesSourceArray() { // Arrange var mask = new DataMask { ["Tags"] = new List() }; var source = JObject.Parse(@"{ ""Tags"": [""개발"", ""테스트"", ""배포""] }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Tags")); var tags = result.GetDataArray("Tags"); Assert.IsNotNull(tags); Assert.AreEqual(3, tags.Count); // DataArray의 항목들을 검증 for (int i = 0; i < tags.Count; i++) { var item = tags[i]; Assert.IsNotNull(item); // DataObject는 "value" 키에 실제 값을 저장 Assert.AreEqual((source["Tags"])[i].ToString(), item.GetString("value")); } } /// /// 복잡한 중첩 구조 매핑 테스트 /// [Test] public void Map_ComplexNestedStructure_MapsCorrectly() { // Arrange var mask = new DataMask { ["Company"] = new DataMask { ["Name"] = "회사명", ["Founded"] = JToken.FromObject(DateTime.Now), ["Departments"] = new List { new DataMask { ["Name"] = "부서명", ["Employees"] = new List { new DataMask { ["Name"] = "직원명", ["Age"] = 30, ["Status"] = JToken.FromObject(UserStatus.Active) } } } } } }; var source = JObject.Parse(@"{ ""Company"": { ""Name"": ""XYZ 주식회사"", ""Founded"": ""2000-01-01T00:00:00"", ""Departments"": [ { ""Name"": ""개발부"", ""Employees"": [ { ""Name"": ""김개발"", ""Age"": 35, ""Status"": ""Active"" }, { ""Name"": ""이테스트"", ""Age"": 28, ""Status"": ""Inactive"" } ] }, { ""Name"": ""마케팅부"", ""Employees"": [ { ""Name"": ""박마케팅"", ""Age"": 32, ""Status"": ""Active"" } ], ""Budget"": 500000 } ], ""Address"": ""서울시 강남구"" } }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert var company = result.GetDataObject("Company"); Assert.IsNotNull(company); Assert.AreEqual("XYZ 주식회사", company.GetString("Name")); Assert.AreEqual(new DateTime(2000, 1, 1), company.GetDateTime("Founded")); Assert.AreEqual(null, company.GetString("Address")); var departments = company.GetDataArray("Departments"); Assert.IsNotNull(departments); Assert.AreEqual(2, departments.Count); var devDept = departments[0]; Assert.AreEqual("개발부", devDept.GetString("Name")); var devEmployees = devDept.GetDataArray("Employees"); Assert.AreEqual(2, devEmployees.Count); Assert.AreEqual("김개발", devEmployees[0].GetString("Name")); Assert.AreEqual(35, devEmployees[0].GetInt("Age")); Assert.AreEqual(UserStatus.Active, devEmployees[0].GetEnum("Status")); Assert.AreEqual("이테스트", devEmployees[1].GetString("Name")); Assert.AreEqual(UserStatus.Inactive, devEmployees[1].GetEnum("Status")); var marketingDept = departments[1]; Assert.AreEqual("마케팅부", marketingDept.GetString("Name")); Assert.AreEqual(0, marketingDept.GetInt("Budget")); var marketingEmployees = marketingDept.GetDataArray("Employees"); Assert.AreEqual(1, marketingEmployees.Count); Assert.AreEqual("박마케팅", marketingEmployees[0].GetString("Name")); } /// /// 다양한 형식의 배열 테스트 /// [Test] public void Map_MixedArrayTypes_HandlesCorrectly() { // Arrange var mask = new DataMask(@"{ ""MixedArray"": [ { ""Key"": ""Value"", ""Key1"": 1, ""Key2"": true}, ] }"); var source = JObject.Parse(@"{ ""MixedArray"": [ { ""Key"": ""NewValue"", ""Key1"": 456, ""Key2"": false, ""Extra"": ""ExtraValue"" }, { ""items"": [1, 2, 3] } ] }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("MixedArray")); var mixedArray = result.GetDataArray("MixedArray"); Assert.IsNotNull(mixedArray); Assert.AreEqual(2, mixedArray.Count); // 문자열 항목 검증 Assert.AreEqual("NewValue", mixedArray[0].GetString("Key")); // 숫자 항목 검증 Assert.AreEqual(456, mixedArray[0].GetInt("Key1")); // 불리언 항목 검증 Assert.AreEqual(false, mixedArray[0].GetBool("Key2")); // 객체 항목 검증 var objInArray = mixedArray[0]; Assert.IsNotNull(objInArray); Assert.AreEqual("NewValue", objInArray.GetString("Key")); } /// /// NamesForReplace 속성이 제대로 구현되었는지 테스트합니다. /// [Test] public void Map_WithNamesForReplace_ShouldRenameProperties() { // Arrange var mask = new DataMask(); mask["OldName"] = "홍길동"; mask["Age"] = 25; mask.NamesForReplace = new Dictionary { { "OldName", "NewName" } }; var source = JObject.Parse(@"{ ""OldName"": ""김철수"", ""Age"": 30 }"); var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("NewName")); Assert.AreEqual("김철수", result.GetString("NewName")); Assert.IsFalse(result.ContainsKey("OldName")); Assert.IsTrue(result.ContainsKey("Age")); Assert.AreEqual(30, result.GetInt("Age")); } /// /// DataMask의 DeepClone 메서드가 모든 속성을 올바르게 복사하는지 테스트합니다. /// [Test] public void Map_DeepClone_CopiesAllProperties() { // Arrange var originalMask = new DataMask(); originalMask["Name"] = "홍길동"; originalMask["Age"] = 25; originalMask.ObjectIdKey = "Name"; originalMask.ObjectName = "Person"; originalMask.NamesForReplace = new Dictionary { { "full_name", "Name" }, { "user_age", "Age" } }; // Act var clonedMask = originalMask.DeepClone(); // 원본 마스크 수정 originalMask["Name"] = "변경된 이름"; originalMask.ObjectName = "ModifiedPerson"; originalMask.NamesForReplace["full_name"] = "FullName"; // Assert - 클론은 변경되지 않아야 함 Assert.AreEqual("홍길동", clonedMask["Name"]); Assert.AreEqual(25, clonedMask["Age"]); Assert.AreEqual("Name", clonedMask.ObjectIdKey); Assert.AreEqual("Person", clonedMask.ObjectName); Assert.AreEqual("Name", clonedMask.NamesForReplace["full_name"]); Assert.AreEqual("Age", clonedMask.NamesForReplace["user_age"]); // 원본 마스크는 변경되어야 함 Assert.AreEqual("변경된 이름", originalMask["Name"]); Assert.AreEqual("ModifiedPerson", originalMask.ObjectName); Assert.AreEqual("FullName", originalMask.NamesForReplace["full_name"]); } /// /// DataMask의 ToJObject 메서드가 올바르게 동작하는지 테스트합니다. /// [Test] public void Map_ToJObject_ConvertsCorrectly() { // Arrange var mask = new DataMask(); mask["Name"] = "홍길동"; mask["Age"] = 25; mask["IsActive"] = true; mask["Height"] = 175.5; // Act var jObject = mask.ToJObject(); // Assert Assert.IsNotNull(jObject); Assert.AreEqual(4, jObject.Count); Assert.AreEqual("홍길동", jObject["Name"].ToString()); Assert.AreEqual(25, jObject["Age"].ToObject()); Assert.AreEqual(true, jObject["IsActive"].ToObject()); Assert.AreEqual(175.5, jObject["Height"].ToObject()); } /// /// DataMapper의 병렬 처리 기능이 대용량 배열에서 올바르게 동작하는지 테스트합니다. /// [Test] public void Map_ParallelArrayProcessing_WorksCorrectly() { // Arrange var mask = new DataMask(); mask["Items"] = new List { new DataMask { ["Id"] = 0, ["Name"] = "" } }; // 2000개 아이템을 가진 소스 배열 생성 var itemsArray = new JArray(); for (int i = 1; i <= 2000; i++) { itemsArray.Add(new JObject { ["Id"] = i, ["Name"] = $"Item {i}" }); } var source = new JObject { ["Items"] = itemsArray }; var mapper = new DataMapper(mask); // Act var result = mapper.Map(source); // Assert var items = result.GetDataArray("Items"); Assert.IsNotNull(items); Assert.AreEqual(2000, items.Count); // 결과 검증 - 몇 개 아이템만 샘플링 Assert.AreEqual(1, items[0].GetInt("Id")); Assert.AreEqual("Item 1", items[0].GetString("Name")); Assert.AreEqual(1000, items[999].GetInt("Id")); Assert.AreEqual("Item 1000", items[999].GetString("Name")); Assert.AreEqual(2000, items[1999].GetInt("Id")); Assert.AreEqual("Item 2000", items[1999].GetString("Name")); } /// /// 재귀 깊이 제한이 올바르게 동작하는지 테스트합니다. /// [Test] public void Map_RecursionDepthLimit_HandlesCorrectly() { // Arrange - 매우 깊게 중첩된 JSON 객체 생성 var mask = CreateDeepNestedMask(20); var source = CreateDeepNestedJObject(20); var mapper = new DataMapper(mask); string maskJsonString = mask.ToJsonString(); // Act - 예외 없이 매핑이 수행되어야 함 var result = mapper.Map(source); // Assert - 최대 중첩 깊이(현재 10)까지 매핑된 구조 확인 var level1 = result.GetDataObject("Level20"); Assert.IsNotNull(level1); var level5 = level1.GetDataObject("Level15") .GetDataObject("Level14") .GetDataObject("Level13") .GetDataObject("Level12"); Assert.IsNotNull(level5); // 깊이 제한 이후 원본 객체를 그대로 반환하므로 // 원래 깊이보다 더 깊게 들어갈 수 있어야 함 var level10 = level5.GetDataObject("Level10") .GetDataObject("Level9") .GetDataObject("Level8") .GetDataObject("Level7") .GetDataObject("Level6"); Assert.IsNotNull(level10); } /// /// 테스트용 깊게 중첩된 DataMask 생성 /// private DataMask CreateDeepNestedMask(int depth) { if (depth <= 0) return new DataMask { ["Value"] = "마스크 값" }; var mask = new DataMask(); mask[$"Level{depth}"] = CreateDeepNestedMask(depth - 1); return mask; } /// /// 테스트용 깊게 중첩된 JObject 생성 /// private JObject CreateDeepNestedJObject(int depth) { if (depth <= 0) return new JObject { ["Value"] = "소스 값" }; var obj = new JObject(); obj[$"Level{depth}"] = CreateDeepNestedJObject(depth - 1); return obj; } } /// /// 테스트에 사용할 샘플 열거형 /// public enum UserStatus { Active, Inactive, Suspended } }