diff --git a/plan/command/add.go b/plan/command/add.go index 3f483a9..46491b7 100644 --- a/plan/command/add.go +++ b/plan/command/add.go @@ -1,27 +1,35 @@ package command import ( + "errors" "fmt" "time" + "github.com/google/uuid" "github.com/urfave/cli/v2" "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/storage" ) +var ( + ErrInvalidArg = errors.New("invalid argument") +) + var AddCmd = &cli.Command{ Name: "add", Usage: "Add a new event", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "name", - Aliases: []string{"n"}, - Usage: "The event that will happen", + Name: "name", + Aliases: []string{"n"}, + Usage: "The event that will happen", + Required: true, }, &cli.StringFlag{ - Name: "on", - Aliases: []string{"o"}, - Usage: "The date, in YYYY-MM-DD format", + Name: "on", + Aliases: []string{"o"}, + Usage: "The date, in YYYY-MM-DD format", + Required: true, }, &cli.StringFlag{ Name: "at", @@ -37,29 +45,55 @@ var AddCmd = &cli.Command{ } func NewAddCmd(repo storage.EventRepo) *cli.Command { - AddCmd.Action = NewAddAction(repo) + AddCmd.Action = func(cCtx *cli.Context) error { + return Add(cCtx.String("name"), cCtx.String("on"), cCtx.String("at"), cCtx.String("for"), repo) + } return AddCmd } -func NewAddAction(repo storage.EventRepo) func(*cli.Context) error { - return func(cCtx *cli.Context) error { - desc := cCtx.String("name") - date, err := time.Parse("2006-01-02", cCtx.String("date")) - if err != nil { - return fmt.Errorf("could not parse date: %v", err) - } - - one := item.Event{ - ID: "a", - EventBody: item.EventBody{ - Title: desc, - Start: date, - }, - } - if err := repo.Store(one); err != nil { - return fmt.Errorf("could not store event: %v", err) - } - - return nil +func Add(nameStr, onStr, atStr, frStr string, repo storage.EventRepo) error { + if nameStr == "" { + return fmt.Errorf("%w: name is required", ErrInvalidArg) } + if onStr == "" { + return fmt.Errorf("%w: date is required", ErrInvalidArg) + } + if atStr == "" && frStr != "" { + return fmt.Errorf("%w: can not have duration without start time", ErrInvalidArg) + } + if atStr == "" && frStr == "" { + frStr = "24h" + } + + startFormat := "2006-01-02" + startStr := onStr + if atStr != "" { + startFormat = fmt.Sprintf("%s 15:04", startFormat) + startStr = fmt.Sprintf("%s %s", startStr, atStr) + } + start, err := time.Parse(startFormat, startStr) + if err != nil { + return fmt.Errorf("%w: could not parse start time and date: %v", ErrInvalidArg, err) + } + + e := item.Event{ + ID: uuid.New().String(), + EventBody: item.EventBody{ + Title: nameStr, + Start: start, + }, + } + + if frStr != "" { + fr, err := time.ParseDuration(frStr) + if err != nil { + return fmt.Errorf("%w: could not parse time: %s", ErrInvalidArg, err) + } + e.Duration = fr + } + if err := repo.Store(e); err != nil { + return fmt.Errorf("could not store event: %v", err) + } + + return nil } diff --git a/plan/command/add_test.go b/plan/command/add_test.go new file mode 100644 index 0000000..f1b16a5 --- /dev/null +++ b/plan/command/add_test.go @@ -0,0 +1,131 @@ +package command_test + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "go-mod.ewintr.nl/planner/item" + "go-mod.ewintr.nl/planner/plan/command" + "go-mod.ewintr.nl/planner/plan/storage" +) + +func TestAdd(t *testing.T) { + t.Parallel() + + oneHour, err := time.ParseDuration("1h") + if err != nil { + t.Errorf("exp nil, got %v", err) + } + oneDay, err := time.ParseDuration("24h") + if err != nil { + t.Errorf("exp nil, got %v", err) + } + + for _, tc := range []struct { + name string + args map[string]string + expEvent item.Event + expErr bool + }{ + { + name: "no name", + args: map[string]string{ + "on": "2024-10-01", + "at": "9:00", + "for": "1h", + }, + expErr: true, + }, + { + name: "no date", + args: map[string]string{ + "name": "event", + "at": "9:00", + "for": "1h", + }, + expErr: true, + }, + { + name: "duration, but no time", + args: map[string]string{ + "name": "event", + "on": "2024-10-01", + "for": "1h", + }, + expErr: true, + }, + { + name: "time, but no duration", + args: map[string]string{ + "name": "event", + "on": "2024-10-01", + "at": "9:00", + }, + expEvent: item.Event{ + ID: "a", + EventBody: item.EventBody{ + Title: "event", + Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC), + }, + }, + }, + { + name: "no time, no duration", + args: map[string]string{ + "name": "event", + "on": "2024-10-01", + }, + expEvent: item.Event{ + ID: "a", + EventBody: item.EventBody{ + Title: "event", + Start: time.Date(2024, 10, 1, 0, 0, 0, 0, time.UTC), + Duration: oneDay, + }, + }, + }, + { + name: "full", + args: map[string]string{ + "name": "event", + "on": "2024-10-01", + "at": "9:00", + "for": "1h", + }, + expEvent: item.Event{ + ID: "a", + EventBody: item.EventBody{ + Title: "event", + Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC), + Duration: oneHour, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + mem := storage.NewMemory() + actErr := command.Add(tc.args["name"], tc.args["on"], tc.args["at"], tc.args["for"], mem) != nil + if tc.expErr != actErr { + t.Errorf("exp %v, got %v", tc.expErr, actErr) + } + if tc.expErr { + return + } + actEvents, err := mem.FindAll() + if err != nil { + t.Errorf("exp nil, got %v", err) + } + if len(actEvents) != 1 { + t.Errorf("exp 1, got %d", len(actEvents)) + } + if actEvents[0].ID == "" { + t.Errorf("exp string not te be empty") + } + tc.expEvent.ID = actEvents[0].ID + if diff := cmp.Diff(tc.expEvent, actEvents[0]); diff != "" { + t.Errorf("(exp +, got -)\n%s", diff) + } + }) + } +}