package keyvalue import ( "bytes" "reflect" "sort" "testing" "time" ) // TestStruct is a test struct with various field types and env tags type TestStruct struct { StringField string `env:"STRING_FIELD"` IntField int `env:"INT_FIELD"` BoolField bool `env:"BOOL_FIELD"` DurationField time.Duration `env:"DURATION_FIELD"` StringSlice []string `env:"STRING_SLICE"` EmptySlice []string `env:"EMPTY_SLICE"` NoTagField string // Field without env tag EmbeddedStruct struct{} // Embedded struct } // EmbeddedTestStruct is a test struct with an embedded struct type EmbeddedTestStruct struct { TestStruct AdditionalField string `env:"ADDITIONAL_FIELD"` } func TestEnvKV(t *testing.T) { // Test case 1: Basic struct with various field types testStruct := TestStruct{ StringField: "test-string", IntField: 42, BoolField: true, DurationField: 5 * time.Second, StringSlice: []string{"one", "two", "three"}, EmptySlice: []string{}, NoTagField: "no-tag", } kvs := EnvKV(testStruct) // Expected key-value pairs expected := KVSlice{ {Key: "STRING_FIELD", Value: "test-string"}, {Key: "INT_FIELD", Value: "42"}, {Key: "BOOL_FIELD", Value: "true"}, {Key: "DURATION_FIELD", Value: "5s"}, {Key: "STRING_SLICE", Value: "one,two,three"}, {Key: "EMPTY_SLICE", Value: ""}, } // Check if the number of key-value pairs is correct if len(kvs) != len(expected) { t.Errorf("Expected %d key-value pairs, got %d", len(expected), len(kvs)) } // Create a map for easier comparison kvMap := make(map[string]string) for _, kv := range kvs { kvMap[kv.Key] = kv.Value } // Check if all expected key-value pairs are present for _, kv := range expected { if val, ok := kvMap[kv.Key]; !ok || val != kv.Value { t.Errorf("Expected key %s with value %s, got %s", kv.Key, kv.Value, val) } } // Test case 2: Struct with embedded struct embeddedStruct := EmbeddedTestStruct{ TestStruct: TestStruct{ StringField: "embedded-string", IntField: 100, }, AdditionalField: "additional", } embeddedKVs := EnvKV(embeddedStruct) // Check if the additional field is present found := false for _, kv := range embeddedKVs { if kv.Key == "ADDITIONAL_FIELD" && kv.Value == "additional" { found = true break } } if !found { t.Errorf("Expected to find ADDITIONAL_FIELD with value 'additional' in embedded struct") } // Test case 3: Pointer to a struct pointerStruct := &TestStruct{ StringField: "pointer-string", IntField: 123, BoolField: false, DurationField: 10 * time.Second, StringSlice: []string{"four", "five", "six"}, } pointerKVs := EnvKV(pointerStruct) // Expected key-value pairs for pointer struct expectedPointer := KVSlice{ {Key: "STRING_FIELD", Value: "pointer-string"}, {Key: "INT_FIELD", Value: "123"}, {Key: "BOOL_FIELD", Value: "false"}, {Key: "DURATION_FIELD", Value: "10s"}, {Key: "STRING_SLICE", Value: "four,five,six"}, {Key: "EMPTY_SLICE", Value: ""}, } // Check if the number of key-value pairs is correct if len(pointerKVs) != len(expectedPointer) { t.Errorf("Expected %d key-value pairs for pointer struct, got %d", len(expectedPointer), len(pointerKVs)) } // Create a map for easier comparison pointerKVMap := make(map[string]string) for _, kv := range pointerKVs { pointerKVMap[kv.Key] = kv.Value } // Check if all expected key-value pairs are present for _, kv := range expectedPointer { if val, ok := pointerKVMap[kv.Key]; !ok || val != kv.Value { t.Errorf("Expected key %s with value %s for pointer struct, got %s", kv.Key, kv.Value, val) } } // Test case 4: Nil pointer var nilPointer *TestStruct = nil nilKVs := EnvKV(nilPointer) // Nil pointer should return empty slice if len(nilKVs) != 0 { t.Errorf("Expected empty slice for nil pointer, got %d items", len(nilKVs)) } } func TestPrintEnv(t *testing.T) { // Test case 1: Regular struct testStruct := TestStruct{ StringField: "test-string", IntField: 42, BoolField: true, DurationField: 5 * time.Second, StringSlice: []string{"one", "two", "three"}, } // Create a buffer to capture the output var buf bytes.Buffer // Call PrintEnv PrintEnv(testStruct, &buf) // Get the output as a string output := buf.String() // Expected output (note: keys should be sorted) expected := "#!/usr/bin/env bash\n" + "export BOOL_FIELD=true\n" + "export DURATION_FIELD=5s\n" + "export EMPTY_SLICE=\n" + "export INT_FIELD=42\n" + "export STRING_FIELD=test-string\n" + "export STRING_SLICE=one,two,three\n" if output != expected { t.Errorf("PrintEnv output does not match expected.\nExpected:\n%s\nGot:\n%s", expected, output) } // Test case 2: Pointer to struct pointerStruct := &TestStruct{ StringField: "pointer-string", IntField: 123, BoolField: false, DurationField: 10 * time.Second, StringSlice: []string{"four", "five", "six"}, } // Create a new buffer for the pointer test var pointerBuf bytes.Buffer // Call PrintEnv with a pointer PrintEnv(pointerStruct, &pointerBuf) // Get the output as a string pointerOutput := pointerBuf.String() // Expected output for pointer struct expectedPointer := "#!/usr/bin/env bash\n" + "export BOOL_FIELD=false\n" + "export DURATION_FIELD=10s\n" + "export EMPTY_SLICE=\n" + "export INT_FIELD=123\n" + "export STRING_FIELD=pointer-string\n" + "export STRING_SLICE=four,five,six\n" if pointerOutput != expectedPointer { t.Errorf("PrintEnv output for pointer struct does not match expected.\nExpected:\n%s\nGot:\n%s", expectedPointer, pointerOutput) } // Test case 3: Nil pointer var nilPointer *TestStruct = nil var nilBuf bytes.Buffer // Call PrintEnv with a nil pointer PrintEnv(nilPointer, &nilBuf) // Get the output as a string nilOutput := nilBuf.String() // Expected output for nil pointer (just the shebang line) expectedNil := "#!/usr/bin/env bash\n" if nilOutput != expectedNil { t.Errorf("PrintEnv output for nil pointer does not match expected.\nExpected:\n%s\nGot:\n%s", expectedNil, nilOutput) } } func TestKVSliceSorting(t *testing.T) { // Create an unsorted KVSlice kvs := KVSlice{ {Key: "C", Value: "3"}, {Key: "A", Value: "1"}, {Key: "B", Value: "2"}, } // Expected sorted KVSlice expected := KVSlice{ {Key: "A", Value: "1"}, {Key: "B", Value: "2"}, {Key: "C", Value: "3"}, } // Sort the KVSlice kvsCopy := make(KVSlice, len(kvs)) copy(kvsCopy, kvs) reflect.DeepEqual(kvs, kvsCopy) // Verify the copy worked // Sort the copy sort.Sort(kvsCopy) // Check if the sorted KVSlice matches the expected if !reflect.DeepEqual(kvsCopy, expected) { t.Errorf("KVSlice sorting failed. Expected %v, got %v", expected, kvsCopy) } }