This commit completes the implementation of nested field support.
- :
- now correctly handles and by recursively flattening them into format.
- Introduced for recursive map marshalling.
- Refactored for cleaner buffer writing.
- : Added comprehensive test cases for nested JSON, nested maps, double-nested maps, and nested keys within JSON to ensure correct marshalling and unmarshalling.
- : Updated tests to reflect the new nil handling and removed redundant JSON object test.
This allows for more flexible and structured data representation within log entries.
218 lines
5.8 KiB
Go
218 lines
5.8 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Type assertions
|
|
var _ encoding.TextMarshaler = Meta{}
|
|
var _ encoding.TextUnmarshaler = new(Meta)
|
|
var _ json.Marshaler = Metas{}
|
|
var _ json.Unmarshaler = new(Metas)
|
|
|
|
var skipMarshalTest = errors.New("skip marshal")
|
|
|
|
func TestMeta(t *testing.T) {
|
|
when := time.Now()
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
value any
|
|
out string
|
|
err error
|
|
newVal any
|
|
}{
|
|
{"int", "num", 42, "@num 42", nil, 42},
|
|
{"int64", "num", int64(42), "@num 42", nil, int(42)},
|
|
{"float", "num", 42.13, "@num 42.13", nil, 42.13},
|
|
{"string", "word", "hello", "@word hello", nil, "hello"},
|
|
{"json number", "num", json.Number("42.13"), "@num 42.13", nil, 42.13},
|
|
{"true", "b", true, "@b true", nil, true},
|
|
{"false", "b", false, "@b false", nil, false},
|
|
{"nil", "n", nil, "", ErrorParsing, ErrorParsing},
|
|
{"time", "when", when, "@when " + when.Format(time.RFC3339), nil, when},
|
|
{"rune", "char", '@', "@char @", nil, "@"},
|
|
{"bytes", "byteme", []byte("yo"), "@byteme yo", nil, "yo"},
|
|
{"byte", "byteme", byte(67), "@byteme C", nil, "C"},
|
|
{"json-arr", "arr", json.RawMessage(`["foo",42,"bar", null,"quux", true]`), `@arr ["foo",42,"bar", null,"quux", true]`, nil, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)},
|
|
{"chan", "nope", make(chan bool), "", errors.New("Unsupported type chan bool"), ""},
|
|
{"whitespace-key", "no space", "hi", "", errors.New("whitespace is not allowed in key: no space"), ""},
|
|
{"empty-mar", "nope", skipMarshalTest, "", nil, ErrorParsing},
|
|
{"no-key-mar", "nope", skipMarshalTest, "nope", nil, ErrorParsing},
|
|
{"no-value-mar", "nope", skipMarshalTest, "@nope ", nil, ErrorParsing},
|
|
{"space-value-mar", "nope", skipMarshalTest, "@nope ", nil, ErrorParsing},
|
|
{"space-nl-value-mar", "nope", skipMarshalTest, "@nope \n ", nil, ErrorParsing},
|
|
{"null-value-mar", "nope", skipMarshalTest, "@nope null", nil, nil},
|
|
{"tilda-value-mar", "nope", skipMarshalTest, "@nope ~", nil, nil},
|
|
{"none-value-mar", "nope", skipMarshalTest, "@nope none", nil, nil},
|
|
{"nil-value-mar", "nope", skipMarshalTest, "@nope nil", nil, nil},
|
|
{"yes-value-mar", "nope", skipMarshalTest, "@nope yes", nil, true},
|
|
{"on-value-mar", "nope", skipMarshalTest, "@nope on", nil, true},
|
|
{"no-value-mar", "nope", skipMarshalTest, "@nope no", nil, false},
|
|
{"off-value-mar", "nope", skipMarshalTest, "@nope off", nil, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, getMetaTestRunner(tt.key, tt.value, tt.out, tt.err, tt.newVal))
|
|
}
|
|
}
|
|
|
|
func getMetaTestRunner(key string, value any, out string, err error, newVal any) func(*testing.T) {
|
|
return func(t *testing.T) {
|
|
st := Meta{key, value}
|
|
n := &Meta{}
|
|
var e error
|
|
|
|
if valE, ok := value.(error); !ok || !errors.Is(valE, skipMarshalTest) {
|
|
var o []byte
|
|
o, e = st.MarshalText()
|
|
assert.Equal(t, out, string(o))
|
|
assert.Equal(t, err, e)
|
|
if e != nil {
|
|
return
|
|
}
|
|
|
|
e = n.UnmarshalText(o)
|
|
} else {
|
|
e = n.UnmarshalText([]byte(out))
|
|
}
|
|
if newE, ok := newVal.(error); ok {
|
|
assert.ErrorIs(t, e, newE)
|
|
} else {
|
|
assert.Equal(t, key, n.Key)
|
|
if ti, ok := newVal.(time.Time); ok {
|
|
valT, ok := n.Value.(time.Time)
|
|
if assert.True(t, ok) {
|
|
assert.WithinRange(t, valT, ti.Add(-time.Second), ti.Add(time.Second))
|
|
}
|
|
|
|
} else {
|
|
assert.Equal(t, newVal, n.Value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMetasJson(t *testing.T) {
|
|
ms := Metas{{"me", 41}, {"you", false}}
|
|
exp := `{"me":41,"you":false}`
|
|
o, err := json.Marshal(ms)
|
|
assert.NoError(t, err)
|
|
assert.JSONEq(t, exp, string(o))
|
|
}
|
|
|
|
func TestMetasJsonUnmarshal(t *testing.T) {
|
|
ms := Metas{}
|
|
in := `{"me":"cool","you":false}`
|
|
err := json.Unmarshal([]byte(in), &ms)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, ms, 2)
|
|
assert.ElementsMatch(t, Metas{
|
|
{"me", "cool"},
|
|
{"you", false},
|
|
}, ms)
|
|
}
|
|
|
|
func TestMetasJsonError(t *testing.T) {
|
|
ms := Metas{}
|
|
in := "not json"
|
|
err := (&ms).UnmarshalJSON([]byte(in))
|
|
assert.Error(t, err)
|
|
assert.Len(t, ms, 0)
|
|
}
|
|
|
|
func TestMetasAppend(t *testing.T) {
|
|
ms := Metas{}
|
|
ms = ms.Append("foo", 42)
|
|
assert.Len(t, ms, 1)
|
|
assert.Equal(t, Meta{"foo", 42}, ms[0])
|
|
}
|
|
|
|
func TestMetasAppendTo(t *testing.T) {
|
|
ms := &Metas{}
|
|
ms.AppendTo("foo", 42)
|
|
assert.Len(t, *ms, 1)
|
|
assert.Equal(t, Meta{"foo", 42}, (*ms)[0])
|
|
}
|
|
|
|
func TestMetasSet(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
initial Metas
|
|
key string
|
|
value any
|
|
expected Metas
|
|
}{
|
|
{
|
|
name: "Set new key",
|
|
initial: Metas{},
|
|
key: "foo",
|
|
value: 42,
|
|
expected: Metas{{"foo", 42}},
|
|
},
|
|
{
|
|
name: "Update existing key",
|
|
initial: Metas{{"foo", 1}},
|
|
key: "foo",
|
|
value: 42,
|
|
expected: Metas{{"foo", 42}},
|
|
},
|
|
{
|
|
name: "Update existing key with different type",
|
|
initial: Metas{{"foo", "hello"}},
|
|
key: "foo",
|
|
value: 42,
|
|
expected: Metas{{"foo", 42}},
|
|
},
|
|
{
|
|
name: "Set multiple new keys",
|
|
initial: Metas{},
|
|
key: "bar",
|
|
value: true,
|
|
expected: Metas{{"bar", true}},
|
|
},
|
|
{
|
|
name: "Update one of multiple existing keys",
|
|
initial: Metas{{"foo", 1}, {"bar", "hello"}},
|
|
key: "foo",
|
|
value: 42,
|
|
expected: Metas{{"foo", 42}, {"bar", "hello"}},
|
|
},
|
|
{
|
|
name: "Set new key when others exist",
|
|
initial: Metas{{"foo", 1}, {"bar", "hello"}},
|
|
key: "baz",
|
|
value: false,
|
|
expected: Metas{{"foo", 1}, {"bar", "hello"}, {"baz", false}},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.initial.Set(tt.key, tt.value)
|
|
assert.ElementsMatch(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMetasGet(t *testing.T) {
|
|
ms := Metas{{"foo", 42}, {"bar", "hello"}}
|
|
|
|
val, found := ms.Get("foo")
|
|
assert.True(t, found)
|
|
assert.Equal(t, 42, val)
|
|
|
|
val, found = ms.Get("bar")
|
|
assert.True(t, found)
|
|
assert.Equal(t, "hello", val)
|
|
|
|
val, found = ms.Get("baz")
|
|
assert.False(t, found)
|
|
assert.Nil(t, val)
|
|
}
|