some date and repo tests

This commit is contained in:
Erik Winter 2021-01-30 11:20:12 +01:00
parent 6701b64863
commit 9092336bf9
10 changed files with 446 additions and 130 deletions

View File

@ -1,9 +1,57 @@
package task
import "time"
import (
"time"
)
type Date time.Time
func (d *Date) Weekday() Weekday {
return d.Weekday()
type Date struct {
t time.Time
}
func NewDate(year, month, day int) *Date {
var m time.Month
switch month {
case 1:
m = time.January
case 2:
m = time.February
case 3:
m = time.March
case 4:
m = time.April
case 5:
m = time.May
case 6:
m = time.June
case 7:
m = time.July
case 8:
m = time.August
case 9:
m = time.September
case 10:
m = time.October
case 11:
m = time.November
case 12:
m = time.December
}
t := time.Date(year, m, day, 10, 0, 0, 0, time.UTC)
if year == 0 && month == 0 && day == 0 {
t = time.Time{}
}
return &Date{
t: t,
}
}
func (d *Date) String() string {
if d.t.IsZero() {
return "no date"
}
return d.t.Format("2006-01-02")
}

View File

@ -0,0 +1,36 @@
package task_test
import (
"testing"
"git.sr.ht/~ewintr/go-kit/test"
"git.sr.ht/~ewintr/gte/internal/task"
)
func TestDateString(t *testing.T) {
for _, tc := range []struct {
name string
date *task.Date
exp string
}{
{
name: "zero",
date: task.NewDate(0, 0, 0),
exp: "no date",
},
{
name: "normal",
date: task.NewDate(2021, 1, 30),
exp: "2021-01-30",
},
{
name: "normalize",
date: task.NewDate(2021, 1, 32),
exp: "2021-02-01",
},
} {
t.Run(tc.name, func(t *testing.T) {
test.Equals(t, tc.exp, tc.date.String())
})
}
}

View File

@ -9,6 +9,8 @@ import (
var (
ErrMStoreError = errors.New("mstore gave error response")
ErrInvalidTask = errors.New("invalid task")
ErrInvalidMessage = errors.New("task contains invalid message")
)
type TaskRepo struct {
@ -24,7 +26,7 @@ func NewRepository(ms mstore.MStorer) *TaskRepo {
func (tr *TaskRepo) FindAll(folder string) ([]*Task, error) {
msgs, err := tr.mstore.Messages(folder)
if err != nil {
return []*Task{}, err
return []*Task{}, fmt.Errorf("%w: %v", ErrMStoreError, err)
}
tasks := []*Task{}
@ -38,6 +40,9 @@ func (tr *TaskRepo) FindAll(folder string) ([]*Task, error) {
}
func (tr *TaskRepo) Update(t *Task) error {
if t == nil {
return ErrInvalidTask
}
if !t.Current {
return ErrOutdatedTask
}
@ -82,22 +87,20 @@ func (tr *TaskRepo) CleanUp() error {
// determine which ones need to be gone
var tobeRemoved []*Task
for _, tasks := range taskSet {
maxUid := uint32(0)
maxVersion := 0
for _, t := range tasks {
if t.Message.Uid > maxUid {
maxUid = t.Message.Uid
if t.Version > maxVersion {
maxVersion = t.Version
}
}
for _, t := range tasks {
if t.Message.Uid < maxUid {
if t.Version < maxVersion {
tobeRemoved = append(tobeRemoved, t)
}
}
}
//fmt.Printf("removing: %+v\n", tobeRemoved)
// remove them
for _, t := range tobeRemoved {
if err := tr.mstore.Remove(t.Message); err != nil {

206
internal/task/repo_test.go Normal file
View File

@ -0,0 +1,206 @@
package task_test
import (
"errors"
"fmt"
"testing"
"git.sr.ht/~ewintr/go-kit/test"
"git.sr.ht/~ewintr/gte/internal/task"
"git.sr.ht/~ewintr/gte/pkg/mstore"
)
func TestRepoFindAll(t *testing.T) {
folderA := "folderA"
folderB := "folderB"
type msgs struct {
Folder string
Subject string
}
for _, tc := range []struct {
name string
tasks []msgs
folder string
expTasks int
expErr error
}{
{
name: "empty",
folder: folderA,
},
{
name: "unknown folder",
folder: "unknown",
expErr: task.ErrMStoreError,
},
{
name: "not empty",
folder: folderA,
tasks: []msgs{
{Folder: folderA, Subject: "sub-1"},
{Folder: folderA, Subject: "sub-2"},
{Folder: folderB, Subject: "sub-3"},
{Folder: folderA, Subject: "sub-4"},
},
expTasks: 3,
},
} {
t.Run(tc.name, func(t *testing.T) {
store, err := mstore.NewMemory([]string{folderA, folderB})
test.OK(t, err)
for _, task := range tc.tasks {
test.OK(t, store.Add(task.Folder, task.Subject, "body"))
}
repo := task.NewRepository(store)
actTasks, err := repo.FindAll(tc.folder)
test.Equals(t, true, errors.Is(err, tc.expErr))
if err != nil {
return
}
test.Equals(t, tc.expTasks, len(actTasks))
})
}
}
func TestRepoUpdate(t *testing.T) {
id := "id"
oldFolder := "old folder"
folder := "folder"
action := "action"
oldMsg := &mstore.Message{
Uid: 1,
Folder: oldFolder,
Subject: "old subject",
Body: "old body",
}
for _, tc := range []struct {
name string
task *task.Task
expErr error
expMsgs []*mstore.Message
}{
{
name: "nil task",
expErr: task.ErrInvalidTask,
},
{
name: "task without message",
task: &task.Task{
Id: id,
Folder: folder,
Action: action,
Current: true,
Dirty: true,
},
expErr: task.ErrMStoreError,
},
{
name: "outdated task",
task: &task.Task{
Id: id,
Folder: folder,
Action: action,
Dirty: true,
},
expErr: task.ErrOutdatedTask,
},
/*
{
name: "unchanged task",
task: &task.Task{
Id: id,
Folder: folder,
Action: action,
Current: true,
},
expMsgs: []*mstore.Message{},
},
*/
{
name: "changed task",
task: &task.Task{
Id: id,
Folder: folder,
Action: action,
Current: true,
Dirty: true,
Message: oldMsg,
},
expMsgs: []*mstore.Message{
{Uid: 2, Folder: folder, Subject: action},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
mem, err := mstore.NewMemory([]string{folder, oldFolder})
test.OK(t, err)
test.OK(t, mem.Add(oldMsg.Folder, oldMsg.Subject, oldMsg.Body))
repo := task.NewRepository(mem)
actErr := repo.Update(tc.task)
test.Equals(t, true, errors.Is(actErr, tc.expErr))
if tc.expErr != nil {
return
}
actMsgs, err := mem.Messages(folder)
test.OK(t, err)
for i, _ := range actMsgs {
actMsgs[i].Body = ""
}
test.Equals(t, tc.expMsgs, actMsgs)
})
}
}
func TestRepoCleanUp(t *testing.T) {
folderNew := "New"
folderPlanned := "Planned"
folders := []string{"INBOX", folderNew, "Recurring",
folderPlanned, "Unplanned",
}
id := "id"
subject := "subject"
mem, err := mstore.NewMemory(folders)
test.OK(t, err)
for v := 1; v <= 3; v++ {
body := fmt.Sprintf(`
id: %s
version: %d
`, id, v)
folder := folderNew
if v%2 == 1 {
folder = folderPlanned
}
test.OK(t, mem.Add(folder, subject, body))
}
repo := task.NewRepository(mem)
test.OK(t, repo.CleanUp())
expNew := []*mstore.Message{}
actNew, err := mem.Messages(folderNew)
test.OK(t, err)
test.Equals(t, expNew, actNew)
expPlanned := []*mstore.Message{{
Uid: 3,
Folder: folderPlanned,
Subject: subject,
Body: `
id: id
version: 3
`,
}}
actPlanned, err := mem.Messages(folderPlanned)
test.OK(t, err)
fmt.Printf("act: %+v\n", actPlanned[0])
test.Equals(t, expPlanned, actPlanned)
}

View File

@ -3,6 +3,7 @@ package task
import (
"errors"
"fmt"
"strconv"
"strings"
"git.sr.ht/~ewintr/gte/pkg/mstore"
@ -16,6 +17,9 @@ var (
const (
FOLDER_INBOX = "INBOX"
FOLDER_NEW = "New"
FOLDER_RECURRING = "Recurring"
FOLDER_PLANNED = "Planned"
FOLDER_UNPLANNED = "Unplanned"
QUOTE_PREFIX = ">"
PREVIOUS_SEPARATOR = "Previous version:"
@ -24,13 +28,20 @@ const (
SUBJECT_SEPARATOR = " - "
FIELD_ID = "id"
FIELD_VERSION = "version"
FIELD_ACTION = "action"
FIELD_PROJECT = "project"
FIELD_DUE = "date"
FIELD_DUE = "due"
)
var (
knownFolders = []string{FOLDER_INBOX, FOLDER_NEW}
knownFolders = []string{
FOLDER_INBOX,
FOLDER_NEW,
FOLDER_RECURRING,
FOLDER_PLANNED,
FOLDER_UNPLANNED,
}
)
// Task reperesents a task based on the data stored in a message
@ -38,6 +49,8 @@ type Task struct {
// Id is a UUID that gets carried over when a new message is constructed
Id string
// Version is a method to determine the latest version for cleanup
Version int
// Folder is the same name as the mstore folder
Folder string
@ -74,6 +87,13 @@ func New(msg *mstore.Message) *Task {
dirty = true
}
// Version, cannot manually be incremented from body
versionStr, _ := FieldFromBody(FIELD_VERSION, msg.Body)
version, _ := strconv.Atoi(versionStr)
if version == 0 {
dirty = true
}
// Action
action, d := FieldFromBody(FIELD_ACTION, msg.Body)
if action == "" {
@ -99,8 +119,13 @@ func New(msg *mstore.Message) *Task {
dirty = true
}
if dirty {
version++
}
return &Task{
Id: id,
Version: version,
Folder: folder,
Action: action,
Project: project,
@ -129,9 +154,10 @@ func (t *Task) FormatSubject() string {
func (t *Task) FormatBody() string {
body := fmt.Sprintf("\n")
order := []string{FIELD_ID, FIELD_PROJECT, FIELD_ACTION}
order := []string{FIELD_ID, FIELD_VERSION, FIELD_PROJECT, FIELD_ACTION}
fields := map[string]string{
FIELD_ID: t.Id,
FIELD_VERSION: strconv.Itoa(t.Version),
FIELD_PROJECT: t.Project,
FIELD_ACTION: t.Action,
}
@ -195,3 +221,9 @@ func FieldFromSubject(field, subject string) string {
return ""
}
func increment(s string) string {
i, _ := strconv.Atoi(s)
i++
return strconv.Itoa(i)
}

View File

@ -11,6 +11,7 @@ import (
func TestNewFromMessage(t *testing.T) {
id := "an id"
version := 2
action := "some action"
project := "project"
folder := task.FOLDER_NEW
@ -19,6 +20,7 @@ func TestNewFromMessage(t *testing.T) {
name string
message *mstore.Message
hasId bool
hasVersion bool
exp *task.Task
}{
{
@ -34,13 +36,16 @@ func TestNewFromMessage(t *testing.T) {
Folder: folder,
Body: fmt.Sprintf(`
id: %s
version: %d
action: %s
project: %s
`, id, action, project),
`, id, version, action, project),
},
hasId: true,
hasVersion: true,
exp: &task.Task{
Id: id,
Version: version,
Folder: folder,
Action: action,
Project: project,
@ -70,12 +75,15 @@ action: %s
Subject: "some other action",
Body: fmt.Sprintf(`
id: %s
version: %d
action: %s
`, id, action),
`, id, version, action),
},
hasId: true,
hasVersion: true,
exp: &task.Task{
Id: id,
Version: version,
Folder: folder,
Action: action,
},
@ -122,6 +130,9 @@ Forwarded message:
test.Equals(t, false, "" == act.Id)
tc.exp.Id = act.Id
}
if !tc.hasVersion {
tc.exp.Version = 1
}
tc.exp.Message = tc.message
tc.exp.Current = true
test.Equals(t, tc.exp, act)
@ -166,6 +177,7 @@ func TestFormatSubject(t *testing.T) {
func TestFormatBody(t *testing.T) {
id := "an id"
version := 6
action := "an action"
project := "project"
@ -179,6 +191,7 @@ func TestFormatBody(t *testing.T) {
task: &task.Task{},
exp: `
id:
version: 0
project:
action:
`,
@ -187,6 +200,7 @@ action:
name: "filled",
task: &task.Task{
Id: id,
Version: version,
Action: action,
Project: project,
Message: &mstore.Message{
@ -195,6 +209,7 @@ action:
},
exp: `
id: an id
version: 6
project: project
action: an action

View File

@ -187,7 +187,7 @@ Subject: %s
}
func (es *Imap) Remove(msg *Message) error {
if !msg.Valid() {
if msg == nil || !msg.Valid() {
return ErrInvalidMessage
}

View File

@ -11,8 +11,8 @@ var (
)
type Memory struct {
Selected string
nextUid uint32
folders []string
messages map[string][]*Message
}
@ -20,6 +20,7 @@ func NewMemory(folders []string) (*Memory, error) {
if len(folders) == 0 {
return &Memory{}, ErrInvalidFolderSet
}
sort.Strings(folders)
msg := make(map[string][]*Message)
for _, f := range folders {
@ -31,29 +32,13 @@ func NewMemory(folders []string) (*Memory, error) {
return &Memory{
messages: msg,
folders: folders,
nextUid: uint32(1),
}, nil
}
func (mem *Memory) Folders() ([]string, error) {
folders := []string{}
for f := range mem.messages {
folders = append(folders, f)
}
sort.Strings(folders)
return folders, nil
}
func (mem *Memory) Select(folder string) error {
if _, ok := mem.messages[folder]; !ok {
return ErrFolderDoesNotExist
}
mem.Selected = folder
return nil
return mem.folders, nil
}
func (mem *Memory) Add(folder, subject, body string) error {
@ -66,6 +51,7 @@ func (mem *Memory) Add(folder, subject, body string) error {
mem.messages[folder] = append(mem.messages[folder], &Message{
Uid: mem.nextUid,
Folder: folder,
Subject: subject,
Body: body,
})
@ -74,28 +60,29 @@ func (mem *Memory) Add(folder, subject, body string) error {
return nil
}
func (mem *Memory) Messages() ([]*Message, error) {
if mem.Selected == "" {
return []*Message{}, ErrNoFolderSelected
func (mem *Memory) Messages(folder string) ([]*Message, error) {
if _, ok := mem.messages[folder]; !ok {
return []*Message{}, ErrFolderDoesNotExist
}
return mem.messages[mem.Selected], nil
return mem.messages[folder], nil
}
func (mem *Memory) Remove(uid uint32) error {
if uid == uint32(0) {
return ErrInvalidUid
func (mem *Memory) Remove(msg *Message) error {
if msg == nil || !msg.Valid() {
return ErrInvalidMessage
}
if mem.Selected == "" {
return ErrNoFolderSelected
if _, ok := mem.messages[msg.Folder]; !ok {
return ErrFolderDoesNotExist
}
for i, m := range mem.messages[mem.Selected] {
if m.Uid == uid {
mem.messages[mem.Selected] = append(mem.messages[mem.Selected][:i], mem.messages[mem.Selected][i+1:]...)
for i, m := range mem.messages[msg.Folder] {
if m.Uid == msg.Uid {
mem.messages[msg.Folder] = append(mem.messages[msg.Folder][:i], mem.messages[msg.Folder][i+1:]...)
return nil
}
}
return ErrMessageDoesNotExist
}

View File

@ -67,58 +67,41 @@ func TestMemoryFolders(t *testing.T) {
}
}
func TestMemorySelect(t *testing.T) {
mem, err := mstore.NewMemory([]string{"one", "two", "three"})
test.OK(t, err)
func TestMemoryAdd(t *testing.T) {
folder := "folder"
subject := "subject"
for _, tc := range []struct {
name string
folder string
exp error
}{
{
name: "empty",
exp: mstore.ErrFolderDoesNotExist,
},
{
name: "not present",
folder: "four",
exp: mstore.ErrFolderDoesNotExist,
},
{
name: "present",
folder: "two",
},
} {
t.Run(tc.name, func(t *testing.T) {
test.Equals(t, tc.exp, mem.Select(tc.folder))
if tc.exp == nil {
test.Equals(t, tc.folder, mem.Selected)
}
})
}
}
func TestMemoryAdd(t *testing.T) {
folder := "folder"
for _, tc := range []struct {
name string
subject string
exp error
expMsgs []*mstore.Message
expErr error
}{
{
name: "empty",
exp: mstore.ErrInvalidMessage,
folder: folder,
expErr: mstore.ErrInvalidMessage,
},
{
name: "invalid folder",
folder: "not there",
subject: subject,
expErr: mstore.ErrFolderDoesNotExist,
},
{
name: "valid",
subject: "subject",
folder: folder,
subject: subject,
expMsgs: []*mstore.Message{
{Uid: 1, Folder: folder, Subject: subject},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
mem, err := mstore.NewMemory([]string{folder})
test.OK(t, err)
test.Equals(t, tc.exp, mem.Add(folder, tc.subject, ""))
test.Equals(t, tc.expErr, mem.Add(tc.folder, tc.subject, ""))
})
}
}
@ -127,19 +110,17 @@ func TestMemoryMessages(t *testing.T) {
folderA := "folderA"
folderB := "folderB"
t.Run("no folder selected", func(t *testing.T) {
mem, err := mstore.NewMemory([]string{folderA})
test.OK(t, err)
_, err = mem.Messages()
test.Equals(t, mstore.ErrNoFolderSelected, err)
})
for _, tc := range []struct {
name string
folder string
amount int
expErr error
}{
{
name: "unknown folder",
folder: "not there",
expErr: mstore.ErrFolderDoesNotExist,
},
{
name: "empty folder",
folder: folderB,
@ -164,6 +145,7 @@ func TestMemoryMessages(t *testing.T) {
for i := 1; i <= tc.amount; i++ {
m := &mstore.Message{
Uid: uint32(i),
Folder: folderA,
Subject: fmt.Sprintf("subject-%d", i),
Body: fmt.Sprintf("body-%d", i),
}
@ -173,8 +155,7 @@ func TestMemoryMessages(t *testing.T) {
test.OK(t, mem.Add(folderA, m.Subject, m.Body))
}
test.OK(t, mem.Select(tc.folder))
actMessages, err := mem.Messages()
actMessages, err := mem.Messages(tc.folder)
test.Equals(t, tc.expErr, err)
test.Equals(t, expMessages, actMessages)
})
@ -183,12 +164,7 @@ func TestMemoryMessages(t *testing.T) {
func TestMemoryRemove(t *testing.T) {
folderA, folderB := "folderA", "folderB"
t.Run("no folder selected", func(t *testing.T) {
mem, err := mstore.NewMemory([]string{folderA})
test.OK(t, err)
test.Equals(t, mstore.ErrNoFolderSelected, mem.Remove(uint32(3)))
})
subject := "subject"
mem, err := mstore.NewMemory([]string{folderA, folderB})
test.OK(t, err)
@ -197,38 +173,49 @@ func TestMemoryRemove(t *testing.T) {
}
for _, tc := range []struct {
name string
folder string
uid uint32
msg *mstore.Message
expUids []uint32
expErr error
}{
{
name: "invalid uid",
folder: folderA,
uid: uint32(0),
expErr: mstore.ErrInvalidUid,
},
{
name: "empty",
folder: folderB,
uid: uint32(1),
msg: &mstore.Message{
Uid: 1,
Folder: folderB,
Subject: subject,
},
expErr: mstore.ErrMessageDoesNotExist,
},
{
name: "nil message",
expErr: mstore.ErrInvalidMessage,
},
{
name: "unknown folder",
msg: &mstore.Message{
Uid: 1,
Folder: "unknown",
Subject: subject,
},
expErr: mstore.ErrFolderDoesNotExist,
},
{
name: "valid",
folder: folderA,
uid: uint32(2),
msg: &mstore.Message{
Uid: 2,
Folder: folderA,
Subject: subject,
},
expUids: []uint32{1, 3},
},
} {
t.Run(tc.name, func(t *testing.T) {
test.OK(t, mem.Select(tc.folder))
test.Equals(t, tc.expErr, mem.Remove(tc.uid))
test.Equals(t, tc.expErr, mem.Remove(tc.msg))
if tc.expErr != nil {
return
}
actUids := []uint32{}
actMsgs, err := mem.Messages()
actMsgs, err := mem.Messages(tc.msg.Folder)
test.OK(t, err)
for _, m := range actMsgs {
actUids = append(actUids, m.Uid)

View File

@ -1,6 +1,8 @@
package mstore
import "errors"
import (
"errors"
)
var (
ErrFolderDoesNotExist = errors.New("folder does not exist")