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) }