Files
clickhouse-tools/internal/chlog/format_test.go
T
unkin-agent 415bf0cce1
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Add chlog CLI with chcat/chtail/chgrep entrypoints
Single Go binary for the ClickHouse log store (logs.raw): chlog with
cat/tail/grep subcommands, plus chcat/chtail/chgrep argv[0]-dispatched
symlink entrypoints. Every query is time-bounded and fully parameterized;
chgrep guards wide unfiltered scans. Ships nfpm RPM with completions and
woodpecker PR/tag pipelines mirroring node-lookup.
2026-08-23 16:43:05 +10:00

151 lines
3.6 KiB
Go

package chlog
import (
"encoding/json"
"strings"
"testing"
"time"
)
func sampleRow() Row {
return Row{
Timestamp: "2026-08-23 05:00:01.234",
Host: "node1",
Source: "k8s",
Namespace: "logging",
Pod: "vector-abc",
Container: "vector",
Stream: "stdout",
Severity: "Error",
Message: "something broke",
Labels: map[string]string{"app": "vector"},
Fields: map[string]string{"req_id": "42"},
}
}
func render(t *testing.T, format string, color bool, r Row) string {
t.Helper()
f, err := NewFormatter(format, color)
if err != nil {
t.Fatal(err)
}
var b strings.Builder
if err := f(&b, r); err != nil {
t.Fatal(err)
}
return b.String()
}
func TestTextNoColor(t *testing.T) {
out := render(t, "text", false, sampleRow())
want := "2026-08-23T05:00:01.234Z logging/vector-abc something broke\n"
if out != want {
t.Errorf("got %q, want %q", out, want)
}
if strings.Contains(out, "\x1b[") {
t.Error("no-color output contains ANSI escapes")
}
}
func TestTextColor(t *testing.T) {
out := render(t, "text", true, sampleRow())
if !strings.Contains(out, "\x1b[") {
t.Error("color output missing ANSI escapes")
}
if !strings.Contains(out, "logging/vector-abc") || !strings.Contains(out, "something broke") {
t.Errorf("content missing: %q", out)
}
}
func TestTextVMRowUsesHost(t *testing.T) {
r := sampleRow()
r.Source = "vm"
r.Namespace = ""
r.Pod = ""
out := render(t, "text", false, r)
if !strings.Contains(out, " node1 ") {
t.Errorf("vm row should show host: %q", out)
}
}
func TestJSONFormat(t *testing.T) {
out := render(t, "json", false, sampleRow())
var m map[string]any
if err := json.Unmarshal([]byte(out), &m); err != nil {
t.Fatalf("not valid JSON: %v: %q", err, out)
}
if m["timestamp"] != "2026-08-23T05:00:01.234Z" {
t.Errorf("timestamp = %v", m["timestamp"])
}
if m["message"] != "something broke" || m["namespace"] != "logging" {
t.Errorf("fields wrong: %v", m)
}
labels, _ := m["labels"].(map[string]any)
if labels["app"] != "vector" {
t.Errorf("labels = %v", m["labels"])
}
}
func TestLogfmtFormat(t *testing.T) {
out := render(t, "logfmt", false, sampleRow())
for _, want := range []string{
"ts=2026-08-23T05:00:01.234Z",
"ns=logging",
"pod=vector-abc",
"severity=Error",
"app=vector",
`msg="something broke"`,
} {
if !strings.Contains(out, want) {
t.Errorf("logfmt missing %q: %q", want, out)
}
}
if !strings.HasSuffix(out, "\n") {
t.Error("missing trailing newline")
}
}
func TestLogfmtQuoting(t *testing.T) {
r := sampleRow()
r.Message = `plain`
out := render(t, "logfmt", false, r)
if !strings.Contains(out, "msg=plain") {
t.Errorf("unquoted simple value expected: %q", out)
}
r.Message = "has \"quotes\" and = signs"
out = render(t, "logfmt", false, r)
if !strings.Contains(out, `msg="has \"quotes\" and = signs"`) {
t.Errorf("quoted value wrong: %q", out)
}
}
func TestUnknownFormat(t *testing.T) {
if _, err := NewFormatter("yaml", false); err == nil {
t.Fatal("expected error for unknown format")
}
}
func TestRowKeyStableAndDistinct(t *testing.T) {
a := sampleRow()
b := sampleRow()
if a.Key() != b.Key() {
t.Error("identical rows should share a key")
}
b.Message = "different"
if a.Key() == b.Key() {
t.Error("different rows should not share a key")
}
}
func TestRowTimeParses(t *testing.T) {
r := sampleRow()
want := time.Date(2026, 8, 23, 5, 0, 1, 234000000, time.UTC)
if !r.Time().Equal(want) {
t.Errorf("Time() = %s, want %s", r.Time(), want)
}
r.Timestamp = "2026-08-23 05:00:01"
if r.Time().IsZero() {
t.Error("timestamp without fraction should still parse")
}
}