snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
build / build (push) Failing after 3s
Some checks failed
build / build (push) Failing after 3s
This commit is contained in:
89
timespan/convert.go
Normal file
89
timespan/convert.go
Normal 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
21
timespan/copy.go
Normal 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
103
timespan/copy_test.go
Normal 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
26
timespan/create.go
Normal 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
138
timespan/create_test.go
Normal 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
76
timespan/get.go
Normal 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
275
timespan/get_test.go
Normal 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
23
timespan/remove.go
Normal 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
78
timespan/remove_test.go
Normal 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
71
timespan/replace_tags.go
Normal 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
|
||||
}
|
||||
109
timespan/replace_tags_test.go
Normal file
109
timespan/replace_tags_test.go
Normal 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
33
timespan/stop.go
Normal 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
111
timespan/stop_test.go
Normal 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
27
timespan/suggestvalue.go
Normal 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
|
||||
}
|
||||
35
timespan/suggestvalue_test.go
Normal file
35
timespan/suggestvalue_test.go
Normal 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
25
timespan/tagcheck.go
Normal 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
27
timespan/timers.go
Normal 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
25
timespan/timers_test.go
Normal 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)
|
||||
}
|
||||
8
timespan/timespanresolver.go
Normal file
8
timespan/timespanresolver.go
Normal 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
42
timespan/update.go
Normal 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
251
timespan/update_test.go
Normal 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
23
timespan/utils_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user