task_definition_test.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. package base
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. // Test structs that mirror the actual configuration structure
  7. type TestBaseConfig struct {
  8. Enabled bool `json:"enabled"`
  9. ScanIntervalSeconds int `json:"scan_interval_seconds"`
  10. MaxConcurrent int `json:"max_concurrent"`
  11. }
  12. type TestTaskConfig struct {
  13. TestBaseConfig
  14. TaskSpecificField float64 `json:"task_specific_field"`
  15. AnotherSpecificField string `json:"another_specific_field"`
  16. }
  17. type TestNestedConfig struct {
  18. TestBaseConfig
  19. NestedStruct struct {
  20. NestedField string `json:"nested_field"`
  21. } `json:"nested_struct"`
  22. TaskField int `json:"task_field"`
  23. }
  24. func TestStructToMap_WithEmbeddedStruct(t *testing.T) {
  25. // Test case 1: Basic embedded struct
  26. config := &TestTaskConfig{
  27. TestBaseConfig: TestBaseConfig{
  28. Enabled: true,
  29. ScanIntervalSeconds: 1800,
  30. MaxConcurrent: 3,
  31. },
  32. TaskSpecificField: 0.25,
  33. AnotherSpecificField: "test_value",
  34. }
  35. result := StructToMap(config)
  36. // Verify all fields are present
  37. expectedFields := map[string]interface{}{
  38. "enabled": true,
  39. "scan_interval_seconds": 1800,
  40. "max_concurrent": 3,
  41. "task_specific_field": 0.25,
  42. "another_specific_field": "test_value",
  43. }
  44. if len(result) != len(expectedFields) {
  45. t.Errorf("Expected %d fields, got %d. Result: %+v", len(expectedFields), len(result), result)
  46. }
  47. for key, expectedValue := range expectedFields {
  48. if actualValue, exists := result[key]; !exists {
  49. t.Errorf("Missing field: %s", key)
  50. } else if !reflect.DeepEqual(actualValue, expectedValue) {
  51. t.Errorf("Field %s: expected %v (%T), got %v (%T)", key, expectedValue, expectedValue, actualValue, actualValue)
  52. }
  53. }
  54. }
  55. func TestStructToMap_WithNestedStruct(t *testing.T) {
  56. config := &TestNestedConfig{
  57. TestBaseConfig: TestBaseConfig{
  58. Enabled: false,
  59. ScanIntervalSeconds: 3600,
  60. MaxConcurrent: 1,
  61. },
  62. NestedStruct: struct {
  63. NestedField string `json:"nested_field"`
  64. }{
  65. NestedField: "nested_value",
  66. },
  67. TaskField: 42,
  68. }
  69. result := StructToMap(config)
  70. // Verify embedded struct fields are included
  71. if enabled, exists := result["enabled"]; !exists || enabled != false {
  72. t.Errorf("Expected enabled=false from embedded struct, got %v", enabled)
  73. }
  74. if scanInterval, exists := result["scan_interval_seconds"]; !exists || scanInterval != 3600 {
  75. t.Errorf("Expected scan_interval_seconds=3600 from embedded struct, got %v", scanInterval)
  76. }
  77. if maxConcurrent, exists := result["max_concurrent"]; !exists || maxConcurrent != 1 {
  78. t.Errorf("Expected max_concurrent=1 from embedded struct, got %v", maxConcurrent)
  79. }
  80. // Verify regular fields are included
  81. if taskField, exists := result["task_field"]; !exists || taskField != 42 {
  82. t.Errorf("Expected task_field=42, got %v", taskField)
  83. }
  84. // Verify nested struct is included as a whole
  85. if nestedStruct, exists := result["nested_struct"]; !exists {
  86. t.Errorf("Missing nested_struct field")
  87. } else {
  88. // The nested struct should be included as-is, not flattened
  89. if nested, ok := nestedStruct.(struct {
  90. NestedField string `json:"nested_field"`
  91. }); !ok || nested.NestedField != "nested_value" {
  92. t.Errorf("Expected nested_struct with NestedField='nested_value', got %v", nestedStruct)
  93. }
  94. }
  95. }
  96. func TestMapToStruct_WithEmbeddedStruct(t *testing.T) {
  97. // Test data with all fields including embedded struct fields
  98. data := map[string]interface{}{
  99. "enabled": true,
  100. "scan_interval_seconds": 2400,
  101. "max_concurrent": 5,
  102. "task_specific_field": 0.15,
  103. "another_specific_field": "updated_value",
  104. }
  105. config := &TestTaskConfig{}
  106. err := MapToStruct(data, config)
  107. if err != nil {
  108. t.Fatalf("MapToStruct failed: %v", err)
  109. }
  110. // Verify embedded struct fields were set
  111. if config.Enabled != true {
  112. t.Errorf("Expected Enabled=true, got %v", config.Enabled)
  113. }
  114. if config.ScanIntervalSeconds != 2400 {
  115. t.Errorf("Expected ScanIntervalSeconds=2400, got %v", config.ScanIntervalSeconds)
  116. }
  117. if config.MaxConcurrent != 5 {
  118. t.Errorf("Expected MaxConcurrent=5, got %v", config.MaxConcurrent)
  119. }
  120. // Verify regular fields were set
  121. if config.TaskSpecificField != 0.15 {
  122. t.Errorf("Expected TaskSpecificField=0.15, got %v", config.TaskSpecificField)
  123. }
  124. if config.AnotherSpecificField != "updated_value" {
  125. t.Errorf("Expected AnotherSpecificField='updated_value', got %v", config.AnotherSpecificField)
  126. }
  127. }
  128. func TestMapToStruct_PartialData(t *testing.T) {
  129. // Test with only some fields present (simulating form data)
  130. data := map[string]interface{}{
  131. "enabled": false,
  132. "max_concurrent": 2,
  133. "task_specific_field": 0.30,
  134. }
  135. // Start with some initial values
  136. config := &TestTaskConfig{
  137. TestBaseConfig: TestBaseConfig{
  138. Enabled: true,
  139. ScanIntervalSeconds: 1800,
  140. MaxConcurrent: 1,
  141. },
  142. TaskSpecificField: 0.20,
  143. AnotherSpecificField: "initial_value",
  144. }
  145. err := MapToStruct(data, config)
  146. if err != nil {
  147. t.Fatalf("MapToStruct failed: %v", err)
  148. }
  149. // Verify updated fields
  150. if config.Enabled != false {
  151. t.Errorf("Expected Enabled=false (updated), got %v", config.Enabled)
  152. }
  153. if config.MaxConcurrent != 2 {
  154. t.Errorf("Expected MaxConcurrent=2 (updated), got %v", config.MaxConcurrent)
  155. }
  156. if config.TaskSpecificField != 0.30 {
  157. t.Errorf("Expected TaskSpecificField=0.30 (updated), got %v", config.TaskSpecificField)
  158. }
  159. // Verify unchanged fields remain the same
  160. if config.ScanIntervalSeconds != 1800 {
  161. t.Errorf("Expected ScanIntervalSeconds=1800 (unchanged), got %v", config.ScanIntervalSeconds)
  162. }
  163. if config.AnotherSpecificField != "initial_value" {
  164. t.Errorf("Expected AnotherSpecificField='initial_value' (unchanged), got %v", config.AnotherSpecificField)
  165. }
  166. }
  167. func TestRoundTripSerialization(t *testing.T) {
  168. // Test complete round-trip: struct -> map -> struct
  169. original := &TestTaskConfig{
  170. TestBaseConfig: TestBaseConfig{
  171. Enabled: true,
  172. ScanIntervalSeconds: 3600,
  173. MaxConcurrent: 4,
  174. },
  175. TaskSpecificField: 0.18,
  176. AnotherSpecificField: "round_trip_test",
  177. }
  178. // Convert to map
  179. dataMap := StructToMap(original)
  180. // Convert back to struct
  181. roundTrip := &TestTaskConfig{}
  182. err := MapToStruct(dataMap, roundTrip)
  183. if err != nil {
  184. t.Fatalf("Round-trip MapToStruct failed: %v", err)
  185. }
  186. // Verify all fields match
  187. if !reflect.DeepEqual(original.TestBaseConfig, roundTrip.TestBaseConfig) {
  188. t.Errorf("BaseConfig mismatch:\nOriginal: %+v\nRound-trip: %+v", original.TestBaseConfig, roundTrip.TestBaseConfig)
  189. }
  190. if original.TaskSpecificField != roundTrip.TaskSpecificField {
  191. t.Errorf("TaskSpecificField mismatch: %v != %v", original.TaskSpecificField, roundTrip.TaskSpecificField)
  192. }
  193. if original.AnotherSpecificField != roundTrip.AnotherSpecificField {
  194. t.Errorf("AnotherSpecificField mismatch: %v != %v", original.AnotherSpecificField, roundTrip.AnotherSpecificField)
  195. }
  196. }
  197. func TestStructToMap_EmptyStruct(t *testing.T) {
  198. config := &TestTaskConfig{}
  199. result := StructToMap(config)
  200. // Should still include all fields, even with zero values
  201. expectedFields := []string{"enabled", "scan_interval_seconds", "max_concurrent", "task_specific_field", "another_specific_field"}
  202. for _, field := range expectedFields {
  203. if _, exists := result[field]; !exists {
  204. t.Errorf("Missing field: %s", field)
  205. }
  206. }
  207. }
  208. func TestStructToMap_NilPointer(t *testing.T) {
  209. var config *TestTaskConfig = nil
  210. result := StructToMap(config)
  211. if len(result) != 0 {
  212. t.Errorf("Expected empty map for nil pointer, got %+v", result)
  213. }
  214. }
  215. func TestMapToStruct_InvalidInput(t *testing.T) {
  216. data := map[string]interface{}{
  217. "enabled": "not_a_bool", // Wrong type
  218. }
  219. config := &TestTaskConfig{}
  220. err := MapToStruct(data, config)
  221. if err == nil {
  222. t.Errorf("Expected error for invalid input type, but got none")
  223. }
  224. }
  225. func TestMapToStruct_NonPointer(t *testing.T) {
  226. data := map[string]interface{}{
  227. "enabled": true,
  228. }
  229. config := TestTaskConfig{} // Not a pointer
  230. err := MapToStruct(data, config)
  231. if err == nil {
  232. t.Errorf("Expected error for non-pointer input, but got none")
  233. }
  234. }
  235. // Benchmark tests to ensure performance is reasonable
  236. func BenchmarkStructToMap(b *testing.B) {
  237. config := &TestTaskConfig{
  238. TestBaseConfig: TestBaseConfig{
  239. Enabled: true,
  240. ScanIntervalSeconds: 1800,
  241. MaxConcurrent: 3,
  242. },
  243. TaskSpecificField: 0.25,
  244. AnotherSpecificField: "benchmark_test",
  245. }
  246. b.ResetTimer()
  247. for i := 0; i < b.N; i++ {
  248. _ = StructToMap(config)
  249. }
  250. }
  251. func BenchmarkMapToStruct(b *testing.B) {
  252. data := map[string]interface{}{
  253. "enabled": true,
  254. "scan_interval_seconds": 1800,
  255. "max_concurrent": 3,
  256. "task_specific_field": 0.25,
  257. "another_specific_field": "benchmark_test",
  258. }
  259. b.ResetTimer()
  260. for i := 0; i < b.N; i++ {
  261. config := &TestTaskConfig{}
  262. _ = MapToStruct(data, config)
  263. }
  264. }
  265. func BenchmarkRoundTrip(b *testing.B) {
  266. original := &TestTaskConfig{
  267. TestBaseConfig: TestBaseConfig{
  268. Enabled: true,
  269. ScanIntervalSeconds: 1800,
  270. MaxConcurrent: 3,
  271. },
  272. TaskSpecificField: 0.25,
  273. AnotherSpecificField: "benchmark_test",
  274. }
  275. b.ResetTimer()
  276. for i := 0; i < b.N; i++ {
  277. dataMap := StructToMap(original)
  278. roundTrip := &TestTaskConfig{}
  279. _ = MapToStruct(dataMap, roundTrip)
  280. }
  281. }