summaryrefslogtreecommitdiff
path: root/internal/protocol/protocol_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-19 20:29:21 +0300
committerPaul Buetow <paul@buetow.org>2025-06-19 20:29:21 +0300
commit2f20d0eacfbc16111fa273f4d6cac339cc61ef51 (patch)
tree43057356276c3971e410d21c909de69eaee0f605 /internal/protocol/protocol_test.go
parent1a9259eb9a10202c28dbd959e6cfa2e2fcf3e064 (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.go194
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