diff options
| author | Paul Buetow <paul@buetow.org> | 2026-01-24 00:23:25 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-01-24 00:23:25 +0200 |
| commit | f725c75b03e006359193f2f6cccf0de22c137da0 (patch) | |
| tree | 747bc2a7f2c5f745ba0d470853fe1002efebf55f | |
| parent | 65ec49a97b1fcf633c1c6ba92e3db71ecd477196 (diff) | |
test: add unit tests for turbo writer types
Add comprehensive unit tests for DirectTurboWriter and TurboChannelWriter:
- DirectTurboWriter: serverless plain mode, network modes, server messages
- TurboChannelWriter: line data, channel full handling, server messages
- Stats tracking verification
Note: Some tests skipped due to global config/dlog dependencies:
- Colored mode tests (require color config)
- DirectLineProcessor tests (require dlog initialization)
These are covered by integration tests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| -rw-r--r-- | internal/server/handlers/turbo_writer_test.go | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/internal/server/handlers/turbo_writer_test.go b/internal/server/handlers/turbo_writer_test.go new file mode 100644 index 0000000..d8dc8a9 --- /dev/null +++ b/internal/server/handlers/turbo_writer_test.go @@ -0,0 +1,366 @@ +package handlers + +import ( + "bytes" + "strings" + "testing" + + "github.com/mimecast/dtail/internal/protocol" +) + +// TestDirectTurboWriter_ServerlessPlain tests plain serverless mode output +func TestDirectTurboWriter_ServerlessPlain(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", true, true) + + // Write a line without trailing newline + err := w.WriteLineData([]byte("test line"), 1, "source.log") + if err != nil { + t.Fatalf("WriteLineData failed: %v", err) + } + + // Flush to get output + err = w.Flush() + if err != nil { + t.Fatalf("Flush failed: %v", err) + } + + // In plain serverless mode, output should be just the content with newline + expected := "test line\n" + if buf.String() != expected { + t.Errorf("Expected %q, got %q", expected, buf.String()) + } + + // Check stats + lines, bytesWritten := w.Stats() + if lines != 1 { + t.Errorf("Expected 1 line written, got %d", lines) + } + if bytesWritten == 0 { + t.Error("Expected non-zero bytes written") + } +} + +// TestDirectTurboWriter_ServerlessPlainWithNewline tests that existing newlines are preserved +func TestDirectTurboWriter_ServerlessPlainWithNewline(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", true, true) + + // Write a line with trailing newline + err := w.WriteLineData([]byte("test line\n"), 1, "source.log") + if err != nil { + t.Fatalf("WriteLineData failed: %v", err) + } + + err = w.Flush() + if err != nil { + t.Fatalf("Flush failed: %v", err) + } + + // Should not add extra newline + expected := "test line\n" + if buf.String() != expected { + t.Errorf("Expected %q, got %q", expected, buf.String()) + } +} + +// TestDirectTurboWriter_ServerlessColored tests colored serverless mode output +// Note: Skipped because it requires color config initialization which is complex to set up in tests +func TestDirectTurboWriter_ServerlessColored(t *testing.T) { + t.Skip("Requires color config initialization - tested via integration tests") +} + +// TestDirectTurboWriter_NetworkPlain tests plain network mode output +func TestDirectTurboWriter_NetworkPlain(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", true, false) + + err := w.WriteLineData([]byte("test line"), 1, "source.log") + if err != nil { + t.Fatalf("WriteLineData failed: %v", err) + } + + err = w.Flush() + if err != nil { + t.Fatalf("Flush failed: %v", err) + } + + // In plain network mode, output should be just the content with newline + expected := "test line\n" + if buf.String() != expected { + t.Errorf("Expected %q, got %q", expected, buf.String()) + } +} + +// TestDirectTurboWriter_NetworkFormatted tests formatted network mode output +func TestDirectTurboWriter_NetworkFormatted(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", false, false) + + err := w.WriteLineData([]byte("test line"), 99, "myfile.log") + if err != nil { + t.Fatalf("WriteLineData failed: %v", err) + } + + err = w.Flush() + if err != nil { + t.Fatalf("Flush failed: %v", err) + } + + output := buf.String() + + // In formatted network mode, output should have protocol structure + if !strings.HasPrefix(output, "REMOTE") { + t.Errorf("Expected output to start with REMOTE, got %q", output) + } + if !strings.Contains(output, "testhost") { + t.Errorf("Expected output to contain hostname, got %q", output) + } + if !strings.Contains(output, "99") { + t.Errorf("Expected output to contain line number 99, got %q", output) + } + if !strings.Contains(output, "myfile.log") { + t.Errorf("Expected output to contain source ID, got %q", output) + } + // Should end with message delimiter + if output[len(output)-1] != protocol.MessageDelimiter { + t.Errorf("Expected output to end with message delimiter, got %q", output) + } +} + +// TestDirectTurboWriter_WriteServerMessage tests server message writing +func TestDirectTurboWriter_WriteServerMessage(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", false, false) + + err := w.WriteServerMessage("Hello from server") + if err != nil { + t.Fatalf("WriteServerMessage failed: %v", err) + } + + output := buf.String() + + if !strings.HasPrefix(output, "SERVER") { + t.Errorf("Expected output to start with SERVER, got %q", output) + } + if !strings.Contains(output, "testhost") { + t.Errorf("Expected output to contain hostname, got %q", output) + } + if !strings.Contains(output, "Hello from server") { + t.Errorf("Expected output to contain message, got %q", output) + } +} + +// TestDirectTurboWriter_WriteServerMessage_Serverless tests that server messages are skipped in serverless mode +func TestDirectTurboWriter_WriteServerMessage_Serverless(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", false, true) + + err := w.WriteServerMessage("Hello from server") + if err != nil { + t.Fatalf("WriteServerMessage failed: %v", err) + } + + // In serverless mode, server messages should be skipped + if buf.Len() != 0 { + t.Errorf("Expected no output in serverless mode, got %q", buf.String()) + } +} + +// TestDirectTurboWriter_WriteServerMessage_HiddenMessage tests hidden message handling +func TestDirectTurboWriter_WriteServerMessage_HiddenMessage(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", false, false) + + err := w.WriteServerMessage(".hidden") + if err != nil { + t.Fatalf("WriteServerMessage failed: %v", err) + } + + output := buf.String() + + // Hidden messages (starting with .) should be written directly + if !strings.HasPrefix(output, ".hidden") { + t.Errorf("Expected output to start with .hidden, got %q", output) + } + // Should NOT have SERVER prefix + if strings.Contains(output, "SERVER") { + t.Errorf("Hidden message should not have SERVER prefix, got %q", output) + } +} + +// TestDirectTurboWriter_MultipleLines tests writing multiple lines +func TestDirectTurboWriter_MultipleLines(t *testing.T) { + var buf bytes.Buffer + w := NewDirectTurboWriter(&buf, "testhost", true, true) + + for i := uint64(1); i <= 5; i++ { + err := w.WriteLineData([]byte("line content"), i, "source.log") + if err != nil { + t.Fatalf("WriteLineData failed on line %d: %v", i, err) + } + } + + err := w.Flush() + if err != nil { + t.Fatalf("Flush failed: %v", err) + } + + lines, _ := w.Stats() + if lines != 5 { + t.Errorf("Expected 5 lines written, got %d", lines) + } + + // Count actual lines in output + outputLines := strings.Count(buf.String(), "\n") + if outputLines != 5 { + t.Errorf("Expected 5 lines in output, got %d", outputLines) + } +} + +// TestTurboChannelWriter_WriteLineData tests channel writer line data +func TestTurboChannelWriter_WriteLineData(t *testing.T) { + ch := make(chan []byte, 10) + w := NewTurboChannelWriter(ch, "testhost", false, false) + + err := w.WriteLineData([]byte("test line"), 1, "source.log") + if err != nil { + t.Fatalf("WriteLineData failed: %v", err) + } + + // Check that data was sent to channel + select { + case data := <-ch: + output := string(data) + if !strings.Contains(output, "REMOTE") { + t.Errorf("Expected output to contain REMOTE, got %q", output) + } + if !strings.Contains(output, "test line") { + t.Errorf("Expected output to contain line content, got %q", output) + } + default: + t.Error("Expected data in channel, got none") + } +} + +// TestTurboChannelWriter_ChannelFull tests behavior when channel is full +func TestTurboChannelWriter_ChannelFull(t *testing.T) { + ch := make(chan []byte, 1) + w := NewTurboChannelWriter(ch, "testhost", true, false) + + // Fill the channel + err := w.WriteLineData([]byte("first"), 1, "source.log") + if err != nil { + t.Fatalf("First WriteLineData failed: %v", err) + } + + // Next write should fail (channel full) + err = w.WriteLineData([]byte("second"), 2, "source.log") + if err == nil { + t.Error("Expected error when channel is full") + } + if !strings.Contains(err.Error(), "channel full") { + t.Errorf("Expected 'channel full' error, got %v", err) + } +} + +// TestTurboChannelWriter_PlainServerless tests plain serverless mode +func TestTurboChannelWriter_PlainServerless(t *testing.T) { + ch := make(chan []byte, 10) + w := NewTurboChannelWriter(ch, "testhost", true, true) + + err := w.WriteLineData([]byte("test line"), 1, "source.log") + if err != nil { + t.Fatalf("WriteLineData failed: %v", err) + } + + select { + case data := <-ch: + output := string(data) + // In plain serverless mode, should NOT have REMOTE prefix + if strings.Contains(output, "REMOTE") { + t.Errorf("Plain serverless should not have REMOTE prefix, got %q", output) + } + if !strings.Contains(output, "test line") { + t.Errorf("Expected output to contain line content, got %q", output) + } + default: + t.Error("Expected data in channel, got none") + } +} + +// TestTurboChannelWriter_WriteServerMessage tests server message handling +func TestTurboChannelWriter_WriteServerMessage(t *testing.T) { + ch := make(chan []byte, 10) + w := NewTurboChannelWriter(ch, "testhost", false, false) + + err := w.WriteServerMessage("Server says hello") + if err != nil { + t.Fatalf("WriteServerMessage failed: %v", err) + } + + select { + case data := <-ch: + output := string(data) + if !strings.Contains(output, "SERVER") { + t.Errorf("Expected output to contain SERVER, got %q", output) + } + if !strings.Contains(output, "Server says hello") { + t.Errorf("Expected output to contain message, got %q", output) + } + default: + t.Error("Expected data in channel, got none") + } +} + +// TestTurboChannelWriter_WriteServerMessage_Serverless tests server messages skipped in serverless +func TestTurboChannelWriter_WriteServerMessage_Serverless(t *testing.T) { + ch := make(chan []byte, 10) + w := NewTurboChannelWriter(ch, "testhost", false, true) + + err := w.WriteServerMessage("Server says hello") + if err != nil { + t.Fatalf("WriteServerMessage failed: %v", err) + } + + // Channel should be empty in serverless mode + select { + case <-ch: + t.Error("Expected no data in channel for serverless mode") + default: + // Expected + } +} + +// TestTurboChannelWriter_Stats tests statistics tracking +func TestTurboChannelWriter_Stats(t *testing.T) { + ch := make(chan []byte, 10) + w := NewTurboChannelWriter(ch, "testhost", true, true) + + for i := uint64(1); i <= 3; i++ { + err := w.WriteLineData([]byte("line"), i, "source.log") + if err != nil { + t.Fatalf("WriteLineData failed: %v", err) + } + } + + lines, bytesWritten := w.Stats() + if lines != 3 { + t.Errorf("Expected 3 lines, got %d", lines) + } + if bytesWritten == 0 { + t.Error("Expected non-zero bytes written") + } +} + +// TestDirectLineProcessor tests the line processor wrapper +// Note: Skipped because DirectLineProcessor uses dlog.Server which requires initialization +func TestDirectLineProcessor(t *testing.T) { + t.Skip("Requires dlog initialization - tested via integration tests") +} + +// TestDirectLineProcessor_Close tests the close method +// Note: Skipped because DirectLineProcessor uses dlog.Server which requires initialization +func TestDirectLineProcessor_Close(t *testing.T) { + t.Skip("Requires dlog initialization - tested via integration tests") +} |
