415bf0cce1
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.
151 lines
3.6 KiB
Go
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")
|
|
}
|
|
}
|