snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
build / build (push) Failing after 3s

This commit is contained in:
upstream-mirror
2026-09-16 05:24:45 +05:00
commit cda7c1a474
272 changed files with 31973 additions and 0 deletions

89
timespan/convert.go Normal file
View File

@@ -0,0 +1,89 @@
package timespan
import (
"fmt"
"time"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
func timespanToInternal(userID int, start model.Time, end *model.Time, tags []*gqlmodel.InputTimeSpanTag, note string) (model.TimeSpan, error) {
_, offset := start.Time().Zone()
span := model.TimeSpan{
StartUserTime: start.OmitTimeZone(),
StartUTC: start.UTC(),
UserID: userID,
Tags: tagsToInternal(tags),
OffsetUTC: offset,
Note: note,
}
if end != nil {
if start.Time().After(end.Time()) {
return span, fmt.Errorf("start must be before end")
}
endUser := end.OmitTimeZone()
span.EndUserTime = &endUser
endUTC := end.UTC()
span.EndUTC = &endUTC
}
return span, nil
}
func timeSpanToExternal(span model.TimeSpan) *gqlmodel.TimeSpan {
location := time.FixedZone("unknown", span.OffsetUTC)
result := gqlmodel.TimeSpan{
Start: model.Time(span.StartUTC.In(location)),
End: nil,
ID: span.ID,
Tags: tagsToExternal(span.Tags),
Note: span.Note,
}
if span.EndUTC != nil && !span.EndUTC.IsZero() {
end := *span.EndUTC
endModel := model.Time(end.In(location))
result.End = &endModel
}
return &result
}
func tagsToExternal(tags []model.TimeSpanTag) []*gqlmodel.TimeSpanTag {
result := []*gqlmodel.TimeSpanTag{}
for _, tag := range tags {
result = append(result, &gqlmodel.TimeSpanTag{
Key: tag.Key,
Value: tag.StringValue,
})
}
return result
}
func tagsToInternal(gqls []*gqlmodel.InputTimeSpanTag) []model.TimeSpanTag {
result := make([]model.TimeSpanTag, 0)
for _, tag := range gqls {
result = append(result, tagToInternal(*tag))
}
return result
}
func tagToInternal(gqls gqlmodel.InputTimeSpanTag) model.TimeSpanTag {
return model.TimeSpanTag{
Key: gqls.Key,
StringValue: gqls.Value,
}
}
func tagsToInputTag(tags []model.TimeSpanTag) []*gqlmodel.InputTimeSpanTag {
result := make([]*gqlmodel.InputTimeSpanTag, 0)
for _, tag := range tags {
result = append(result, &gqlmodel.InputTimeSpanTag{
Key: tag.Key,
Value: tag.StringValue,
})
}
return result
}

21
timespan/copy.go Normal file
View File

@@ -0,0 +1,21 @@
package timespan
import (
"context"
"fmt"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// CopyTimeSpan copies a time span.
func (r *ResolverForTimeSpan) CopyTimeSpan(ctx context.Context, id int, start model.Time, end *model.Time) (*gqlmodel.TimeSpan, error) {
old := &model.TimeSpan{ID: id}
if r.DB.Preload("Tags").Where("user_id = ?", auth.GetUser(ctx).ID).Find(old).RecordNotFound() {
return nil, fmt.Errorf("time span with id %d does not exist", id)
}
return r.CreateTimeSpan(ctx, start, end, tagsToInputTag(old.Tags), old.Note)
}

103
timespan/copy_test.go Normal file
View File

@@ -0,0 +1,103 @@
package timespan
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestCopy_noPermission(t *testing.T) {
db := test.InMemoryDB(t)
db.User(3)
db.User(5)
db.User(2)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 3,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T16:30:00Z"),
Tags: []model.TimeSpanTag{},
})
defer db.Close()
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CopyTimeSpan(fake.User(2), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"))
require.Nil(t, timeSpan)
require.EqualError(t, err, "time span with id 3 does not exist")
assertTimeSpanCount(t, db, 1)
}
func TestCopy(t *testing.T) {
db := test.InMemoryDB(t)
db.User(3)
db.Create(&model.TagDefinition{Key: "test", UserID: 3})
db.Create(&model.TimeSpan{
ID: 3,
UserID: 3,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{
{Key: "test", TimeSpanID: 3},
},
Note: "A special note",
})
defer db.Close()
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CopyTimeSpan(fake.User(3), 3, test.ModelTime("2019-06-15T10:30:00+02:00"), nil)
require.NoError(t, err)
expected := &gqlmodel.TimeSpan{
ID: 4,
Start: test.ModelTime("2019-06-15T10:30:00+02:00"),
End: nil,
Tags: []*gqlmodel.TimeSpanTag{
{Key: "test"},
},
Note: "A special note",
}
require.Equal(t, expected, timeSpan)
assertTimeSpanCount(t, db, 2)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 4,
UserID: 3,
StartUserTime: test.Time("2019-06-15T10:30:00Z"),
StartUTC: test.Time("2019-06-15T08:30:00Z"),
EndUserTime: nil,
EndUTC: nil,
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{
{Key: "test", TimeSpanID: 4},
},
Note: "A special note",
})
}
func TestCopy_notFound(t *testing.T) {
db := test.InMemoryDB(t)
db.User(3)
db.User(2)
db.User(5)
defer db.Close()
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CopyTimeSpan(fake.User(2), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"))
require.Nil(t, timeSpan)
require.EqualError(t, err, "time span with id 3 does not exist")
assertTimeSpanCount(t, db, 0)
}

26
timespan/create.go Normal file
View File

@@ -0,0 +1,26 @@
package timespan
import (
"context"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// CreateTimeSpan creates a time span
func (r *ResolverForTimeSpan) CreateTimeSpan(ctx context.Context, start model.Time, end *model.Time, tags []*gqlmodel.InputTimeSpanTag, note string) (*gqlmodel.TimeSpan, error) {
timeSpan, err := timespanToInternal(auth.GetUser(ctx).ID, start, end, tags, note)
if err != nil {
return nil, err
}
if err := tagsExist(r.DB, auth.GetUser(ctx).ID, timeSpan.Tags); err != nil {
return nil, err
}
r.DB.Create(&timeSpan)
external := timeSpanToExternal(timeSpan)
return external, nil
}

138
timespan/create_test.go Normal file
View File

@@ -0,0 +1,138 @@
package timespan
import (
"testing"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func Test_Create_withoutEnd(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CreateTimeSpan(fake.User(5), test.ModelTime("2019-06-10T18:30:00+02:00"), nil, nil, "")
require.Nil(t, err)
expected := &gqlmodel.TimeSpan{
ID: 1,
Start: test.ModelTime("2019-06-10T18:30:00+02:00"),
Tags: []*gqlmodel.TimeSpanTag{},
Note: "",
}
require.Equal(t, expected, timeSpan)
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 1,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
Note: "",
})
}
func Test_Create(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
resolver := ResolverForTimeSpan{DB: db.DB}
note := "A note describing the timespan"
timeSpan, err := resolver.CreateTimeSpan(fake.User(5), test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T19:30:00+02:00"), nil, note)
log.Debug().Msg("oops")
require.Nil(t, err)
expected := &gqlmodel.TimeSpan{
ID: 1,
Start: test.ModelTime("2019-06-10T18:30:00+02:00"),
End: test.ModelTimeP("2019-06-10T19:30:00+02:00"),
Tags: []*gqlmodel.TimeSpanTag{},
Note: note,
}
require.Equal(t, expected, timeSpan)
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 1,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T19:30:00Z"),
EndUTC: test.TimeP("2019-06-10T17:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
Note: note,
})
}
func Test_Create_fail_endBeforeStart(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CreateTimeSpan(fake.User(5), test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T17:30:00+02:00"), nil, "")
require.Nil(t, timeSpan)
require.EqualError(t, err, "start must be before end")
assertTimeSpanCount(t, db, 0)
}
func Test_Create_fail_notExistingTag(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CreateTimeSpan(fake.User(5), test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}}, "")
require.Nil(t, timeSpan)
require.EqualError(t, err, "tag 'test' does not exist")
assertTimeSpanCount(t, db, 0)
}
func Test_Create_withTag(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CreateTimeSpan(fake.User(5), test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T19:30:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}}, "")
require.NotNil(t, timeSpan)
require.NoError(t, err)
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 1,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T19:30:00Z"),
EndUTC: test.TimeP("2019-06-10T17:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{
{Key: "test", TimeSpanID: 1},
},
Note: "",
})
}
func Test_Create_fail_tagAddedMultipleTimes(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.CreateTimeSpan(fake.User(5), test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}, {Key: "test"}}, "")
require.Nil(t, timeSpan)
require.EqualError(t, err, "tag 'test' is present multiple times")
assertTimeSpanCount(t, db, 0)
}

76
timespan/get.go Normal file
View File

@@ -0,0 +1,76 @@
package timespan
import (
"context"
"errors"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// TimeSpans returns all time spans for a user
func (r *ResolverForTimeSpan) TimeSpans(ctx context.Context, fromInclusive *model.Time, toInclusive *model.Time, cursor *gqlmodel.InputCursor) (*gqlmodel.PagedTimeSpans, error) {
user := auth.GetUser(ctx)
cursor = normalize(cursor)
if cursor.StartID == nil {
var s model.TimeSpan
if err := r.DB.Model(new(model.TimeSpan)).Select("max(id) as id").Find(&s).Error; err != nil {
return nil, err
}
cursor.StartID = &s.ID
}
call := r.DB.Preload("Tags").Where("user_id = ?", user.ID).Not("end_user_time is NULL").Order("start_user_time DESC").Limit(*cursor.PageSize)
if cursor.Offset != nil && cursor.StartID != nil {
call = call.Where("id <= ?", *cursor.StartID).Offset(*cursor.Offset)
}
if fromInclusive != nil {
if toInclusive != nil {
if fromInclusive.Time().After(toInclusive.Time()) {
return nil, errors.New("fromInclusive must be before toInclusive")
}
call = call.Where("start_user_time <= ? AND end_user_time >= ?", toInclusive.OmitTimeZone(), fromInclusive.OmitTimeZone())
} else {
call = call.Where("start_user_time >= ? OR end_user_time >= ?", fromInclusive.OmitTimeZone(), fromInclusive.OmitTimeZone())
}
} else if toInclusive != nil {
call = call.Where("end_user_time <= ? OR start_user_time <= ?", toInclusive.OmitTimeZone(), toInclusive.OmitTimeZone())
}
var timeSpans []model.TimeSpan
call.Find(&timeSpans)
var result []*gqlmodel.TimeSpan
for _, span := range timeSpans {
result = append(result, timeSpanToExternal(span))
}
return &gqlmodel.PagedTimeSpans{
TimeSpans: result,
Cursor: &gqlmodel.Cursor{
HasMore: len(timeSpans) != 0 && *cursor.Offset%*cursor.PageSize == 0,
Offset: *cursor.Offset + len(timeSpans),
StartID: *cursor.StartID,
PageSize: *cursor.PageSize},
}, nil
}
func normalize(cursor *gqlmodel.InputCursor) *gqlmodel.InputCursor {
if cursor == nil {
cursor = &gqlmodel.InputCursor{}
}
maxPageSize := 100
if cursor.PageSize == nil || maxPageSize < *cursor.PageSize {
cursor.PageSize = &maxPageSize
}
if cursor.Offset == nil {
zero := 0
cursor.Offset = &zero
}
return cursor
}

275
timespan/get_test.go Normal file
View File

@@ -0,0 +1,275 @@
package timespan
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
var (
timeSpan1 = &model.TimeSpan{
ID: 1,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:40:00Z"),
StartUTC: test.Time("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T18:40:00Z"),
OffsetUTC: 0,
Tags: []model.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
modelTimeSpan1 = gqlmodel.TimeSpan{
ID: 1,
Start: test.ModelTime("2019-06-10T18:30:00Z"),
End: test.ModelTimeP("2019-06-10T18:40:00Z"),
Tags: []*gqlmodel.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
timeSpan2 = &model.TimeSpan{
ID: 2,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:40:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:50:00Z"),
StartUTC: test.Time("2019-06-10T18:40:00Z"),
EndUTC: test.TimeP("2019-06-10T18:50:00Z"),
OffsetUTC: 0,
Tags: []model.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
modelTimeSpan2 = gqlmodel.TimeSpan{
ID: 2,
Start: test.ModelTime("2019-06-10T18:40:00Z"),
End: test.ModelTimeP("2019-06-10T18:50:00Z"),
Tags: []*gqlmodel.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
timeSpan3 = &model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:50:00Z"),
EndUserTime: test.TimeP("2019-06-10T19:00:00Z"),
StartUTC: test.Time("2019-06-10T18:50:00Z"),
EndUTC: test.TimeP("2019-06-10T19:00:00Z"),
OffsetUTC: 0,
Tags: []model.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
modelTimeSpan3 = gqlmodel.TimeSpan{
ID: 3,
Start: test.ModelTime("2019-06-10T18:50:00Z"),
End: test.ModelTimeP("2019-06-10T19:00:00Z"),
Tags: []*gqlmodel.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
timeSpan4 = &model.TimeSpan{
ID: 4,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:00:00Z"),
EndUserTime: test.TimeP("2019-06-10T19:00:00Z"),
StartUTC: test.Time("2019-06-10T18:00:00Z"),
EndUTC: test.TimeP("2019-06-10T19:00:00Z"),
OffsetUTC: 0,
Tags: []model.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
modelTimeSpan4 = gqlmodel.TimeSpan{
ID: 4,
Start: test.ModelTime("2019-06-10T18:00:00Z"),
End: test.ModelTimeP("2019-06-10T19:00:00Z"),
Tags: []*gqlmodel.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
runningTimeSpan = &model.TimeSpan{
ID: 5,
UserID: 5,
StartUserTime: test.Time("2019-06-11T18:00:00Z"),
StartUTC: test.Time("2019-06-11T18:00:00Z"),
EndUserTime: nil,
EndUTC: nil,
OffsetUTC: 0,
Tags: []model.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
modelRunningTimeSpan = gqlmodel.TimeSpan{
ID: 5,
Start: test.ModelTime("2019-06-11T18:00:00Z"),
End: nil,
Tags: []*gqlmodel.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
timeSpanOtherUser = &model.TimeSpan{
ID: 6,
UserID: 2,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:40:00Z"),
StartUTC: test.Time("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T18:40:00Z"),
OffsetUTC: 0,
Tags: []model.TimeSpanTag{
{Key: "test"},
{Key: "test2"},
},
}
)
type data struct {
DB []*model.TimeSpan
From *model.Time
To *model.Time
Expected []*gqlmodel.TimeSpan
Cursor *gqlmodel.InputCursor
}
func (d data) String() string {
str := "DB=["
for _, v := range d.DB {
str += fmt.Sprint(v.ID) + ","
}
str = str + "]"
if d.From != nil {
str = str + "|From=" + d.From.Time().Format(time.RFC3339)
}
if d.To != nil {
str = str + "|To=" + d.To.Time().Format(time.RFC3339)
}
str += "|Expected=["
for _, v := range d.Expected {
str += fmt.Sprint(v.ID) + ","
}
str = str + "]"
return str
}
func TestGet(t *testing.T) {
d := []data{
{
DB: []*model.TimeSpan{timeSpan1, timeSpanOtherUser},
From: nil,
To: nil,
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan1},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4, runningTimeSpan},
From: nil,
To: nil,
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan3, &modelTimeSpan2, &modelTimeSpan1, &modelTimeSpan4},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: nil,
To: nil,
Cursor: &gqlmodel.InputCursor{PageSize: p(1)},
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan3},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: nil,
To: nil,
Cursor: &gqlmodel.InputCursor{PageSize: p(1), Offset: p(1), StartID: p(4)},
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan2},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: nil,
To: nil,
Cursor: &gqlmodel.InputCursor{PageSize: p(1), Offset: p(0), StartID: p(1)},
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan1},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: test.ModelTimeP("2019-06-10T18:40:01+02:00"),
To: nil,
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan3, &modelTimeSpan2, &modelTimeSpan4},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: modelTimeSpan1.End,
To: nil,
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan3, &modelTimeSpan2, &modelTimeSpan1, &modelTimeSpan4},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: nil,
To: test.ModelTimeP("2019-06-10T18:45:00+02:00"),
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan2, &modelTimeSpan1, &modelTimeSpan4},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: test.ModelTimeP("2019-06-10T18:35:00+02:00"),
To: test.ModelTimeP("2019-06-10T18:45:00+02:00"),
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan2, &modelTimeSpan1, &modelTimeSpan4},
},
{
DB: []*model.TimeSpan{timeSpan1, timeSpan2, timeSpan3, timeSpan4},
From: test.ModelTimeP("2019-06-10T18:55:00+02:00"),
To: test.ModelTimeP("2019-06-10T18:56:00+02:00"),
Expected: []*gqlmodel.TimeSpan{&modelTimeSpan3, &modelTimeSpan4},
},
{
DB: []*model.TimeSpan{runningTimeSpan},
From: test.ModelTimeP("2019-06-11T18:30:00Z"),
To: test.ModelTimeP("2019-06-11T19:00:00Z"),
Expected: nil,
},
}
for i, testData := range d {
t.Run(fmt.Sprintf("%d>%s", i, testData), func(t *testing.T) {
db := test.InMemoryDB(t)
db.User(5)
db.User(2)
defer db.Close()
for _, entry := range testData.DB {
db.Create(entry)
}
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpans, err := resolver.TimeSpans(fake.User(5), testData.From, testData.To, testData.Cursor)
require.NoError(t, err)
require.Equal(t, testData.Expected, timeSpans.TimeSpans)
})
}
}
func TestGet_fail_toBeforeStart(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpans, err := resolver.TimeSpans(fake.User(5), test.ModelTimeP("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T17:30:00+02:00"), nil)
require.Nil(t, timeSpans)
require.EqualError(t, err, "fromInclusive must be before toInclusive")
}
func p(i int) *int {
return &i
}

23
timespan/remove.go Normal file
View File

@@ -0,0 +1,23 @@
package timespan
import (
"context"
"fmt"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// RemoveTimeSpan removes a timespan.
func (r *ResolverForTimeSpan) RemoveTimeSpan(ctx context.Context, id int) (*gqlmodel.TimeSpan, error) {
timeSpan := model.TimeSpan{ID: id}
if r.DB.Preload("Tags").Where("user_id = ?", auth.GetUser(ctx).ID).Find(&timeSpan).RecordNotFound() {
return nil, fmt.Errorf("timespan with id %d does not exist", timeSpan.ID)
}
remove := r.DB.Where(&model.TimeSpan{ID: id}).Delete(new(model.TimeSpan))
external := timeSpanToExternal(timeSpan)
return external, remove.Error
}

78
timespan/remove_test.go Normal file
View File

@@ -0,0 +1,78 @@
package timespan
import (
"testing"
"github.com/traggo/server/generated/gqlmodel"
"github.com/stretchr/testify/require"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestRemoveTimeSpan_succeeds_removesTimeSpan(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
user := db.User(3)
ts := user.TimeSpan("2019-06-11T18:00:00Z", "2019-06-11T18:00:00Z")
ts.Tag("hello", "world")
resolver := ResolverForTimeSpan{DB: db.DB}
actual, err := resolver.RemoveTimeSpan(fake.User(3), ts.TimeSpan.ID)
require.NoError(t, err)
expected := &gqlmodel.TimeSpan{
ID: ts.TimeSpan.ID,
Start: test.ModelTime("2019-06-11T18:00:00Z"),
End: test.ModelTimeP("2019-06-11T18:00:00Z"),
Tags: []*gqlmodel.TimeSpanTag{
{Key: "hello", Value: "world"},
},
}
require.Equal(t, expected, actual)
assertTimeSpanCount(t, db, 0)
}
func TestRemoveTimeSpan_succeeds_removesTags(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
user := db.User(3)
ts := user.TimeSpan("2019-06-11T18:00:00Z", "2019-06-11T18:00:00Z")
ts.Tag("hello", "world")
resolver := ResolverForTimeSpan{DB: db.DB}
_, err := resolver.RemoveTimeSpan(fake.User(3), ts.TimeSpan.ID)
require.NoError(t, err)
ts.AssertHasTag("hello", "world", false)
}
func TestRemoveTimeSpan_fails_notExistingTimeSpan(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(3)
resolver := ResolverForTimeSpan{DB: db.DB}
_, err := resolver.RemoveTimeSpan(fake.User(3), 5)
require.EqualError(t, err, "timespan with id 5 does not exist")
}
func TestRemoveTimeSpan_fails_noPermission(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(3)
db.User(5)
db.Create(&model.TimeSpan{
StartUserTime: test.Time("2019-06-11T18:00:00Z"),
StartUTC: test.Time("2019-06-11T18:00:00Z"),
EndUserTime: nil,
EndUTC: nil,
OffsetUTC: 0,
ID: 1,
UserID: 3,
})
resolver := ResolverForTimeSpan{DB: db.DB}
_, err := resolver.RemoveTimeSpan(fake.User(5), 1)
require.EqualError(t, err, "timespan with id 1 does not exist")
}

71
timespan/replace_tags.go Normal file
View File

@@ -0,0 +1,71 @@
package timespan
import (
"context"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// ReplaceTimeSpanTags replaces time span tags.
func (r *ResolverForTimeSpan) ReplaceTimeSpanTags(ctx context.Context, fromExternal gqlmodel.InputTimeSpanTag, toExternal gqlmodel.InputTimeSpanTag, opt gqlmodel.InputReplaceOptions) (*bool, error) {
userID := auth.GetUser(ctx).ID
from := tagToInternal(fromExternal)
to := tagToInternal(toExternal)
if err := tagsExist(r.DB, userID, []model.TimeSpanTag{from}); err != nil {
return nil, err
}
if err := tagsExist(r.DB, userID, []model.TimeSpanTag{to}); err != nil {
return nil, err
}
tx := r.DB.Begin()
hasToKey := tx.Table("time_span_tags as innertst").
Where("innertst.key = ?", to.Key).
Where("innerts.id = innertst.time_span_id").
SubQuery()
if opt.Override == gqlmodel.OverrideModeOverride {
timeSpanIdsWithExistingToKey := tx.Table("time_spans as innerts").
Select("id").
Where("innerts.user_id = ?", userID).
Where("EXISTS ?", hasToKey).
SubQuery()
if err := tx.Where("time_span_id in ?", timeSpanIdsWithExistingToKey).
Where(&model.TimeSpanTag{Key: to.Key}).
Delete(new(model.TimeSpanTag)).Error; err != nil {
tx.Rollback()
return nil, err
}
}
timeSpansIdsOfUser := tx.Table("time_spans as innerts").
Select("id").
Where("innerts.user_id = ?", userID).
Where("NOT(EXISTS ?)", hasToKey).
SubQuery()
if update := tx.Model(&model.TimeSpanTag{}).
Where("time_span_id in ?", timeSpansIdsOfUser).
Where(from).
Updates(to); update.Error != nil {
tx.Rollback()
return nil, update.Error
}
if opt.Override == gqlmodel.OverrideModeDiscard {
if err := tx.Where(&from).Delete(new(model.TimeSpanTag)).Error; err != nil {
tx.Rollback()
return nil, err
}
}
commit := tx.Commit()
return nil, commit.Error
}

View File

@@ -0,0 +1,109 @@
package timespan
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestReplace_simple(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
user := db.User(1)
user.NewTagDefinition("old")
user.NewTagDefinition("new")
ts := user.TimeSpan("2019-06-11T18:00:00Z", "2019-06-11T18:00:00Z")
ts.Tag("old", "1")
resolver := ResolverForTimeSpan{DB: db.DB}
_, err := resolver.ReplaceTimeSpanTags(fake.User(1),
gqlmodel.InputTimeSpanTag{Key: "old", Value: "1"},
gqlmodel.InputTimeSpanTag{Key: "new", Value: "1.0"},
gqlmodel.InputReplaceOptions{Override: gqlmodel.OverrideModeDiscard})
require.NoError(t, err)
ts.AssertHasTag("new", "1.0", true)
ts.AssertHasTag("old", "1", false)
}
func TestReplace_discard(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
user := db.User(1)
user.NewTagDefinition("old")
user.NewTagDefinition("new")
ts := user.TimeSpan("2019-06-11T18:00:00Z", "2019-06-11T18:00:00Z")
ts.Tag("old", "1")
ts.Tag("new", "2.0")
resolver := ResolverForTimeSpan{DB: db.DB}
_, err := resolver.ReplaceTimeSpanTags(fake.User(1),
gqlmodel.InputTimeSpanTag{Key: "old", Value: "1"},
gqlmodel.InputTimeSpanTag{Key: "new", Value: "1.0"},
gqlmodel.InputReplaceOptions{Override: gqlmodel.OverrideModeDiscard})
require.NoError(t, err)
ts.AssertHasTag("new", "2.0", true)
ts.AssertHasTag("new", "1.0", false)
ts.AssertHasTagIgnoreValue("old", false)
}
func TestReplace_override(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
user := db.User(1)
user.NewTagDefinition("old")
user.NewTagDefinition("new")
ts := user.TimeSpan("2019-06-11T18:00:00Z", "2019-06-11T18:00:00Z")
ts.Tag("old", "1")
ts.Tag("new", "2.0")
resolver := ResolverForTimeSpan{DB: db.DB}
_, err := resolver.ReplaceTimeSpanTags(fake.User(1),
gqlmodel.InputTimeSpanTag{Key: "old", Value: "1"},
gqlmodel.InputTimeSpanTag{Key: "new", Value: "1.0"},
gqlmodel.InputReplaceOptions{Override: gqlmodel.OverrideModeOverride})
require.NoError(t, err)
ts.AssertHasTag("new", "2.0", false)
ts.AssertHasTag("new", "1.0", true)
ts.AssertHasTagIgnoreValue("old", false)
}
func TestReplace_multiuser(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
left := db.User(1)
right := db.User(2)
left.NewTagDefinition("old")
left.NewTagDefinition("new")
right.NewTagDefinition("old")
right.NewTagDefinition("new")
leftTs := left.TimeSpan("2019-06-11T18:00:00Z", "2019-06-11T18:00:00Z")
leftTs.Tag("old", "1")
leftTs.Tag("new", "2.0")
rightTs := right.TimeSpan("2019-06-11T18:00:00Z", "2019-06-11T18:00:00Z")
rightTs.Tag("old", "1")
rightTs.Tag("new", "2.0")
resolver := ResolverForTimeSpan{DB: db.DB}
_, err := resolver.ReplaceTimeSpanTags(fake.User(1),
gqlmodel.InputTimeSpanTag{Key: "old", Value: "1"},
gqlmodel.InputTimeSpanTag{Key: "new", Value: "1.0"},
gqlmodel.InputReplaceOptions{Override: gqlmodel.OverrideModeOverride})
require.NoError(t, err)
leftTs.AssertHasTag("new", "2.0", false)
leftTs.AssertHasTag("new", "1.0", true)
leftTs.AssertHasTagIgnoreValue("old", false)
rightTs.AssertHasTag("old", "1", true)
rightTs.AssertHasTag("new", "2.0", true)
}

33
timespan/stop.go Normal file
View File

@@ -0,0 +1,33 @@
package timespan
import (
"context"
"fmt"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// StopTimeSpan sets an end date to an existing time span.
func (r *ResolverForTimeSpan) StopTimeSpan(ctx context.Context, id int, end model.Time) (*gqlmodel.TimeSpan, error) {
old := &model.TimeSpan{ID: id}
if r.DB.Preload("Tags").Where("user_id = ?", auth.GetUser(ctx).ID).Find(old).RecordNotFound() {
return nil, fmt.Errorf("time span with id %d does not exist", id)
}
if old.EndUTC != nil {
return nil, fmt.Errorf("timespan with id %d has already an end date", id)
}
utc := end.UTC()
old.EndUTC = &utc
userTime := end.OmitTimeZone()
old.EndUserTime = &userTime
r.DB.Where("time_span_id = ?", old.ID).Delete(new(model.TimeSpanTag))
r.DB.Save(old)
external := timeSpanToExternal(*old)
return external, nil
}

111
timespan/stop_test.go Normal file
View File

@@ -0,0 +1,111 @@
package timespan
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func Test_Stop_fail_notExisting(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.StopTimeSpan(fake.User(5), 3, test.ModelTime("2019-06-10T18:35:00+02:00"))
require.Nil(t, timeSpan)
require.EqualError(t, err, "time span with id 3 does not exist")
assertTimeSpanCount(t, db, 0)
}
func Test_Stop_fail_noPermission(t *testing.T) {
db := test.InMemoryDB(t)
db.User(3)
db.User(2)
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 3,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
defer db.Close()
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.StopTimeSpan(fake.User(2), 3, test.ModelTime("2019-06-10T18:35:00+02:00"))
require.Nil(t, timeSpan)
require.EqualError(t, err, "time span with id 3 does not exist")
assertTimeSpanCount(t, db, 1)
}
func Test_Stop_fail_alreadyFinished(t *testing.T) {
db := test.InMemoryDB(t)
db.User(2)
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 2,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T19:30:00Z"),
EndUTC: test.TimeP("2019-06-10T17:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
defer db.Close()
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.StopTimeSpan(fake.User(2), 3, test.ModelTime("2019-06-10T18:35:00+02:00"))
require.Nil(t, timeSpan)
require.EqualError(t, err, "timespan with id 3 has already an end date")
assertTimeSpanCount(t, db, 1)
}
func Test_Stop(t *testing.T) {
db := test.InMemoryDB(t)
db.User(2)
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 2,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
defer db.Close()
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.StopTimeSpan(fake.User(2), 3, test.ModelTime("2019-06-10T18:35:00+02:00"))
require.NoError(t, err)
expected := &gqlmodel.TimeSpan{
ID: 3,
Start: test.ModelTime("2019-06-10T18:30:00+02:00"),
End: test.ModelTimeP("2019-06-10T18:35:00+02:00"),
Tags: []*gqlmodel.TimeSpanTag{},
}
require.Equal(t, expected, timeSpan)
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 3,
UserID: 2,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:35:00Z"),
EndUTC: test.TimeP("2019-06-10T16:35:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
}

27
timespan/suggestvalue.go Normal file
View File

@@ -0,0 +1,27 @@
package timespan
import (
"context"
"github.com/traggo/server/auth"
"github.com/traggo/server/model"
)
// SuggestTagValue suggests a tag value.
func (r *ResolverForTimeSpan) SuggestTagValue(ctx context.Context, key string, query string) ([]string, error) {
var suggestions []model.TimeSpanTag
find := r.DB.
Select("DISTINCT time_span_tags.string_value").
Joins("JOIN time_spans on time_spans.id = time_span_tags.time_span_id").
Where("user_id = ?", auth.GetUser(ctx).ID).
Where("key = ?", key).Where("LOWER(string_value) LIKE LOWER(?)", "%"+query+"%").
Limit(10).
Find(&suggestions)
var result []string
for _, value := range suggestions {
result = append(result, value.StringValue)
}
return result, find.Error
}

View File

@@ -0,0 +1,35 @@
package timespan
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
const date = "2019-06-10T18:30:00Z"
func TestGQL_SuggestTagValue(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
user := db.User(1)
user.TimeSpan(date, date).Tag("proj", "gotify").Tag("issue", "3")
user.TimeSpan(date, date).Tag("proj", "traggo").Tag("issue", "3")
user.TimeSpan(date, date).Tag("proj", "traggo").Tag("issue", "3")
user.TimeSpan(date, date).Tag("proj", "meh").Tag("issue", "3")
other := db.User(2)
other.TimeSpan(date, date).Tag("proj", "secret").Tag("issue", "3")
resolver := ResolverForTimeSpan{DB: db.DB}
tags, err := resolver.SuggestTagValue(fake.User(1), "proj", "")
require.Nil(t, err)
expected := []string{"gotify", "traggo", "meh"}
require.Equal(t, expected, tags)
tags, err = resolver.SuggestTagValue(fake.User(1), "proj", "uff")
require.Nil(t, err)
require.Empty(t, tags)
}

25
timespan/tagcheck.go Normal file
View File

@@ -0,0 +1,25 @@
package timespan
import (
"fmt"
"github.com/jinzhu/gorm"
"github.com/traggo/server/model"
)
func tagsExist(db *gorm.DB, userID int, tags []model.TimeSpanTag) error {
existingTags := make(map[string]struct{})
for _, tag := range tags {
if _, ok := existingTags[tag.Key]; ok {
return fmt.Errorf("tag '%s' is present multiple times", tag.Key)
}
if db.Where("key = ?", tag.Key).Where("user_id = ?", userID).Find(new(model.TagDefinition)).RecordNotFound() {
return fmt.Errorf("tag '%s' does not exist", tag.Key)
}
existingTags[tag.Key] = struct{}{}
}
return nil
}

27
timespan/timers.go Normal file
View File

@@ -0,0 +1,27 @@
package timespan
import (
"context"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// Timers returns all running timers for a user
func (r *ResolverForTimeSpan) Timers(ctx context.Context) ([]*gqlmodel.TimeSpan, error) {
user := auth.GetUser(ctx)
var timeSpans []model.TimeSpan
r.DB.Preload("Tags").
Where("user_id = ?", user.ID).
Where("end_user_time is null").
Order("start_user_time DESC").
Find(&timeSpans)
result := []*gqlmodel.TimeSpan{}
for _, span := range timeSpans {
result = append(result, timeSpanToExternal(span))
}
return result, nil
}

25
timespan/timers_test.go Normal file
View File

@@ -0,0 +1,25 @@
package timespan
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestTimers(t *testing.T) {
db := test.InMemoryDB(t)
db.User(5)
db.Create(timeSpan1)
db.Create(runningTimeSpan)
defer db.Close()
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpans, err := resolver.Timers(fake.User(5))
require.NoError(t, err)
expected := []*gqlmodel.TimeSpan{&modelRunningTimeSpan}
require.Equal(t, expected, timeSpans)
}

View File

@@ -0,0 +1,8 @@
package timespan
import "github.com/jinzhu/gorm"
// ResolverForTimeSpan resolves time span specific things.
type ResolverForTimeSpan struct {
DB *gorm.DB
}

42
timespan/update.go Normal file
View File

@@ -0,0 +1,42 @@
package timespan
import (
"context"
"fmt"
"time"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// UpdateTimeSpan update a time span
func (r *ResolverForTimeSpan) UpdateTimeSpan(ctx context.Context, id int, start model.Time, end *model.Time, tags []*gqlmodel.InputTimeSpanTag, oldStart *model.Time, note string) (*gqlmodel.TimeSpan, error) {
timeSpan, err := timespanToInternal(auth.GetUser(ctx).ID, start, end, tags, note)
if err != nil {
return nil, err
}
oldTimeSpan := model.TimeSpan{ID: id}
if r.DB.Where("user_id = ?", auth.GetUser(ctx).ID).Find(&oldTimeSpan).RecordNotFound() {
return nil, fmt.Errorf("time span with id %d does not exist", id)
}
if err := tagsExist(r.DB, auth.GetUser(ctx).ID, timeSpan.Tags); err != nil {
return nil, err
}
timeSpan.ID = id
r.DB.Where("time_span_id = ?", timeSpan.ID).Delete(new(model.TimeSpanTag))
r.DB.Save(&timeSpan)
external := timeSpanToExternal(timeSpan)
if oldStart == nil {
location := time.FixedZone("unknown", oldTimeSpan.OffsetUTC)
old := model.Time(oldTimeSpan.StartUTC.In(location))
oldStart = &old
}
external.OldStart = oldStart
return external, nil
}

251
timespan/update_test.go Normal file
View File

@@ -0,0 +1,251 @@
package timespan
import (
"testing"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func Test_Update_withoutEnd(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(3)
note := "A note"
db.Create(&model.TimeSpan{
ID: 1,
UserID: 3,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
Note: note,
})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(3), 1, test.ModelTime("2019-06-10T19:30:00+02:00"), nil, nil, nil, "")
require.Nil(t, err)
expected := &gqlmodel.TimeSpan{
ID: 1,
Start: test.ModelTime("2019-06-10T19:30:00+02:00"),
OldStart: test.ModelTimeP("2019-06-10T18:30:00+02:00"),
Tags: []*gqlmodel.TimeSpanTag{},
Note: "",
}
require.Equal(t, expected, timeSpan)
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 1,
UserID: 3,
StartUserTime: test.Time("2019-06-10T19:30:00Z"),
StartUTC: test.Time("2019-06-10T17:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
Note: "",
})
}
func Test_Update(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
note := "A note"
db.Create(&model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
Note: note,
})
note = "An upated note"
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(5), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T20:30:00+02:00"), nil, nil, note)
log.Debug().Msg("oops")
require.Nil(t, err)
expected := &gqlmodel.TimeSpan{
ID: 3,
Start: test.ModelTime("2019-06-10T18:30:00+02:00"),
OldStart: test.ModelTimeP("2019-06-10T18:30:00+02:00"),
End: test.ModelTimeP("2019-06-10T20:30:00+02:00"),
Tags: []*gqlmodel.TimeSpanTag{},
Note: note,
}
require.Equal(t, expected, timeSpan)
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T20:30:00Z"),
EndUTC: test.TimeP("2019-06-10T18:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
Note: note,
})
}
func Test_Update_fail_endBeforeStart(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(5), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T17:30:00+02:00"), nil, nil, "")
require.Nil(t, timeSpan)
require.EqualError(t, err, "start must be before end")
assertTimeSpanCount(t, db, 1)
}
func Test_Update_fail_notExistingTag(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(5), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}}, nil, "nil")
require.Nil(t, timeSpan)
require.EqualError(t, err, "tag 'test' does not exist")
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
}
func Test_Update_withTag(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T19:30:00Z"),
EndUTC: test.TimeP("2019-06-10T17:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(5), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T19:30:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}}, nil, "")
require.NotNil(t, timeSpan)
require.NoError(t, err)
assertTimeSpanCount(t, db, 1)
assertTimeSpanExist(t, db, model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T19:30:00Z"),
EndUTC: test.TimeP("2019-06-10T17:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{
{Key: "test", TimeSpanID: 3},
},
})
}
func Test_Update_fail_tagAddedMultipleTimes(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 5,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(5), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}, {Key: "test"}}, nil, "")
require.Nil(t, timeSpan)
require.EqualError(t, err, "tag 'test' is present multiple times")
assertTimeSpanCount(t, db, 1)
}
func Test_Update_fail_notExisting(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(5)
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(5), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}, {Key: "test"}}, nil, "")
require.Nil(t, timeSpan)
require.EqualError(t, err, "time span with id 3 does not exist")
assertTimeSpanCount(t, db, 0)
}
func Test_Update_fail_noPermission(t *testing.T) {
db := test.InMemoryDB(t)
db.User(3)
db.User(2)
db.User(5)
db.Create(&model.TimeSpan{
ID: 3,
UserID: 3,
StartUserTime: test.Time("2019-06-10T18:30:00Z"),
StartUTC: test.Time("2019-06-10T16:30:00Z"),
EndUserTime: test.TimeP("2019-06-10T18:30:00Z"),
EndUTC: test.TimeP("2019-06-10T16:30:00Z"),
OffsetUTC: 7200,
Tags: []model.TimeSpanTag{},
})
defer db.Close()
db.Create(&model.TagDefinition{Key: "test", UserID: 5})
resolver := ResolverForTimeSpan{DB: db.DB}
timeSpan, err := resolver.UpdateTimeSpan(fake.User(2), 3, test.ModelTime("2019-06-10T18:30:00+02:00"),
test.ModelTimeP("2019-06-10T18:35:00+02:00"), []*gqlmodel.InputTimeSpanTag{{Key: "test"}, {Key: "test"}}, nil, "")
require.Nil(t, timeSpan)
require.EqualError(t, err, "time span with id 3 does not exist")
assertTimeSpanCount(t, db, 1)
}

23
timespan/utils_test.go Normal file
View File

@@ -0,0 +1,23 @@
package timespan
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
)
func assertTimeSpanExist(t *testing.T, db *test.Database, expected model.TimeSpan) {
found := new(model.TimeSpan)
find := db.Preload("Tags").Where("user_id = ?", expected.UserID).Where("id = ?", expected.ID).Find(found)
require.Nil(t, find.Error)
require.NotNil(t, found)
require.Equal(t, expected, *found)
}
func assertTimeSpanCount(t *testing.T, db *test.Database, expected int) {
count := new(int)
db.Model(new(model.TimeSpan)).Count(count)
require.Equal(t, expected, *count)
}