You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
9.1 KiB
338 lines
9.1 KiB
package base
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// Test structs that mirror the actual configuration structure
|
|
type TestBaseConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
ScanIntervalSeconds int `json:"scan_interval_seconds"`
|
|
MaxConcurrent int `json:"max_concurrent"`
|
|
}
|
|
|
|
type TestTaskConfig struct {
|
|
TestBaseConfig
|
|
TaskSpecificField float64 `json:"task_specific_field"`
|
|
AnotherSpecificField string `json:"another_specific_field"`
|
|
}
|
|
|
|
type TestNestedConfig struct {
|
|
TestBaseConfig
|
|
NestedStruct struct {
|
|
NestedField string `json:"nested_field"`
|
|
} `json:"nested_struct"`
|
|
TaskField int `json:"task_field"`
|
|
}
|
|
|
|
func TestStructToMap_WithEmbeddedStruct(t *testing.T) {
|
|
// Test case 1: Basic embedded struct
|
|
config := &TestTaskConfig{
|
|
TestBaseConfig: TestBaseConfig{
|
|
Enabled: true,
|
|
ScanIntervalSeconds: 1800,
|
|
MaxConcurrent: 3,
|
|
},
|
|
TaskSpecificField: 0.25,
|
|
AnotherSpecificField: "test_value",
|
|
}
|
|
|
|
result := StructToMap(config)
|
|
|
|
// Verify all fields are present
|
|
expectedFields := map[string]interface{}{
|
|
"enabled": true,
|
|
"scan_interval_seconds": 1800,
|
|
"max_concurrent": 3,
|
|
"task_specific_field": 0.25,
|
|
"another_specific_field": "test_value",
|
|
}
|
|
|
|
if len(result) != len(expectedFields) {
|
|
t.Errorf("Expected %d fields, got %d. Result: %+v", len(expectedFields), len(result), result)
|
|
}
|
|
|
|
for key, expectedValue := range expectedFields {
|
|
if actualValue, exists := result[key]; !exists {
|
|
t.Errorf("Missing field: %s", key)
|
|
} else if !reflect.DeepEqual(actualValue, expectedValue) {
|
|
t.Errorf("Field %s: expected %v (%T), got %v (%T)", key, expectedValue, expectedValue, actualValue, actualValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStructToMap_WithNestedStruct(t *testing.T) {
|
|
config := &TestNestedConfig{
|
|
TestBaseConfig: TestBaseConfig{
|
|
Enabled: false,
|
|
ScanIntervalSeconds: 3600,
|
|
MaxConcurrent: 1,
|
|
},
|
|
NestedStruct: struct {
|
|
NestedField string `json:"nested_field"`
|
|
}{
|
|
NestedField: "nested_value",
|
|
},
|
|
TaskField: 42,
|
|
}
|
|
|
|
result := StructToMap(config)
|
|
|
|
// Verify embedded struct fields are included
|
|
if enabled, exists := result["enabled"]; !exists || enabled != false {
|
|
t.Errorf("Expected enabled=false from embedded struct, got %v", enabled)
|
|
}
|
|
|
|
if scanInterval, exists := result["scan_interval_seconds"]; !exists || scanInterval != 3600 {
|
|
t.Errorf("Expected scan_interval_seconds=3600 from embedded struct, got %v", scanInterval)
|
|
}
|
|
|
|
if maxConcurrent, exists := result["max_concurrent"]; !exists || maxConcurrent != 1 {
|
|
t.Errorf("Expected max_concurrent=1 from embedded struct, got %v", maxConcurrent)
|
|
}
|
|
|
|
// Verify regular fields are included
|
|
if taskField, exists := result["task_field"]; !exists || taskField != 42 {
|
|
t.Errorf("Expected task_field=42, got %v", taskField)
|
|
}
|
|
|
|
// Verify nested struct is included as a whole
|
|
if nestedStruct, exists := result["nested_struct"]; !exists {
|
|
t.Errorf("Missing nested_struct field")
|
|
} else {
|
|
// The nested struct should be included as-is, not flattened
|
|
if nested, ok := nestedStruct.(struct {
|
|
NestedField string `json:"nested_field"`
|
|
}); !ok || nested.NestedField != "nested_value" {
|
|
t.Errorf("Expected nested_struct with NestedField='nested_value', got %v", nestedStruct)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMapToStruct_WithEmbeddedStruct(t *testing.T) {
|
|
// Test data with all fields including embedded struct fields
|
|
data := map[string]interface{}{
|
|
"enabled": true,
|
|
"scan_interval_seconds": 2400,
|
|
"max_concurrent": 5,
|
|
"task_specific_field": 0.15,
|
|
"another_specific_field": "updated_value",
|
|
}
|
|
|
|
config := &TestTaskConfig{}
|
|
err := MapToStruct(data, config)
|
|
|
|
if err != nil {
|
|
t.Fatalf("MapToStruct failed: %v", err)
|
|
}
|
|
|
|
// Verify embedded struct fields were set
|
|
if config.Enabled != true {
|
|
t.Errorf("Expected Enabled=true, got %v", config.Enabled)
|
|
}
|
|
|
|
if config.ScanIntervalSeconds != 2400 {
|
|
t.Errorf("Expected ScanIntervalSeconds=2400, got %v", config.ScanIntervalSeconds)
|
|
}
|
|
|
|
if config.MaxConcurrent != 5 {
|
|
t.Errorf("Expected MaxConcurrent=5, got %v", config.MaxConcurrent)
|
|
}
|
|
|
|
// Verify regular fields were set
|
|
if config.TaskSpecificField != 0.15 {
|
|
t.Errorf("Expected TaskSpecificField=0.15, got %v", config.TaskSpecificField)
|
|
}
|
|
|
|
if config.AnotherSpecificField != "updated_value" {
|
|
t.Errorf("Expected AnotherSpecificField='updated_value', got %v", config.AnotherSpecificField)
|
|
}
|
|
}
|
|
|
|
func TestMapToStruct_PartialData(t *testing.T) {
|
|
// Test with only some fields present (simulating form data)
|
|
data := map[string]interface{}{
|
|
"enabled": false,
|
|
"max_concurrent": 2,
|
|
"task_specific_field": 0.30,
|
|
}
|
|
|
|
// Start with some initial values
|
|
config := &TestTaskConfig{
|
|
TestBaseConfig: TestBaseConfig{
|
|
Enabled: true,
|
|
ScanIntervalSeconds: 1800,
|
|
MaxConcurrent: 1,
|
|
},
|
|
TaskSpecificField: 0.20,
|
|
AnotherSpecificField: "initial_value",
|
|
}
|
|
|
|
err := MapToStruct(data, config)
|
|
|
|
if err != nil {
|
|
t.Fatalf("MapToStruct failed: %v", err)
|
|
}
|
|
|
|
// Verify updated fields
|
|
if config.Enabled != false {
|
|
t.Errorf("Expected Enabled=false (updated), got %v", config.Enabled)
|
|
}
|
|
|
|
if config.MaxConcurrent != 2 {
|
|
t.Errorf("Expected MaxConcurrent=2 (updated), got %v", config.MaxConcurrent)
|
|
}
|
|
|
|
if config.TaskSpecificField != 0.30 {
|
|
t.Errorf("Expected TaskSpecificField=0.30 (updated), got %v", config.TaskSpecificField)
|
|
}
|
|
|
|
// Verify unchanged fields remain the same
|
|
if config.ScanIntervalSeconds != 1800 {
|
|
t.Errorf("Expected ScanIntervalSeconds=1800 (unchanged), got %v", config.ScanIntervalSeconds)
|
|
}
|
|
|
|
if config.AnotherSpecificField != "initial_value" {
|
|
t.Errorf("Expected AnotherSpecificField='initial_value' (unchanged), got %v", config.AnotherSpecificField)
|
|
}
|
|
}
|
|
|
|
func TestRoundTripSerialization(t *testing.T) {
|
|
// Test complete round-trip: struct -> map -> struct
|
|
original := &TestTaskConfig{
|
|
TestBaseConfig: TestBaseConfig{
|
|
Enabled: true,
|
|
ScanIntervalSeconds: 3600,
|
|
MaxConcurrent: 4,
|
|
},
|
|
TaskSpecificField: 0.18,
|
|
AnotherSpecificField: "round_trip_test",
|
|
}
|
|
|
|
// Convert to map
|
|
dataMap := StructToMap(original)
|
|
|
|
// Convert back to struct
|
|
roundTrip := &TestTaskConfig{}
|
|
err := MapToStruct(dataMap, roundTrip)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Round-trip MapToStruct failed: %v", err)
|
|
}
|
|
|
|
// Verify all fields match
|
|
if !reflect.DeepEqual(original.TestBaseConfig, roundTrip.TestBaseConfig) {
|
|
t.Errorf("BaseConfig mismatch:\nOriginal: %+v\nRound-trip: %+v", original.TestBaseConfig, roundTrip.TestBaseConfig)
|
|
}
|
|
|
|
if original.TaskSpecificField != roundTrip.TaskSpecificField {
|
|
t.Errorf("TaskSpecificField mismatch: %v != %v", original.TaskSpecificField, roundTrip.TaskSpecificField)
|
|
}
|
|
|
|
if original.AnotherSpecificField != roundTrip.AnotherSpecificField {
|
|
t.Errorf("AnotherSpecificField mismatch: %v != %v", original.AnotherSpecificField, roundTrip.AnotherSpecificField)
|
|
}
|
|
}
|
|
|
|
func TestStructToMap_EmptyStruct(t *testing.T) {
|
|
config := &TestTaskConfig{}
|
|
result := StructToMap(config)
|
|
|
|
// Should still include all fields, even with zero values
|
|
expectedFields := []string{"enabled", "scan_interval_seconds", "max_concurrent", "task_specific_field", "another_specific_field"}
|
|
|
|
for _, field := range expectedFields {
|
|
if _, exists := result[field]; !exists {
|
|
t.Errorf("Missing field: %s", field)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStructToMap_NilPointer(t *testing.T) {
|
|
var config *TestTaskConfig = nil
|
|
result := StructToMap(config)
|
|
|
|
if len(result) != 0 {
|
|
t.Errorf("Expected empty map for nil pointer, got %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestMapToStruct_InvalidInput(t *testing.T) {
|
|
data := map[string]interface{}{
|
|
"enabled": "not_a_bool", // Wrong type
|
|
}
|
|
|
|
config := &TestTaskConfig{}
|
|
err := MapToStruct(data, config)
|
|
|
|
if err == nil {
|
|
t.Errorf("Expected error for invalid input type, but got none")
|
|
}
|
|
}
|
|
|
|
func TestMapToStruct_NonPointer(t *testing.T) {
|
|
data := map[string]interface{}{
|
|
"enabled": true,
|
|
}
|
|
|
|
config := TestTaskConfig{} // Not a pointer
|
|
err := MapToStruct(data, config)
|
|
|
|
if err == nil {
|
|
t.Errorf("Expected error for non-pointer input, but got none")
|
|
}
|
|
}
|
|
|
|
// Benchmark tests to ensure performance is reasonable
|
|
func BenchmarkStructToMap(b *testing.B) {
|
|
config := &TestTaskConfig{
|
|
TestBaseConfig: TestBaseConfig{
|
|
Enabled: true,
|
|
ScanIntervalSeconds: 1800,
|
|
MaxConcurrent: 3,
|
|
},
|
|
TaskSpecificField: 0.25,
|
|
AnotherSpecificField: "benchmark_test",
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = StructToMap(config)
|
|
}
|
|
}
|
|
|
|
func BenchmarkMapToStruct(b *testing.B) {
|
|
data := map[string]interface{}{
|
|
"enabled": true,
|
|
"scan_interval_seconds": 1800,
|
|
"max_concurrent": 3,
|
|
"task_specific_field": 0.25,
|
|
"another_specific_field": "benchmark_test",
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
config := &TestTaskConfig{}
|
|
_ = MapToStruct(data, config)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRoundTrip(b *testing.B) {
|
|
original := &TestTaskConfig{
|
|
TestBaseConfig: TestBaseConfig{
|
|
Enabled: true,
|
|
ScanIntervalSeconds: 1800,
|
|
MaxConcurrent: 3,
|
|
},
|
|
TaskSpecificField: 0.25,
|
|
AnotherSpecificField: "benchmark_test",
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
dataMap := StructToMap(original)
|
|
roundTrip := &TestTaskConfig{}
|
|
_ = MapToStruct(dataMap, roundTrip)
|
|
}
|
|
}
|