my-log/models/entry_test.go

314 lines
9 KiB
Go

package models
import (
"bufio"
"encoding"
"encoding/json"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// Type assertions
var _ encoding.TextMarshaler = Entry{}
var _ encoding.TextUnmarshaler = new(Entry)
var _ json.Marshaler = Entry{}
var _ json.Unmarshaler = new(Entry)
func TestEntryMarshal(t *testing.T) {
when := time.Now()
whens := when.Format(DateFormat)
simple := []Meta{}
nolines := []string{}
tests := []struct {
name string
title string
date time.Time
fields []Meta
first string
lines []string
err error
}{
{"no-title", "", when, simple, "", nolines, ErrorMissingTitle},
{"zero-date", "Empty title", time.Time{}, simple, "", nolines, ErrorMissingDate},
{"one-line", "A Title", when, simple, "@begin " + whens + " - A Title @end", nolines, nil},
{
"one-field",
"Title 2",
when,
[]Meta{{"age", 41}},
"@begin " + whens + " - Title 2",
[]string{"@age 41 @end"},
nil,
},
{
"three-fields",
"Title 3",
when,
[]Meta{{"age", 41}, {"cool", true}, {"name", "Jim"}},
"@begin " + whens + " - Title 3",
[]string{"@age 41", "@cool true", "@name Jim"},
nil,
},
/* uncomment when implemented
{
"json-field",
"Title J",
when,
[]Meta{{"json", json.RawMessage(`{"age": 41, "cool": true, "name": "Jim"}`)}},
"@begin " + whens + " - Title J",
[]string{"@age 41", "@cool true", "@name Jim"},
nil,
},
*/
}
for _, tt := range tests {
t.Run(tt.name, getEntryMarshalTestRunner(tt.title, tt.date, tt.fields, tt.first, tt.lines, tt.err))
}
}
func getEntryMarshalTestRunner(title string, date time.Time, fields []Meta, first string, lines []string, err error) func(*testing.T) {
return func(t *testing.T) {
en := Entry{title, date, fields}
o, er := en.MarshalText()
assert.Equal(t, err, er)
if first == "" {
return
}
if len(lines) == 0 {
assert.Equal(t, first, string(o))
return
}
os := string(o)
assert.Regexp(t, "^"+first, os)
for _, line := range lines {
assert.Regexp(t, "(?m)^"+line, os)
}
}
}
func TestEntryUnmarshal(t *testing.T) {
when := time.Now()
whens := when.Format(DateFormat)
simple := []Meta{}
tests := []struct {
name string
in string
title string
date time.Time
fields []Meta
err error
}{
{"one-line", "@begin " + whens + " - A Title @end", "A Title", when, simple, nil},
{"rfc3999-date", "@begin " + when.Format(time.RFC3339) + " - A Title @end", "A Title", when, simple, nil},
{"multi-title", "@begin " + whens + " - A Title\nwith break @end", "A Title\nwith break", when, simple, nil},
{"no-title", "@begin " + whens + " - @end", "", when, simple, ErrorMissingTitle},
{"parse-error", "this is no good", "", when, simple, ErrorParsing},
{"no-date", "@begin - A Title @end", "A Title", when, simple, ErrorMissingDate},
{"bad-date", "@begin not-a-real date - A Title @end", "A Title", when, simple, ErrorParsing},
{"one-field", "@begin " + whens + " - A Title\n@age 41 @end", "A Title", when, []Meta{{"age", 41}}, nil},
{
"two-fields",
"@begin " + whens + " - A Title\n@age 41\n@cool true @end",
"A Title",
when,
[]Meta{{"age", 41}, {"cool", true}},
nil,
},
{
"obj-field",
"@begin " + whens + " - A Title\n" + `@me {"name":"Dan","coder":true} @end`,
"A Title",
when,
[]Meta{{"me", json.RawMessage(`{"name":"Dan","coder":true}`)}},
nil,
},
/* uncomment when implemented
{
"json-field",
"@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`,
"A Title",
when,
[]Meta{{"name", "Dan"}, {"coder", true}},
nil,
},
*/
}
for _, tt := range tests {
t.Run(tt.name, getEntryUnmarshalTestRunner(tt.in, tt.title, tt.date, tt.fields, tt.err))
}
}
func getEntryUnmarshalTestRunner(in string, title string, date time.Time, fields []Meta, err error) func(*testing.T) {
return func(t *testing.T) {
e := &Entry{}
er := e.UnmarshalText([]byte(in))
if err != nil {
assert.ErrorIs(t, er, err)
return
}
assert.Equal(t, title, e.Title)
assert.WithinRange(t, e.Date, date.Add(-time.Second), date.Add(time.Second))
for _, f := range fields {
got := false
for _, m := range e.Fields {
var mVal any = m.Value
var fVal any = f.Value
if mJ, ok := m.Value.(json.RawMessage); ok {
mVal = string(mJ)
}
if fJ, ok := f.Value.(json.RawMessage); ok {
fVal = string(fJ)
}
if m.Key == f.Key && mVal == fVal {
got = true
break
}
}
assert.Truef(t, got, "Couldn't find field %+v", f)
}
}
}
func TestScan(t *testing.T) {
in := "@begin date - Title\nlong\n@foo john\njones\n@bar 42@nobody @end"
read := strings.NewReader(in)
scan := bufio.NewScanner(read)
scan.Split(scanEntry)
assert.True(t, scan.Scan())
assert.Equal(t, "@begin date - Title\nlong", scan.Text())
assert.True(t, scan.Scan())
assert.Equal(t, "@foo john\njones", scan.Text())
assert.True(t, scan.Scan())
assert.Equal(t, "@bar 42@nobody", scan.Text())
assert.False(t, scan.Scan())
}
func TestEntryJsonMarshal(t *testing.T) {
when := time.Now()
whens := when.Format(time.RFC3339)
simple := []Meta{}
tests := []struct {
name string
title string
date time.Time
fields []Meta
out string
err error
}{
{"simple", "A Title", when, simple, `{"title":"A Title","date":"` + whens + `"}`, nil},
{"one-field", "A Title 2", when, []Meta{{"age", 41}}, `{"title":"A Title 2","date":"` + whens + `","age":41}`, nil},
{"skip-title-field", "A Title", when, []Meta{{"title", "Different title"}}, `{"title":"A Title","date":"` + whens + `"}`, nil},
{"skip-date-field", "A Title", when, []Meta{{"date", when.Add(time.Hour)}}, `{"title":"A Title","date":"` + whens + `"}`, nil},
{"skip-dupe-field", "A Title", when, []Meta{{"foo", "bar"}, {"foo", "baz"}}, `{"title":"A Title","date":"` + whens + `","foo": "bar"}`, nil},
{"two-fields", "A Title", when, []Meta{{"foo", "bar"}, {"baz", 42}}, `{"title":"A Title","date":"` + whens + `","foo": "bar","baz":42}`, nil},
{"empty-title", "", when, simple, "", ErrorMissingTitle},
{"empty-date", "A Title", time.Time{}, simple, "", ErrorMissingDate},
{"obj-field", "A Title", when, []Meta{{"obj", json.RawMessage(`{"foo":"bar","title":"Sub-title"}`)}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","title":"Sub-title"}}`, nil},
{"date-field", "A Title", when, []Meta{{"when", when.Add(time.Hour)}}, `{"title":"A Title","date":"` + whens + `","when":"` + when.Add(time.Hour).Format(time.RFC3339) + `"}`, nil},
}
for _, tt := range tests {
t.Run(tt.name, getEntryJsonMarshalTestRunner(tt.title, tt.date, tt.fields, tt.out, tt.err))
}
}
func getEntryJsonMarshalTestRunner(title string, date time.Time, fields []Meta, out string, err error) func(t *testing.T) {
return func(t *testing.T) {
e := Entry{title, date, fields}
o, er := json.Marshal(e)
if err == nil {
assert.JSONEq(t, out, string(o))
} else {
assert.ErrorIs(t, er, err)
}
}
}
func TestEntryJsonUnmarshal(t *testing.T) {
when := time.Now()
whens := when.Format(time.RFC3339)
simple := []Meta{}
tests := []struct {
name string
in string
title string
date time.Time
fields []Meta
err error
}{
{"simple", `{"title":"A Title","date":"` + whens + `"}`, "A Title", when, simple, nil},
{"missing-title", `{"date":"` + whens + `"}`, "", when, simple, ErrorMissingTitle},
{"missing-date", `{"title":"A Title"}`, "", when, simple, ErrorMissingDate},
{"empty-title", `{"title":"","date":"` + whens + `"}`, "", when, simple, ErrorMissingTitle},
{"empty-date", `{"title":"A Title","date":""}`, "", when, simple, ErrorMissingDate},
{"bad-date", `{"title":"A Title","date":"bad"}`, "", when, simple, ErrorParsing},
{
"single-field",
`{"title":"A Title","date":"` + whens + `","hello":"Hi"}`,
"A Title",
when,
[]Meta{{"hello", "Hi"}},
nil,
},
{
"many-fields",
`{"title":"A Title","date":"` + whens + `","hello":"Hi","bye":42,"b":true}`,
"A Title",
when,
[]Meta{{"hello", "Hi"}, {"bye", 42}, {"b", true}},
nil,
},
{
"date-field",
`{"title":"A Title","date":"` + whens + `","posted":"` + when.Add(-time.Hour).Format(time.RFC3339) + `"}`,
"A Title",
when,
[]Meta{{"posted", when.Add(-time.Hour)}},
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, getEntryJsonUnmarshalTestRunner(tt.in, tt.title, tt.date, tt.fields, tt.err))
}
}
func getEntryJsonUnmarshalTestRunner(in, title string, date time.Time, fields []Meta, err error) func(t *testing.T) {
return func(t *testing.T) {
e := new(Entry)
er := json.Unmarshal([]byte(in), e)
if err != nil {
assert.ErrorIs(t, er, err)
return
}
assert.Equal(t, title, e.Title)
assert.WithinRange(t, e.Date, date.Add(-time.Second), date.Add(time.Second))
for _, f := range fields {
got := false
for _, m := range e.Fields {
var mVal any = m.Value
var fVal any = f.Value
if mJ, ok := m.Value.(json.RawMessage); ok {
mVal = string(mJ)
}
if fJ, ok := f.Value.(json.RawMessage); ok {
fVal = string(fJ)
}
if m.Key == f.Key && mVal == fVal {
got = true
break
}
}
assert.Truef(t, got, "Couldn't find field %+v", f)
}
}
}