352 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			352 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package models
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"encoding"
 | |
| 	"encoding/json"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| // Type assertions
 | |
| var _ encoding.TextMarshaler = Entry{}
 | |
| var _ encoding.TextUnmarshaler = new(Entry)
 | |
| var _ json.Marshaler = Entry{}
 | |
| var _ json.Unmarshaler = new(Entry)
 | |
| 
 | |
| func TestPartialEntry(t *testing.T) {
 | |
| 	e := PartialEntry()
 | |
| 	assert.True(t, e.skipMissing)
 | |
| 	err := json.Unmarshal([]byte(`{"a":42}`), &e)
 | |
| 	assert.NoError(t, err)
 | |
| 	assert.Equal(t, "", e.Title)
 | |
| 	assert.Equal(t, time.Time{}, e.Date)
 | |
| 	require.Len(t, e.Fields, 1)
 | |
| 	f := e.Fields[0]
 | |
| 	assert.Equal(t, "a", f.Key)
 | |
| 	assert.Equal(t, int64(42), f.Value)
 | |
| }
 | |
| 
 | |
| 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,
 | |
| 		},
 | |
| 		{
 | |
| 			"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, false}
 | |
| 		o, er := en.MarshalText()
 | |
| 		assert.Equal(t, err, er)
 | |
| 		if first == "" {
 | |
| 			return
 | |
| 		}
 | |
| 		if len(lines) == 0 {
 | |
| 			assert.Equal(t, "\n"+first, string(o))
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		os := string(o)
 | |
| 		assert.Regexp(t, "^\n"+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,
 | |
| 		},
 | |
| 		{
 | |
| 			"json-field",
 | |
| 			"@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`,
 | |
| 			"Some Guy",
 | |
| 			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. We have %+v", f, e.Fields)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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},
 | |
| 		{"json-field", "A Title", when, []Meta{{"json", json.RawMessage(`{"age": 41, "cool": true, "name": "Jim"}`)}}, `{"title":"A Title","date":"` + whens + `","age":41,"cool": true, "name": "Jim"}`, 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, false}
 | |
| 		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().Truncate(time.Second)
 | |
| 	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},
 | |
| 		{"bad-json", `{"title":"A Title","date":"`, "", 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,"fl":42.13}`,
 | |
| 			"A Title",
 | |
| 			when,
 | |
| 			[]Meta{{"hello", "Hi"}, {"bye", int64(42)}, {"b", true}, {"fl", float64(42.13)}},
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			"date-field",
 | |
| 			`{"title":"A Title","date":"` + whens + `","posted":"` + when.Add(-time.Hour).In(time.UTC).Format(time.RFC3339) + `"}`,
 | |
| 			"A Title",
 | |
| 			when,
 | |
| 			[]Meta{{"posted", when.Add(-time.Hour)}},
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			"json-field",
 | |
| 			`{"title":"A Title","date":"` + whens + `","json":{"age": 41, "cool": true, "name": "Jim"}}`,
 | |
| 			"A Title",
 | |
| 			when,
 | |
| 			[]Meta{{"age", int64(41)}, {"cool", true}, {"name", "Jim"}},
 | |
| 			nil,
 | |
| 		},
 | |
| 		{
 | |
| 			"json-field-embed",
 | |
| 			`{"title":"A Title","date":"` + whens + `","json":"{\"age\": 41, \"cool\": true, \"name\": \"Jim\"}"}`,
 | |
| 			"A Title",
 | |
| 			when,
 | |
| 			[]Meta{{"age", int64(41)}, {"cool", true}, {"name", "Jim"}},
 | |
| 			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 := e.UnmarshalJSON([]byte(in))
 | |
| 		if err != nil {
 | |
| 			assert.ErrorIs(t, er, err)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		assert.Nil(t, er)
 | |
| 		assert.Equal(t, title, e.Title)
 | |
| 		assert.WithinRange(t, e.Date, date.Add(-time.Second), date.Add(time.Second))
 | |
| 		assert.Len(t, e.Fields, len(fields))
 | |
| 		for _, f := range fields {
 | |
| 			got := false
 | |
| 			fTime, isTime := f.Value.(time.Time)
 | |
| 			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
 | |
| 				}
 | |
| 				if isTime && m.Key == f.Key {
 | |
| 					mTime, _ := mVal.(time.Time)
 | |
| 					if assert.WithinRange(t, mTime, fTime.Add(-2*time.Second), fTime.Add(2*time.Second)) {
 | |
| 						got = true
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			assert.Truef(t, got, "Couldn't find field %+v. We have %+v", f, e.Fields)
 | |
| 		}
 | |
| 	}
 | |
| }
 |