diff options
| author | Paul Buetow <paul@buetow.org> | 2025-06-19 20:29:21 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-06-19 20:29:21 +0300 |
| commit | 2f20d0eacfbc16111fa273f4d6cac339cc61ef51 (patch) | |
| tree | 43057356276c3971e410d21c909de69eaee0f605 /internal/protocol/protocol_test.go | |
| parent | 1a9259eb9a10202c28dbd959e6cfa2e2fcf3e064 (diff) | |
Implement Phase 1: Foundation for improved maintainability and testability
- Add standardized error handling package (internal/errors)
- Sentinel errors for common conditions
- Error wrapping and chaining support
- MultiError for batch operations
- Add comprehensive test utilities package (internal/testutil)
- File/directory test helpers
- Assertion functions for common test patterns
- Mock SSH server for integration testing
- Test data generators
- Add unit tests for core packages
- Protocol package: delimiter validation and usage tests
- Config package: comprehensive configuration tests
- Discovery package: server discovery method tests
- IO/FS package: stats tracking and grep processor tests
All tests passing. This establishes a solid foundation for further improvements.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal/protocol/protocol_test.go')
| -rw-r--r-- | internal/protocol/protocol_test.go | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/internal/protocol/protocol_test.go b/internal/protocol/protocol_test.go new file mode 100644 index 0000000..44eb7d3 --- /dev/null +++ b/internal/protocol/protocol_test.go @@ -0,0 +1,194 @@ +package protocol + +import ( + "strings" + "testing" + + "github.com/mimecast/dtail/internal/testutil" +) + +func TestProtocolConstants(t *testing.T) { + // Test that protocol version follows expected format + t.Run("protocol version format", func(t *testing.T) { + // Should be in format X.Y + parts := strings.Split(ProtocolCompat, ".") + if len(parts) != 2 { + t.Errorf("ProtocolCompat should be in X.Y format, got %q", ProtocolCompat) + } + }) + + // Test message delimiter uniqueness + t.Run("delimiter uniqueness", func(t *testing.T) { + // Note: CSVDelimiter and AggregateGroupKeyCombinator intentionally use the same delimiter + delimiters := map[string]string{ + "MessageDelimiter": string(MessageDelimiter), + "FieldDelimiter": FieldDelimiter, + "AggregateKVDelimiter": AggregateKVDelimiter, + "AggregateDelimiter": AggregateDelimiter, + } + + // Check that protocol delimiters are unique (excluding CSV/GroupKey which share ",") + seen := make(map[string]string) + for name, d := range delimiters { + if prevName, exists := seen[d]; exists { + t.Errorf("Delimiter %q used by both %s and %s", d, prevName, name) + } + seen[d] = name + } + + // Verify CSV and GroupKey combinator are the same (by design) + testutil.AssertEqual(t, CSVDelimiter, AggregateGroupKeyCombinator) + }) + + // Test that delimiters are not empty + t.Run("non-empty delimiters", func(t *testing.T) { + if MessageDelimiter == 0 { + t.Error("MessageDelimiter should not be zero byte") + } + if FieldDelimiter == "" { + t.Error("FieldDelimiter should not be empty") + } + if CSVDelimiter == "" { + t.Error("CSVDelimiter should not be empty") + } + if AggregateKVDelimiter == "" { + t.Error("AggregateKVDelimiter should not be empty") + } + if AggregateDelimiter == "" { + t.Error("AggregateDelimiter should not be empty") + } + if AggregateGroupKeyCombinator == "" { + t.Error("AggregateGroupKeyCombinator should not be empty") + } + }) + + // Test expected values (for documentation and regression prevention) + t.Run("expected values", func(t *testing.T) { + testutil.AssertEqual(t, byte('¬'), MessageDelimiter) + testutil.AssertEqual(t, "|", FieldDelimiter) + testutil.AssertEqual(t, ",", CSVDelimiter) + testutil.AssertEqual(t, "≔", AggregateKVDelimiter) + testutil.AssertEqual(t, "∥", AggregateDelimiter) + testutil.AssertEqual(t, ",", AggregateGroupKeyCombinator) + testutil.AssertEqual(t, "4.1", ProtocolCompat) + }) + + // Test that special delimiters don't conflict with common characters + t.Run("delimiter safety", func(t *testing.T) { + // Common characters that shouldn't be used as delimiters + commonChars := []string{ + " ", "\n", "\r", "\t", // Whitespace + "a", "e", "i", "o", "u", // Common letters + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", // Digits + ".", ":", ";", "-", "_", "/", "\\", // Common punctuation in logs + } + + delimiters := []string{ + string(MessageDelimiter), + FieldDelimiter, + AggregateKVDelimiter, + AggregateDelimiter, + } + + for _, delimiter := range delimiters { + for _, common := range commonChars { + if delimiter == common { + t.Errorf("Delimiter %q conflicts with common character", delimiter) + } + } + } + }) +} + +func TestDelimiterUsage(t *testing.T) { + // Test typical protocol message construction and parsing patterns + t.Run("message construction", func(t *testing.T) { + // Simulate building a protocol message + fields := []string{"HEALTH", "OK", "server1", "100"} + message := strings.Join(fields, FieldDelimiter) + + // Should be able to reconstruct fields + parsed := strings.Split(message, FieldDelimiter) + if len(parsed) != len(fields) { + t.Errorf("Expected %d fields, got %d", len(fields), len(parsed)) + } + for i, field := range fields { + testutil.AssertEqual(t, field, parsed[i]) + } + }) + + t.Run("aggregate message construction", func(t *testing.T) { + // Simulate MapReduce aggregation message + key := "error" + value := "42" + kvPair := key + AggregateKVDelimiter + value + + // Should be able to parse key-value + parts := strings.Split(kvPair, AggregateKVDelimiter) + if len(parts) != 2 { + t.Fatalf("Expected 2 parts in KV pair, got %d", len(parts)) + } + testutil.AssertEqual(t, key, parts[0]) + testutil.AssertEqual(t, value, parts[1]) + }) + + t.Run("multiple messages", func(t *testing.T) { + // Simulate multiple messages in a stream + messages := []string{"MSG1", "MSG2", "MSG3"} + + // Build stream with message delimiter between messages + var parts []string + for _, msg := range messages { + parts = append(parts, msg) + } + + // Join with delimiter + delimiter := string(MessageDelimiter) + stream := strings.Join(parts, delimiter) + + // Parse messages back + parsed := strings.Split(stream, delimiter) + if len(parsed) != len(messages) { + t.Errorf("Expected %d messages, got %d", len(messages), len(parsed)) + } + for i, msg := range messages { + if i < len(parsed) { + testutil.AssertEqual(t, msg, parsed[i]) + } + } + }) +} + +func TestCSVDelimiter(t *testing.T) { + // Test CSV parsing scenarios + t.Run("csv field parsing", func(t *testing.T) { + csvLine := "field1,field2,field3,field4" + fields := strings.Split(csvLine, CSVDelimiter) + + if len(fields) != 4 { + t.Errorf("Expected 4 CSV fields, got %d", len(fields)) + } + + expected := []string{"field1", "field2", "field3", "field4"} + for i, field := range expected { + testutil.AssertEqual(t, field, fields[i]) + } + }) +} + +func TestGroupKeyCombinator(t *testing.T) { + // Test group key combination for MapReduce + t.Run("combine group keys", func(t *testing.T) { + keys := []string{"host", "service", "level"} + combined := strings.Join(keys, AggregateGroupKeyCombinator) + + // Should be able to split back + parsed := strings.Split(combined, AggregateGroupKeyCombinator) + if len(parsed) != len(keys) { + t.Errorf("Expected %d keys, got %d", len(keys), len(parsed)) + } + for i, key := range keys { + testutil.AssertEqual(t, key, parsed[i]) + } + }) +}
\ No newline at end of file |
