fix tests

This commit is contained in:
Erik Winter 2024-12-23 11:30:24 +01:00
parent 041db5abe9
commit c439f2b483
19 changed files with 202 additions and 350 deletions

View File

@ -10,7 +10,6 @@ import (
type EventBody struct { type EventBody struct {
Title string `json:"title"` Title string `json:"title"`
Date Date `json:"date"`
Time Time `json:"time"` Time Time `json:"time"`
Duration time.Duration `json:"duration"` Duration time.Duration `json:"duration"`
} }
@ -48,6 +47,7 @@ func (e *EventBody) UnmarshalJSON(data []byte) error {
type Event struct { type Event struct {
ID string `json:"id"` ID string `json:"id"`
Date Date `json:"date"`
Recurrer Recurrer `json:"recurrer"` Recurrer Recurrer `json:"recurrer"`
RecurNext Date `json:"recurNext"` RecurNext Date `json:"recurNext"`
EventBody EventBody
@ -64,6 +64,7 @@ func NewEvent(i Item) (Event, error) {
} }
e.ID = i.ID e.ID = i.ID
e.Date = i.Date
e.Recurrer = i.Recurrer e.Recurrer = i.Recurrer
e.RecurNext = i.RecurNext e.RecurNext = i.RecurNext
@ -79,6 +80,7 @@ func (e Event) Item() (Item, error) {
return Item{ return Item{
ID: e.ID, ID: e.ID,
Kind: KindEvent, Kind: KindEvent,
Date: e.Date,
Recurrer: e.Recurrer, Recurrer: e.Recurrer,
RecurNext: e.RecurNext, RecurNext: e.RecurNext,
Body: string(body), Body: string(body),
@ -105,3 +107,10 @@ func EventDiff(a, b Event) string {
return cmp.Diff(string(aJSON), string(bJSON)) return cmp.Diff(string(aJSON), string(bJSON))
} }
func EventDiffs(a, b []Event) string {
aJSON, _ := json.Marshal(a)
bJSON, _ := json.Marshal(b)
return cmp.Diff(string(aJSON), string(bJSON))
}

View File

@ -25,10 +25,10 @@ func TestNewEvent(t *testing.T) {
name: "wrong kind", name: "wrong kind",
it: item.Item{ it: item.Item{
ID: "a", ID: "a",
Date: item.NewDate(2024, 9, 20),
Kind: item.KindTask, Kind: item.KindTask,
Body: `{ Body: `{
"title":"title", "title":"title",
"date":"2024-09-20",
"time":"08:00", "time":"08:00",
"duration":"1h" "duration":"1h"
}`, }`,
@ -49,20 +49,20 @@ func TestNewEvent(t *testing.T) {
it: item.Item{ it: item.Item{
ID: "a", ID: "a",
Kind: item.KindEvent, Kind: item.KindEvent,
Date: item.NewDate(2024, 9, 20),
Recurrer: item.NewRecurrer("2024-12-08, daily"), Recurrer: item.NewRecurrer("2024-12-08, daily"),
Body: `{ Body: `{
"title":"title", "title":"title",
"date":"2024-09-20",
"time":"08:00", "time":"08:00",
"duration":"1h" "duration":"1h"
}`, }`,
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: "a", ID: "a",
Date: item.NewDate(2024, 9, 20),
Recurrer: item.NewRecurrer("2024-12-08, daily"), Recurrer: item.NewRecurrer("2024-12-08, daily"),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Date: item.NewDate(2024, 9, 20),
Time: item.NewTime(8, 0), Time: item.NewTime(8, 0),
Duration: oneHour, Duration: oneHour,
}, },
@ -102,16 +102,16 @@ func TestEventItem(t *testing.T) {
expItem: item.Item{ expItem: item.Item{
Kind: item.KindEvent, Kind: item.KindEvent,
Updated: time.Time{}, Updated: time.Time{},
Body: `{"duration":"0s","title":"","date":"no date","time":"00:00"}`, Body: `{"duration":"0s","title":"","time":"00:00"}`,
}, },
}, },
{ {
name: "normal", name: "normal",
event: item.Event{ event: item.Event{
ID: "a", ID: "a",
Date: item.NewDate(2024, 9, 23),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Date: item.NewDate(2024, 9, 23),
Time: item.NewTime(8, 0), Time: item.NewTime(8, 0),
Duration: oneHour, Duration: oneHour,
}, },
@ -120,7 +120,8 @@ func TestEventItem(t *testing.T) {
ID: "a", ID: "a",
Kind: item.KindEvent, Kind: item.KindEvent,
Updated: time.Time{}, Updated: time.Time{},
Body: `{"duration":"1h0m0s","title":"title","date":"2024-09-23","time":"08:00"}`, Date: item.NewDate(2024, 9, 23),
Body: `{"duration":"1h0m0s","title":"title","time":"08:00"}`,
}, },
}, },
} { } {
@ -159,8 +160,8 @@ func TestEventValidate(t *testing.T) {
name: "missing title", name: "missing title",
event: item.Event{ event: item.Event{
ID: "a", ID: "a",
EventBody: item.EventBody{
Date: item.NewDate(2024, 9, 20), Date: item.NewDate(2024, 9, 20),
EventBody: item.EventBody{
Time: item.NewTime(8, 0), Time: item.NewTime(8, 0),
Duration: oneHour, Duration: oneHour,
}, },
@ -181,9 +182,9 @@ func TestEventValidate(t *testing.T) {
name: "no duration", name: "no duration",
event: item.Event{ event: item.Event{
ID: "a", ID: "a",
Date: item.NewDate(2024, 9, 20),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Date: item.NewDate(2024, 9, 20),
Time: item.NewTime(8, 0), Time: item.NewTime(8, 0),
}, },
}, },
@ -192,9 +193,9 @@ func TestEventValidate(t *testing.T) {
name: "valid", name: "valid",
event: item.Event{ event: item.Event{
ID: "a", ID: "a",
Date: item.NewDate(2024, 9, 20),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Date: item.NewDate(2024, 9, 20),
Time: item.NewTime(8, 0), Time: item.NewTime(8, 0),
Duration: oneHour, Duration: oneHour,
}, },

View File

@ -3,7 +3,6 @@ package command
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
@ -27,9 +26,7 @@ func NewAdd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage
FlagOn: &FlagDate{}, FlagOn: &FlagDate{},
FlagAt: &FlagTime{}, FlagAt: &FlagTime{},
FlagFor: &FlagDuration{}, FlagFor: &FlagDuration{},
FlagRecStart: &FlagDate{}, FlagRec: &FlagRecurrer{},
FlagRecPeriod: &FlagPeriod{},
FlagRecCount: &FlagInt{},
}, },
}, },
} }
@ -71,48 +68,22 @@ func (add *Add) Execute(main []string, flags map[string]string) error {
return fmt.Errorf("could not set duration to 24 hours") return fmt.Errorf("could not set duration to 24 hours")
} }
} }
var recCount int
for _, f := range []string{FlagRecStart, FlagRecPeriod, FlagRecCount} {
if as.IsSet(f) {
recCount++
}
}
if recCount != 0 && recCount != 3 {
return fmt.Errorf("rec-start, rec-period and rec-count must either all be present, or none of them")
}
return add.do() return add.do()
} }
func (add *Add) do() error { func (add *Add) do() error {
as := add.argSet as := add.argSet
start := as.GetTime(FlagOn)
if as.IsSet(FlagAt) {
at := as.GetTime(FlagAt)
h := time.Duration(at.Hour()) * time.Hour
m := time.Duration(at.Minute()) * time.Minute
start = start.Add(h).Add(m)
}
e := item.Event{ e := item.Event{
ID: uuid.New().String(), ID: uuid.New().String(),
Date: as.GetDate(FlagOn),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: as.Main, Title: as.Main,
Start: start, Time: as.GetTime(FlagAt),
Duration: as.GetDuration(FlagFor),
}, },
} }
if as.IsSet(FlagFor) {
e.Duration = as.GetDuration(FlagFor)
}
if as.IsSet(FlagRecStart) {
e.Recurrer = &item.Recur{
Start: as.GetTime(FlagRecStart),
Period: as.GetRecurPeriod(FlagRecPeriod),
Count: as.GetInt(FlagRecCount),
}
}
if err := add.eventRepo.Store(e); err != nil { if err := add.eventRepo.Store(e); err != nil {
return fmt.Errorf("could not store event: %v", err) return fmt.Errorf("could not store event: %v", err)
} }

View File

@ -4,7 +4,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/google/go-cmp/cmp"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/command"
"go-mod.ewintr.nl/planner/plan/storage/memory" "go-mod.ewintr.nl/planner/plan/storage/memory"
@ -13,13 +12,11 @@ import (
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
t.Parallel() t.Parallel()
aDateStr := "2024-11-02" aDate := item.NewDate(2024, 11, 2)
aDate := time.Date(2024, 11, 2, 0, 0, 0, 0, time.UTC) aTime := item.NewTime(12, 0)
aTimeStr := "12:00"
aDay := time.Duration(24) * time.Hour aDay := time.Duration(24) * time.Hour
anHourStr := "1h" anHourStr := "1h"
anHour := time.Hour anHour := time.Hour
aDateAndTime := time.Date(2024, 11, 2, 12, 0, 0, 0, time.UTC)
for _, tc := range []struct { for _, tc := range []struct {
name string name string
@ -36,7 +33,7 @@ func TestAdd(t *testing.T) {
name: "title missing", name: "title missing",
main: []string{"add"}, main: []string{"add"},
flags: map[string]string{ flags: map[string]string{
command.FlagOn: aDateStr, command.FlagOn: aDate.String(),
}, },
expErr: true, expErr: true,
}, },
@ -49,46 +46,31 @@ func TestAdd(t *testing.T) {
name: "only date", name: "only date",
main: []string{"add", "title"}, main: []string{"add", "title"},
flags: map[string]string{ flags: map[string]string{
command.FlagOn: aDateStr, command.FlagOn: aDate.String(),
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: "title", ID: "title",
Date: aDate,
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Start: aDate,
Duration: aDay, Duration: aDay,
}, },
}, },
}, },
{
name: "date and time",
main: []string{"add", "title"},
flags: map[string]string{
command.FlagOn: aDateStr,
command.FlagAt: aTimeStr,
},
expEvent: item.Event{
ID: "title",
EventBody: item.EventBody{
Title: "title",
Start: aDateAndTime,
Duration: anHour,
},
},
},
{ {
name: "date, time and duration", name: "date, time and duration",
main: []string{"add", "title"}, main: []string{"add", "title"},
flags: map[string]string{ flags: map[string]string{
command.FlagOn: aDateStr, command.FlagOn: aDate.String(),
command.FlagAt: aTimeStr, command.FlagAt: aTime.String(),
command.FlagFor: anHourStr, command.FlagFor: anHourStr,
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: "title", ID: "title",
Date: aDate,
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Start: aDateAndTime, Time: aTime,
Duration: anHour, Duration: anHour,
}, },
}, },
@ -97,53 +79,11 @@ func TestAdd(t *testing.T) {
name: "date and duration", name: "date and duration",
main: []string{"add", "title"}, main: []string{"add", "title"},
flags: map[string]string{ flags: map[string]string{
command.FlagOn: aDateStr, command.FlagOn: aDate.String(),
command.FlagFor: anHourStr, command.FlagFor: anHourStr,
}, },
expErr: true, expErr: true,
}, },
{
name: "rec-start without rec-period",
main: []string{"add", "title"},
flags: map[string]string{
command.FlagOn: aDateStr,
command.FlagRecStart: "2024-12-08",
},
expErr: true,
},
{
name: "rec-period without rec-start",
main: []string{"add", "title"},
flags: map[string]string{
command.FlagOn: aDateStr,
command.FlagRecPeriod: "day",
},
expErr: true,
},
{
name: "rec-start with rec-period and rec-count",
main: []string{"add", "title"},
flags: map[string]string{
command.FlagOn: aDateStr,
command.FlagRecStart: "2024-12-08",
command.FlagRecPeriod: "day",
command.FlagRecCount: "1",
},
expEvent: item.Event{
ID: "title",
Recurrer: &item.Recur{
Start: time.Date(2024, 12, 8, 0, 0, 0, 0, time.UTC),
Period: item.PeriodDay,
Count: 1,
},
RecurNext: time.Time{},
EventBody: item.EventBody{
Title: "title",
Start: aDate,
Duration: aDay,
},
},
},
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
eventRepo := memory.NewEvent() eventRepo := memory.NewEvent()
@ -181,7 +121,7 @@ func TestAdd(t *testing.T) {
t.Errorf("exp string not te be empty") t.Errorf("exp string not te be empty")
} }
tc.expEvent.ID = actEvents[0].ID tc.expEvent.ID = actEvents[0].ID
if diff := cmp.Diff(tc.expEvent, actEvents[0]); diff != "" { if diff := item.EventDiff(tc.expEvent, actEvents[0]); diff != "" {
t.Errorf("(exp -, got +)\n%s", diff) t.Errorf("(exp -, got +)\n%s", diff)
} }

View File

@ -40,14 +40,26 @@ func (as *ArgSet) GetString(name string) string {
return val return val
} }
func (as *ArgSet) GetTime(name string) time.Time { func (as *ArgSet) GetDate(name string) item.Date {
flag, ok := as.Flags[name] flag, ok := as.Flags[name]
if !ok { if !ok {
return time.Time{} return item.Date{}
} }
val, ok := flag.Get().(time.Time) val, ok := flag.Get().(item.Date)
if !ok { if !ok {
return time.Time{} return item.Date{}
}
return val
}
func (as *ArgSet) GetTime(name string) item.Time {
flag, ok := as.Flags[name]
if !ok {
return item.Time{}
}
val, ok := flag.Get().(item.Time)
if !ok {
return item.Time{}
} }
return val return val
} }
@ -64,14 +76,14 @@ func (as *ArgSet) GetDuration(name string) time.Duration {
return val return val
} }
func (as *ArgSet) GetRecurPeriod(name string) item.RecurPeriod { func (as *ArgSet) GetRecurrer(name string) item.Recurrer {
flag, ok := as.Flags[name] flag, ok := as.Flags[name]
if !ok { if !ok {
return item.RecurPeriod("") return nil
} }
val, ok := flag.Get().(item.RecurPeriod) val, ok := flag.Get().(item.Recurrer)
if !ok { if !ok {
return item.RecurPeriod("") return nil
} }
return val return val
} }

View File

@ -56,11 +56,11 @@ func TestArgSet(t *testing.T) {
{ {
name: "recur period flag success", name: "recur period flag success",
flags: map[string]command.Flag{ flags: map[string]command.Flag{
"period": &command.FlagPeriod{Name: "period"}, "recur": &command.FlagRecurrer{Name: "recur"},
}, },
flagName: "period", flagName: "recur",
setValue: "month", setValue: "2024-12-23, daily",
exp: item.PeriodMonth, exp: item.NewRecurrer("2024-12-23, daily"),
}, },
{ {
name: "unknown flag error", name: "unknown flag error",
@ -95,26 +95,9 @@ func TestArgSet(t *testing.T) {
return return
} }
// Verify IsSet() returns true after setting
if !as.IsSet(tt.flagName) { if !as.IsSet(tt.flagName) {
t.Errorf("ArgSet.IsSet() = false, want true for flag %s", tt.flagName) t.Errorf("ArgSet.IsSet() = false, want true for flag %s", tt.flagName)
} }
// Verify the value was set correctly based on flag type
switch v := tt.exp.(type) {
case string:
if got := as.GetString(tt.flagName); got != v {
t.Errorf("ArgSet.GetString() = %v, want %v", got, v)
}
case time.Time:
if got := as.GetTime(tt.flagName); !got.Equal(v) {
t.Errorf("ArgSet.GetTime() = %v, want %v", got, v)
}
case time.Duration:
if got := as.GetDuration(tt.flagName); got != v {
t.Errorf("ArgSet.GetDuration() = %v, want %v", got, v)
}
}
}) })
} }
} }

View File

@ -11,9 +11,7 @@ const (
FlagOn = "on" FlagOn = "on"
FlagAt = "at" FlagAt = "at"
FlagFor = "for" FlagFor = "for"
FlagRecStart = "rec-start" FlagRec = "rec"
FlagRecPeriod = "rec-period"
FlagRecCount = "rec-count"
) )
type Command interface { type Command interface {

View File

@ -3,7 +3,6 @@ package command_test
import ( import (
"errors" "errors"
"testing" "testing"
"time"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/command"
@ -16,9 +15,9 @@ func TestDelete(t *testing.T) {
e := item.Event{ e := item.Event{
ID: "id", ID: "id",
Date: item.NewDate(2024, 10, 7),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "name", Title: "name",
Start: time.Date(2024, 10, 7, 9, 30, 0, 0, time.UTC),
}, },
} }

View File

@ -3,7 +3,6 @@ package command
import ( import (
"errors" "errors"
"fmt" "fmt"
"slices"
"strconv" "strconv"
"time" "time"
@ -46,35 +45,35 @@ func (fs *FlagString) Get() any {
type FlagDate struct { type FlagDate struct {
Name string Name string
Value time.Time Value item.Date
} }
func (ft *FlagDate) Set(val string) error { func (fd *FlagDate) Set(val string) error {
d, err := time.Parse(DateFormat, val) d := item.NewDateFromString(val)
if err != nil { if d.IsZero() {
return fmt.Errorf("could not parse date: %v", d) return fmt.Errorf("could not parse date: %v", d)
} }
ft.Value = d fd.Value = d
return nil return nil
} }
func (ft *FlagDate) IsSet() bool { func (fd *FlagDate) IsSet() bool {
return !ft.Value.IsZero() return !fd.Value.IsZero()
} }
func (fs *FlagDate) Get() any { func (fd *FlagDate) Get() any {
return fs.Value return fd.Value
} }
type FlagTime struct { type FlagTime struct {
Name string Name string
Value time.Time Value item.Time
} }
func (ft *FlagTime) Set(val string) error { func (ft *FlagTime) Set(val string) error {
d, err := time.Parse(TimeFormat, val) d := item.NewTimeFromString(val)
if err != nil { if d.IsZero() {
return fmt.Errorf("could not parse date: %v", d) return fmt.Errorf("could not parse date: %v", d)
} }
ft.Value = d ft.Value = d
@ -112,25 +111,25 @@ func (fs *FlagDuration) Get() any {
return fs.Value return fs.Value
} }
type FlagPeriod struct { type FlagRecurrer struct {
Name string Name string
Value item.RecurPeriod Value item.Recurrer
} }
func (fp *FlagPeriod) Set(val string) error { func (fr *FlagRecurrer) Set(val string) error {
if !slices.Contains(item.ValidPeriods, item.RecurPeriod(val)) { fr.Value = item.NewRecurrer(val)
return fmt.Errorf("not a valid period: %v", val) if fr.Value == nil {
return fmt.Errorf("not a valid recurrer: %v", val)
} }
fp.Value = item.RecurPeriod(val)
return nil return nil
} }
func (fp *FlagPeriod) IsSet() bool { func (fr *FlagRecurrer) IsSet() bool {
return fp.Value != "" return fr.Value != nil
} }
func (fp *FlagPeriod) Get() any { func (fr *FlagRecurrer) Get() any {
return fp.Value return fr.Value
} }
type FlagInt struct { type FlagInt struct {

View File

@ -37,14 +37,13 @@ func TestFlagString(t *testing.T) {
func TestFlagDate(t *testing.T) { func TestFlagDate(t *testing.T) {
t.Parallel() t.Parallel()
valid := time.Date(2024, 11, 20, 0, 0, 0, 0, time.UTC) valid := item.NewDate(2024, 11, 20)
validStr := "2024-11-20"
f := command.FlagDate{} f := command.FlagDate{}
if f.IsSet() { if f.IsSet() {
t.Errorf("exp false, got true") t.Errorf("exp false, got true")
} }
if err := f.Set(validStr); err != nil { if err := f.Set(valid.String()); err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
@ -52,26 +51,25 @@ func TestFlagDate(t *testing.T) {
t.Errorf("exp true, got false") t.Errorf("exp true, got false")
} }
act, ok := f.Get().(time.Time) act, ok := f.Get().(item.Date)
if !ok { if !ok {
t.Errorf("exp true, got false") t.Errorf("exp true, got false")
} }
if act != valid { if act.String() != valid.String() {
t.Errorf("exp %v, got %v", valid, act) t.Errorf("exp %v, got %v", valid.String(), act.String())
} }
} }
func TestFlagTime(t *testing.T) { func TestFlagTime(t *testing.T) {
t.Parallel() t.Parallel()
valid := time.Date(0, 1, 1, 12, 30, 0, 0, time.UTC) valid := item.NewTime(12, 30)
validStr := "12:30"
f := command.FlagTime{} f := command.FlagTime{}
if f.IsSet() { if f.IsSet() {
t.Errorf("exp false, got true") t.Errorf("exp false, got true")
} }
if err := f.Set(validStr); err != nil { if err := f.Set(valid.String()); err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
@ -79,12 +77,12 @@ func TestFlagTime(t *testing.T) {
t.Errorf("exp true, got false") t.Errorf("exp true, got false")
} }
act, ok := f.Get().(time.Time) act, ok := f.Get().(item.Time)
if !ok { if !ok {
t.Errorf("exp true, got false") t.Errorf("exp true, got false")
} }
if act != valid { if act.String() != valid.String() {
t.Errorf("exp %v, got %v", valid, act) t.Errorf("exp %v, got %v", valid.String(), act.String())
} }
} }
@ -115,12 +113,12 @@ func TestFlagDurationTime(t *testing.T) {
} }
} }
func TestFlagPeriod(t *testing.T) { func TestFlagRecurrer(t *testing.T) {
t.Parallel() t.Parallel()
valid := item.PeriodMonth validStr := "2024-12-23, daily"
validStr := "month" valid := item.NewRecurrer(validStr)
f := command.FlagPeriod{} f := command.FlagRecurrer{}
if f.IsSet() { if f.IsSet() {
t.Errorf("exp false, got true") t.Errorf("exp false, got true")
} }
@ -133,7 +131,7 @@ func TestFlagPeriod(t *testing.T) {
t.Errorf("exp true, got false") t.Errorf("exp true, got false")
} }
act, ok := f.Get().(item.RecurPeriod) act, ok := f.Get().(item.Recurrer)
if !ok { if !ok {
t.Errorf("exp true, got false") t.Errorf("exp true, got false")
} }

View File

@ -2,7 +2,6 @@ package command
import ( import (
"fmt" "fmt"
"time"
"go-mod.ewintr.nl/planner/plan/storage" "go-mod.ewintr.nl/planner/plan/storage"
) )
@ -41,7 +40,7 @@ func (list *List) do() error {
if !ok { if !ok {
return fmt.Errorf("could not find local id for %s", e.ID) return fmt.Errorf("could not find local id for %s", e.ID)
} }
fmt.Printf("%s\t%d\t%s\t%s\t%s\n", e.ID, lid, e.Title, e.Start.Format(time.DateTime), e.Duration.String()) fmt.Printf("%s\t%d\t%s\t%s\t%s\n", e.ID, lid, e.Title, e.Date.String(), e.Duration.String())
} }
return nil return nil

View File

@ -2,7 +2,6 @@ package command_test
import ( import (
"testing" "testing"
"time"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/command"
@ -16,9 +15,9 @@ func TestList(t *testing.T) {
localRepo := memory.NewLocalID() localRepo := memory.NewLocalID()
e := item.Event{ e := item.Event{
ID: "id", ID: "id",
Date: item.NewDate(2024, 10, 7),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "name", Title: "name",
Start: time.Date(2024, 10, 7, 9, 30, 0, 0, time.UTC),
}, },
} }
if err := eventRepo.Store(e); err != nil { if err := eventRepo.Store(e); err != nil {

View File

@ -138,9 +138,9 @@ func TestSyncReceive(t *testing.T) {
}}, }},
expEvent: []item.Event{{ expEvent: []item.Event{{
ID: "a", ID: "a",
Date: item.NewDate(2024, 10, 23),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Start: time.Date(2024, 10, 23, 8, 0, 0, 0, time.UTC),
Duration: oneHour, Duration: oneHour,
}, },
}}, }},
@ -152,9 +152,9 @@ func TestSyncReceive(t *testing.T) {
name: "update existing", name: "update existing",
present: []item.Event{{ present: []item.Event{{
ID: "a", ID: "a",
Date: item.NewDate(2024, 10, 23),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "title", Title: "title",
Start: time.Date(2024, 10, 23, 8, 0, 0, 0, time.UTC),
Duration: oneHour, Duration: oneHour,
}, },
}}, }},
@ -169,9 +169,9 @@ func TestSyncReceive(t *testing.T) {
}}, }},
expEvent: []item.Event{{ expEvent: []item.Event{{
ID: "a", ID: "a",
Date: item.NewDate(2024, 10, 23),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "new title", Title: "new title",
Start: time.Date(2024, 10, 23, 8, 0, 0, 0, time.UTC),
Duration: oneHour, Duration: oneHour,
}, },
}}, }},
@ -210,7 +210,7 @@ func TestSyncReceive(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
if diff := cmp.Diff(tc.expEvent, actEvents); diff != "" { if diff := item.EventDiffs(tc.expEvent, actEvents); diff != "" {
t.Errorf("(exp +, got -)\n%s", diff) t.Errorf("(exp +, got -)\n%s", diff)
} }
actLocalIDs, err := localIDRepo.FindAll() actLocalIDs, err := localIDRepo.FindAll()

View File

@ -4,9 +4,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/storage" "go-mod.ewintr.nl/planner/plan/storage"
) )
@ -29,8 +27,7 @@ func NewUpdate(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo st
FlagOn: &FlagDate{}, FlagOn: &FlagDate{},
FlagAt: &FlagTime{}, FlagAt: &FlagTime{},
FlagFor: &FlagDuration{}, FlagFor: &FlagDuration{},
FlagRecStart: &FlagDate{}, FlagRec: &FlagRecurrer{},
FlagRecPeriod: &FlagPeriod{},
}, },
}, },
} }
@ -87,35 +84,17 @@ func (update *Update) do() error {
if as.Main != "" { if as.Main != "" {
e.Title = as.Main e.Title = as.Main
} }
if as.IsSet(FlagOn) || as.IsSet(FlagAt) {
on := time.Date(e.Start.Year(), e.Start.Month(), e.Start.Day(), 0, 0, 0, 0, time.UTC)
atH := time.Duration(e.Start.Hour()) * time.Hour
atM := time.Duration(e.Start.Minute()) * time.Minute
if as.IsSet(FlagOn) { if as.IsSet(FlagOn) {
on = as.GetTime(FlagOn) e.Date = as.GetDate(FlagOn)
} }
if as.IsSet(FlagAt) { if as.IsSet(FlagAt) {
at := as.GetTime(FlagAt) e.Time = as.GetTime(FlagAt)
atH = time.Duration(at.Hour()) * time.Hour
atM = time.Duration(at.Minute()) * time.Minute
} }
e.Start = on.Add(atH).Add(atM)
}
if as.IsSet(FlagFor) { if as.IsSet(FlagFor) {
e.Duration = as.GetDuration(FlagFor) e.Duration = as.GetDuration(FlagFor)
} }
if as.IsSet(FlagRecStart) || as.IsSet(FlagRecPeriod) { if as.IsSet(FlagRec) {
if e.Recurrer == nil { e.Recurrer = as.GetRecurrer(FlagRec)
e.Recurrer = &item.Recur{}
}
if as.IsSet(FlagRecStart) {
e.Recurrer.Start = as.GetTime(FlagRecStart)
}
if as.IsSet(FlagRecPeriod) {
e.Recurrer.Period = as.GetRecurPeriod(FlagRecPeriod)
}
} }
if !e.Valid() { if !e.Valid() {

View File

@ -5,7 +5,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/google/go-cmp/cmp"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/command"
"go-mod.ewintr.nl/planner/plan/storage/memory" "go-mod.ewintr.nl/planner/plan/storage/memory"
@ -21,7 +20,8 @@ func TestUpdateExecute(t *testing.T) {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
title := "title" title := "title"
start := time.Date(2024, 10, 6, 10, 0, 0, 0, time.UTC) aDate := item.NewDate(2024, 10, 6)
aTime := item.NewTime(10, 0)
twoHour, err := time.ParseDuration("2h") twoHour, err := time.ParseDuration("2h")
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
@ -50,9 +50,10 @@ func TestUpdateExecute(t *testing.T) {
main: []string{"update", fmt.Sprintf("%d", lid), "updated"}, main: []string{"update", fmt.Sprintf("%d", lid), "updated"},
expEvent: item.Event{ expEvent: item.Event{
ID: eid, ID: eid,
Date: item.NewDate(2024, 10, 6),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: "updated", Title: "updated",
Start: start, Time: aTime,
Duration: oneHour, Duration: oneHour,
}, },
}, },
@ -75,9 +76,10 @@ func TestUpdateExecute(t *testing.T) {
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: eid, ID: eid,
Date: item.NewDate(2024, 10, 2),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: title, Title: title,
Start: time.Date(2024, 10, 2, 10, 0, 0, 0, time.UTC), Time: aTime,
Duration: oneHour, Duration: oneHour,
}, },
}, },
@ -100,9 +102,10 @@ func TestUpdateExecute(t *testing.T) {
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: eid, ID: eid,
Date: item.NewDate(2024, 10, 6),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: title, Title: title,
Start: time.Date(2024, 10, 6, 11, 0, 0, 0, time.UTC), Time: item.NewTime(11, 0),
Duration: oneHour, Duration: oneHour,
}, },
}, },
@ -117,9 +120,10 @@ func TestUpdateExecute(t *testing.T) {
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: eid, ID: eid,
Date: item.NewDate(2024, 10, 2),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: title, Title: title,
Start: time.Date(2024, 10, 2, 11, 0, 0, 0, time.UTC), Time: item.NewTime(11, 0),
Duration: oneHour, Duration: oneHour,
}, },
}, },
@ -142,61 +146,35 @@ func TestUpdateExecute(t *testing.T) {
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: eid, ID: eid,
Date: item.NewDate(2024, 10, 6),
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: title, Title: title,
Start: time.Date(2024, 10, 6, 10, 0, 0, 0, time.UTC), Time: aTime,
Duration: twoHour, Duration: twoHour,
}, },
}, },
}, },
{ {
name: "invalid rec start", name: "invalid rec",
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ flags: map[string]string{
"rec-start": "invalud", "rec": "invalud",
}, },
expErr: true, expErr: true,
}, },
{ {
name: "valid rec start", name: "valid rec",
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ flags: map[string]string{
"rec-start": "2024-12-08", "rec": "2024-12-08, daily",
}, },
expEvent: item.Event{ expEvent: item.Event{
ID: eid, ID: eid,
Recurrer: &item.Recur{ Date: aDate,
Start: time.Date(2024, 12, 8, 0, 0, 0, 0, time.UTC), Recurrer: item.NewRecurrer("2024-12-08, daily"),
},
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: title, Title: title,
Start: start, Time: aTime,
Duration: oneHour,
},
},
},
{
name: "invalid rec period",
main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{
"rec-period": "invalid",
},
expErr: true,
},
{
name: "valid rec period",
main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{
"rec-period": "month",
},
expEvent: item.Event{
ID: eid,
Recurrer: &item.Recur{
Period: item.PeriodMonth,
},
EventBody: item.EventBody{
Title: title,
Start: start,
Duration: oneHour, Duration: oneHour,
}, },
}, },
@ -208,9 +186,10 @@ func TestUpdateExecute(t *testing.T) {
syncRepo := memory.NewSync() syncRepo := memory.NewSync()
if err := eventRepo.Store(item.Event{ if err := eventRepo.Store(item.Event{
ID: eid, ID: eid,
Date: aDate,
EventBody: item.EventBody{ EventBody: item.EventBody{
Title: title, Title: title,
Start: start, Time: aTime,
Duration: oneHour, Duration: oneHour,
}, },
}); err != nil { }); err != nil {
@ -233,7 +212,7 @@ func TestUpdateExecute(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
if diff := cmp.Diff(tc.expEvent, actEvent); diff != "" { if diff := item.EventDiff(tc.expEvent, actEvent); diff != "" {
t.Errorf("(exp -, got +)\n%s", diff) t.Errorf("(exp -, got +)\n%s", diff)
} }
updated, err := syncRepo.FindAll() updated, err := syncRepo.FindAll()

View File

@ -3,7 +3,6 @@ package memory
import ( import (
"testing" "testing"
"github.com/google/go-cmp/cmp"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
) )
@ -50,7 +49,7 @@ func TestEvent(t *testing.T) {
if actErr != nil { if actErr != nil {
t.Errorf("exp nil, got %v", actErr) t.Errorf("exp nil, got %v", actErr)
} }
if diff := cmp.Diff([]item.Event{e1, e2}, actEvents); diff != "" { if diff := item.EventDiffs([]item.Event{e1, e2}, actEvents); diff != "" {
t.Errorf("(exp -, got +)\n%s", diff) t.Errorf("(exp -, got +)\n%s", diff)
} }
} }

View File

@ -28,16 +28,17 @@ func (s *SqliteEvent) Store(event item.Event) error {
INSERT INTO events INSERT INTO events
(id, title, start, duration, recur) (id, title, start, duration, recur)
VALUES VALUES
(?, ?, ?, ?, ?) (?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE ON CONFLICT(id) DO UPDATE
SET SET
title=?, title=?,
start=?, date=?,
time=?
duration=?, duration=?,
recur=? recurrer=?
`, `,
event.ID, event.Title, event.Start.Format(timestampFormat), event.Duration.String(), recurStr, event.ID, event.Title, event.Date.String(), event.Time.String(), event.Duration.String(), recurStr,
event.Title, event.Start.Format(timestampFormat), event.Duration.String(), recurStr); err != nil { event.Title, event.Date.String(), event.Time.String(), event.Duration.String(), recurStr); err != nil {
return fmt.Errorf("%w: %v", ErrSqliteFailure, err) return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} }
return nil return nil
@ -45,37 +46,32 @@ recur=?
func (s *SqliteEvent) Find(id string) (item.Event, error) { func (s *SqliteEvent) Find(id string) (item.Event, error) {
var event item.Event var event item.Event
var durStr string var dateStr, timeStr, recurStr, durStr string
var recurStr *string
err := s.db.QueryRow(` err := s.db.QueryRow(`
SELECT id, title, start, duration, recur SELECT id, title, date, time, duration, recurrer
FROM events FROM events
WHERE id = ?`, id).Scan(&event.ID, &event.Title, &event.Start, &durStr, &recurStr) WHERE id = ?`, id).Scan(&event.ID, &event.Title, &dateStr, &timeStr, &durStr, &recurStr)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
return item.Event{}, fmt.Errorf("event not found: %w", err) return item.Event{}, fmt.Errorf("event not found: %w", err)
case err != nil: case err != nil:
return item.Event{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err) return item.Event{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} }
event.Date = item.NewDateFromString(dateStr)
event.Time = item.NewTimeFromString(timeStr)
dur, err := time.ParseDuration(durStr) dur, err := time.ParseDuration(durStr)
if err != nil { if err != nil {
return item.Event{}, fmt.Errorf("could not unmarshal recurrer: %v", err) return item.Event{}, fmt.Errorf("could not unmarshal recurrer: %v", err)
} }
event.Duration = dur event.Duration = dur
if recurStr != nil { event.Recurrer = item.NewRecurrer(recurStr)
var rec item.Recur
if err := json.Unmarshal([]byte(*recurStr), &rec); err != nil {
return item.Event{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
}
event.Recurrer = &rec
}
return event, nil return event, nil
} }
func (s *SqliteEvent) FindAll() ([]item.Event, error) { func (s *SqliteEvent) FindAll() ([]item.Event, error) {
rows, err := s.db.Query(` rows, err := s.db.Query(`
SELECT id, title, start, duration, recur SELECT id, title, date, time, duration, recurrer
FROM events`) FROM events`)
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
@ -84,23 +80,19 @@ FROM events`)
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var event item.Event var event item.Event
var durStr string var dateStr, timeStr, recurStr, durStr string
var recurStr *string if err := rows.Scan(&event.ID, &event.Title, &dateStr, &timeStr, &durStr, &recurStr); err != nil {
if err := rows.Scan(&event.ID, &event.Title, &event.Start, &durStr, &recurStr); err != nil {
return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} }
dur, err := time.ParseDuration(durStr) dur, err := time.ParseDuration(durStr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} }
event.Date = item.NewDateFromString(dateStr)
event.Time = item.NewTimeFromString(timeStr)
event.Duration = dur event.Duration = dur
if recurStr != nil { event.Recurrer = item.NewRecurrer(recurStr)
var rec item.Recur
if err := json.Unmarshal([]byte(*recurStr), &rec); err != nil {
return nil, fmt.Errorf("could not unmarshal recurrer: %v", err)
}
event.Recurrer = &rec
}
result = append(result, event) result = append(result, event)
} }

View File

@ -35,6 +35,13 @@ var migrations = []string{
`ALTER TABLE events DROP COLUMN recur_next`, `ALTER TABLE events DROP COLUMN recur_next`,
`ALTER TABLE events ADD COLUMN recur TEXT`, `ALTER TABLE events ADD COLUMN recur TEXT`,
`ALTER TABLE items ADD COLUMN recurrer TEXT`, `ALTER TABLE items ADD COLUMN recurrer TEXT`,
`ALTER TABLE events DROP COLUMN recur`,
`ALTER TABLE events ADD COLUMN recurrer TEXT NOT NULL`,
`ALTER TABLE events ADD COLUMN recur_next TEXT NOT NULL`,
`ALTER TABLE events DROP COLUMN start`,
`ALTER TABLE events ADD COLUMN date TEXT NOT NULL`,
`ALTER TABLE events ADD COLUMN time TEXT NOT NULL`,
`ALTER TABLE items ADD COLUMN recur_next TEXT NOT NULL`,
} }
var ( var (

View File

@ -2,7 +2,6 @@ package sqlite
import ( import (
"database/sql" "database/sql"
"encoding/json"
"fmt" "fmt"
"time" "time"
@ -18,7 +17,7 @@ func NewSqliteSync(db *sql.DB) *SqliteSync {
} }
func (s *SqliteSync) FindAll() ([]item.Item, error) { func (s *SqliteSync) FindAll() ([]item.Item, error) {
rows, err := s.db.Query("SELECT id, kind, updated, deleted, recurrer, body FROM items") rows, err := s.db.Query("SELECT id, kind, updated, deleted, recurrer, recur_next, body FROM items")
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: failed to query items: %v", ErrSqliteFailure, err) return nil, fmt.Errorf("%w: failed to query items: %v", ErrSqliteFailure, err)
} }
@ -27,8 +26,8 @@ func (s *SqliteSync) FindAll() ([]item.Item, error) {
var items []item.Item var items []item.Item
for rows.Next() { for rows.Next() {
var i item.Item var i item.Item
var updatedStr, recurStr string var updatedStr, recurStr, recurNextStr string
err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &recurStr, &i.Body) err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &recurStr, &recurNextStr, &i.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: failed to scan item: %v", ErrSqliteFailure, err) return nil, fmt.Errorf("%w: failed to scan item: %v", ErrSqliteFailure, err)
} }
@ -36,13 +35,9 @@ func (s *SqliteSync) FindAll() ([]item.Item, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse updated time: %v", err) return nil, fmt.Errorf("failed to parse updated time: %v", err)
} }
if recurStr != "" { i.Recurrer = item.NewRecurrer(recurStr)
var recurrer item.Recur i.RecurNext = item.NewDateFromString(recurNextStr)
if err := json.Unmarshal([]byte(recurStr), &recurrer); err != nil {
return nil, fmt.Errorf("failed to unmarshal recurrer: %v", err)
}
i.Recurrer = &recurrer
}
items = append(items, i) items = append(items, i)
} }
@ -59,22 +54,15 @@ func (s *SqliteSync) Store(i item.Item) error {
i.Updated = time.Now() i.Updated = time.Now()
} }
var recurStr string
if i.Recurrer != nil {
recurBytes, err := json.Marshal(i.Recurrer)
if err != nil {
return fmt.Errorf("failed to marshal recurrer: %v", err)
}
recurStr = string(recurBytes)
}
_, err := s.db.Exec( _, err := s.db.Exec(
"INSERT OR REPLACE INTO items (id, kind, updated, deleted, recurrer, body) VALUES (?, ?, ?, ?, ?, ?)", `INSERT OR REPLACE INTO items (id, kind, updated, deleted, recurrer, recur_next, body)
VALUES (?, ?, ?, ?, ?, ?)`,
i.ID, i.ID,
i.Kind, i.Kind,
i.Updated.UTC().Format(time.RFC3339), i.Updated.UTC().Format(time.RFC3339),
i.Deleted, i.Deleted,
recurStr, i.Recurrer.String(),
i.RecurNext.String(),
sql.NullString{String: i.Body, Valid: i.Body != ""}, // This allows empty string but not NULL sql.NullString{String: i.Body, Valid: i.Body != ""}, // This allows empty string but not NULL
) )
if err != nil { if err != nil {