encoder_test.go

package lambdalog

import (
	"encoding/json"
	"errors"
	"strings"
	"testing"
	"time"
)

func TestEncodeValue_Primitives(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		in   any
		want string
	}{
		{"nil", nil, "null"},
		{"bool-true", true, "true"},
		{"bool-false", false, "false"},
		{"int", 42, "42"},
		{"int64-neg", int64(-7), "-7"},
		{"uint", uint(9), "9"},
		{"float", 1.5, "1.5"},
		{"string-plain", "hello", `"hello"`},
		{"string-escape", "line\nbreak", `"line\nbreak"`},
		{"string-quote", `he said "hi"`, `"he said \"hi\""`},
		{"bytes", []byte("abc"), `"abc"`},
		{"duration", 250 * time.Millisecond, `"250ms"`},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			got := string(encodeValue(nil, tc.in))
			if got != tc.want {
				t.Fatalf("encodeValue(%v) = %s, want %s", tc.in, got, tc.want)
			}
		})
	}
}

func TestEncodeValue_ErrorRendersMessage(t *testing.T) {
	t.Parallel()

	err := errors.New("disk full")
	got := string(encodeValue(nil, err))
	if got != `"disk full"` {
		t.Fatalf("unexpected: %s", got)
	}
}

func TestEncodeValue_MapIsSorted(t *testing.T) {
	t.Parallel()

	m := map[string]any{"zeta": 1, "alpha": 2, "mu": 3}
	got := string(encodeValue(nil, m))
	want := `{"alpha":2,"mu":3,"zeta":1}`
	if got != want {
		t.Fatalf("map encoding not sorted: %s", got)
	}
}

func TestEncodeValue_FallsBackToStdJSON(t *testing.T) {
	t.Parallel()

	type custom struct {
		A int    `json:"a"`
		B string `json:"b"`
	}
	got := string(encodeValue(nil, custom{A: 1, B: "two"}))
	var rt custom
	if err := json.Unmarshal([]byte(got), &rt); err != nil {
		t.Fatalf("fallback produced invalid JSON: %v (%s)", err, got)
	}
	if rt.A != 1 || rt.B != "two" {
		t.Fatalf("fallback round-trip failed: %+v", rt)
	}
}

func TestEncodeRecord_IsSingleJSONLine(t *testing.T) {
	t.Parallel()

	line := encodeRecord(LevelInfo, "hi", []attr{{Key: "user", Value: "bob"}}, []any{"n", 3})
	if !strings.HasSuffix(string(line), "\n") {
		t.Fatal("encoded record should end with newline")
	}
	var rec map[string]any
	if err := json.Unmarshal(line[:len(line)-1], &rec); err != nil {
		t.Fatalf("record is not valid JSON: %v (%s)", err, line)
	}
	if rec["level"] != "info" || rec["msg"] != "hi" || rec["user"] != "bob" {
		t.Fatalf("unexpected decoded record: %+v", rec)
	}
}

func TestAppendQuoted_ControlCharacters(t *testing.T) {
	t.Parallel()

	got := string(appendQuoted(nil, "a\x01b"))
	if !strings.Contains(got, ``) {
		t.Fatalf("expected \\u0001 escape, got %s", got)
	}
}

func TestSortStrings(t *testing.T) {
	t.Parallel()

	xs := []string{"c", "a", "b", "a"}
	sortStrings(xs)
	want := []string{"a", "a", "b", "c"}
	for i := range want {
		if xs[i] != want[i] {
			t.Fatalf("sortStrings result: %v", xs)
		}
	}
}