blob: 6e94b07a55f67c868e59ea966b702e65f34eca4f [file]
package handlers
import (
"infra/appengine/rotang"
"infra/appengine/rotang/pkg/algo"
"infra/appengine/rotang/pkg/datastore"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"go.chromium.org/luci/auth/identity"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/auth/authtest"
"go.chromium.org/luci/server/router"
"go.chromium.org/luci/server/templates"
"golang.org/x/net/context"
)
func TestCleanOwners(t *testing.T) {
tests := []struct {
name string
fail bool
email string
owners string
want []string
}{{
name: "Single entry",
email: "testuser@test.com",
owners: "testuser@test.com ",
want: []string{"testuser@test.com"},
}, {
name: "Multiple entries",
email: "testuser@test.com",
owners: "first@test.com,second@test.com, testuser@test.com , third@test.com",
want: []string{"first@test.com", "second@test.com", "testuser@test.com", "third@test.com"},
}, {
name: "Owner not in list",
fail: true,
email: "testuser@test.com",
owners: "first@test.com,second@test.com, third@test.com",
want: []string{"first@test.com", "second@test.com", "third@test.com"},
},
}
for _, tst := range tests {
res, err := cleanOwners(tst.email, tst.owners)
if got, want := (err != nil), tst.fail; got != want {
t.Errorf("%s: cleanOwners(%q, %q) = %t want: %t, err: %v", tst.name, tst.email, tst.owners, got, want, err)
continue
}
if err != nil {
continue
}
if diff := pretty.Compare(tst.want, res); diff != "" {
t.Errorf("%s: cleanOwners(%q, %q) differs -want +got, %s", tst.name, tst.email, tst.owners, diff)
}
}
}
func TestFillIntegers(t *testing.T) {
testTime, err := time.Parse("15:04", "13:37")
if err != nil {
t.Fatalf("time.Parse() failed: %v", err)
}
tests := []struct {
name string
fail bool
values url.Values
want rotang.Config
}{{
name: "Working",
values: url.Values{
"EmailNotify": {"7"},
"Expiration": {"4"},
"ShiftsToSchedule": {"5"},
"shiftLength": {"5"},
"shiftSkip": {"2"},
"shiftMembers": {"2"},
"shiftStart": {"13:37"},
},
want: rotang.Config{
Expiration: 4,
DaysToSchedule: 5,
Email: rotang.Email{
DaysBeforeNotify: 7,
},
Shifts: rotang.ShiftConfig{
Length: 5,
Skip: 2,
ShiftMembers: 2,
StartTime: testTime,
},
},
}, {
name: "Failing",
fail: true,
values: url.Values{
"EmailNotify": {"7"},
"Expiration": {"4"},
"ShiftsToSchedule": {"Not a Number"},
"shiftLength": {"5"},
"shiftSkip": {"2"},
"shiftMembers": {"2"},
"shiftStart": {"13:37"},
},
want: rotang.Config{
Expiration: 4,
DaysToSchedule: 5,
Email: rotang.Email{
DaysBeforeNotify: 7,
},
Shifts: rotang.ShiftConfig{
Length: 5,
Skip: 2,
ShiftMembers: 2,
StartTime: testTime,
},
},
}}
for _, tst := range tests {
ctx := &router.Context{
Request: &http.Request{
Form: tst.values,
},
}
var cfg rotang.Config
err := fillIntegers(ctx, &cfg)
if got, want := (err != nil), tst.fail; got != want {
t.Errorf("%s: fillIntegers(_, _) = %t want: %t, err: %v", tst.name, got, want, err)
continue
}
if err != nil {
continue
}
if diff := pretty.Compare(tst.want, cfg); diff != "" {
t.Errorf("%s: fillIntegers(_, _) differ -want +got %s", tst.name, diff)
}
}
}
func TestFillShifts(t *testing.T) {
tests := []struct {
name string
fail bool
values url.Values
want []rotang.Shift
}{{
name: "Single shift",
values: url.Values{
"addShiftName": {"MTV All Day"},
"addShiftDuration": {"24"},
},
want: []rotang.Shift{
{
Name: "MTV All Day",
Duration: 24 * time.Hour,
},
},
}, {
name: "Multiple Shifts",
values: url.Values{
"addShiftName": {"MTV Half Day", "SYD Half Day"},
"addShiftDuration": {"12", "12"},
},
want: []rotang.Shift{
{
Name: "MTV Half Day",
Duration: 12 * time.Hour,
}, {
Name: "SYD Half Day",
Duration: 12 * time.Hour,
},
},
}, {
name: "Not a number",
fail: true,
values: url.Values{
"addShiftName": {"MTV Half Day", "SYD Half Day"},
"addShiftDuration": {"12", "Not a number"},
},
},
}
for _, tst := range tests {
ctx := &router.Context{
Request: &http.Request{
Form: tst.values,
},
}
shifts, err := fillShifts(ctx)
if got, want := (err != nil), tst.fail; got != want {
t.Errorf("%s: fillShifts(ctx) = %t want: %t, err: %v", tst.name, got, want, err)
continue
}
if err != nil {
continue
}
if diff := pretty.Compare(tst.want, shifts); diff != "" {
t.Errorf("%s: fillShifts(ctx) differs -want +got, %s", tst.name, diff)
}
}
}
func TestFillMembers(t *testing.T) {
tests := []struct {
name string
fail bool
values url.Values
memberPool []rotang.Member
want []rotang.ShiftMember
}{{
name: "Add single member",
values: url.Values{
"members": {"aa@aa.com"},
},
memberPool: []rotang.Member{
{
Email: "aa@aa.com",
},
},
want: []rotang.ShiftMember{
{
Email: "aa@aa.com",
},
},
}, {
name: "Add multiple members",
values: url.Values{
"members": {"aa@aa.com", "bb@bb.com"},
"addEmail": {"cc@cc.com", "dd@dd.com"},
"addName": {"Cc", "Dd"},
"addTZ": {"", ""},
},
memberPool: []rotang.Member{
{
Email: "aa@aa.com",
},
{
Email: "bb@bb.com",
},
},
want: []rotang.ShiftMember{
{
Email: "cc@cc.com",
},
{
Email: "dd@dd.com",
},
{
Email: "aa@aa.com",
},
{
Email: "bb@bb.com",
},
},
}, {
name: "Member not in pool",
fail: true,
values: url.Values{
"members": {"aa@aa.com"},
},
want: []rotang.ShiftMember{
{
Email: "aa@aa.com",
},
},
}, {
name: "Member with empty email",
values: url.Values{
"members": {"aa@aa.com", "bb@bb.com"},
"addEmail": {"cc@cc.com", ""},
"addName": {"Cc", "Dd"},
"addTZ": {"", ""},
},
memberPool: []rotang.Member{
{
Email: "aa@aa.com",
},
{
Email: "bb@bb.com",
},
},
want: []rotang.ShiftMember{
{
Email: "cc@cc.com",
},
{
Email: "aa@aa.com",
},
{
Email: "bb@bb.com",
},
},
},
}
testContext := newTestContext()
ds := datastore.New(testContext)
for _, tst := range tests {
t.Run(tst.name, func(t *testing.T) {
for _, m := range tst.memberPool {
if err := ds.CreateMember(testContext, &m); err != nil {
t.Fatalf("%s: ds.CreateMember(ctx, _) failed: %v)", tst.name, err)
}
defer ds.DeleteMember(testContext, m.Email)
}
ctx := &router.Context{
Context: testContext,
Request: &http.Request{
Form: tst.values,
},
}
members, err := fillMembers(ctx, ds)
if got, want := (err != nil), tst.fail; got != want {
t.Fatalf("%s: fillMembers(_, _) = %t want: %t, err: %v", tst.name, got, want, err)
}
if err != nil {
return
}
if diff := pretty.Compare(tst.want, members); diff != "" {
t.Fatalf("%s: fillMembers(_, _) differs -want +got %s", tst.name, diff)
}
})
}
}
func TestGETHandlerCreateRota(t *testing.T) {
ctx := newTestContext()
tests := []struct {
name string
fail bool
ctx *router.Context
user string
memberPool []rotang.Member
}{{
name: "No members in the pool",
fail: true,
user: "test@test.com",
ctx: &router.Context{
Context: ctx,
Writer: httptest.NewRecorder(),
Request: getRequest("/createrota", ""),
},
}, {
name: "GET Success",
user: "test@test.com",
memberPool: []rotang.Member{
{
Email: "member@test.com",
},
},
ctx: &router.Context{
Context: ctx,
Writer: httptest.NewRecorder(),
Request: getRequest("/createrota", ""),
},
}, {
name: "Not logged in",
fail: true,
ctx: &router.Context{
Context: ctx,
Writer: httptest.NewRecorder(),
Request: getRequest("/createrota", ""),
},
},
}
opts := Options{
URL: "http://localhost:8080",
Generators: &algo.Generators{},
}
setupStoreHandlers(&opts, datastore.New)
h, err := New(&opts)
if err != nil {
t.Fatalf("New failed: %v", err)
}
for _, tst := range tests {
t.Run(tst.name, func(t *testing.T) {
for _, m := range tst.memberPool {
if err := h.memberStore(ctx).CreateMember(ctx, &m); err != nil {
t.Fatalf("%s: CreateMember(ctx, _) failed: %v", tst.name, err)
}
defer h.memberStore(ctx).DeleteMember(ctx, m.Email)
}
tst.ctx.Context = templates.Use(tst.ctx.Context, &templates.Bundle{
Loader: templates.FileSystemLoader(templatesLocation),
}, nil)
if tst.user != "" {
tst.ctx.Context = auth.WithState(tst.ctx.Context, &authtest.FakeState{
Identity: identity.Identity("user:" + tst.user),
})
}
h.HandleCreateRota(tst.ctx)
recorder := tst.ctx.Writer.(*httptest.ResponseRecorder)
if got, want := (recorder.Code != http.StatusOK), tst.fail; got != want {
t.Fatalf("%s: HandleCreateRota(ctx) = %t want: %t, res: %v", tst.name, got, want, recorder.Body)
}
})
}
}
func TestHandleCreateRota(t *testing.T) {
ctx := newTestContext()
ctxCancel, cancel := context.WithCancel(ctx)
cancel()
testTime, err := time.Parse("15:04", "13:37")
if err != nil {
t.Fatalf("time.Parse() failed: %v", err)
}
tests := []struct {
name string
fail bool
user string
values url.Values
ctx *router.Context
memberPool []rotang.Member
want rotang.Configuration
}{{
name: "Canceled context",
fail: true,
user: "test@user.com",
ctx: &router.Context{
Context: ctxCancel,
Writer: httptest.NewRecorder(),
},
}, {
name: "Not logged in",
fail: true,
ctx: &router.Context{
Context: ctx,
Writer: httptest.NewRecorder(),
},
}, {
name: "Success",
user: "test@user.com",
ctx: &router.Context{
Context: ctx,
Writer: httptest.NewRecorder(),
},
values: url.Values{
"Name": {"Test Rota"},
"Description": {"Test create rota"},
"Calendar": {"a@b.com"},
"EmailSubjectTemplate": {"{{Subject}}"},
"EmailBodyTemplate": {"{{Body}}"},
"Owners": {"test@user.com"},
"EmailNotify": {"7"},
"Expiration": {"4"},
"ShiftsToSchedule": {"5"},
"shiftLength": {"5"},
"shiftSkip": {"2"},
"shiftMembers": {"2"},
"shiftStart": {"13:37"},
"members": {"aa@aa.com", "bb@bb.com"},
"addName": {"First", "Second", "Third"},
"addEmail": {"first@first.com", "second@second.com", "third@third.com"},
"addTZ": {"America/Los_Angeles", "US/Eastern", "Asia/Tokyo"},
},
memberPool: []rotang.Member{
{
Email: "aa@aa.com",
},
{
Email: "bb@bb.com",
},
},
want: rotang.Configuration{
Config: rotang.Config{
Name: "Test Rota",
Description: "Test create rota",
Calendar: "a@b.com",
Expiration: 4,
Owners: []string{"test@user.com"},
DaysToSchedule: 5,
Email: rotang.Email{
DaysBeforeNotify: 7,
Subject: "{{Subject}}",
Body: "{{Body}}",
},
Shifts: rotang.ShiftConfig{
Length: 5,
Skip: 2,
ShiftMembers: 2,
StartTime: testTime,
},
},
Members: []rotang.ShiftMember{
{
Email: "first@first.com",
},
{
Email: "second@second.com",
},
{
Email: "third@third.com",
},
{
Email: "aa@aa.com",
},
{
Email: "bb@bb.com",
},
},
},
}, {
name: "Add member mismatched fields",
user: "test@user.com",
fail: true,
ctx: &router.Context{
Context: ctx,
Writer: httptest.NewRecorder(),
},
values: url.Values{
"Name": {"Test Rota"},
"Description": {"Test create rota"},
"Calendar": {"a@b.com"},
"EmailSubjectTemplate": {"{{Subject}}"},
"EmailBodyTemplate": {"{{Body}}"},
"Owners": {"test@user.com"},
"EmailNotify": {"7"},
"Expiration": {"4"},
"ShiftsToSchedule": {"5"},
"shiftLength": {"5"},
"shiftSkip": {"2"},
"shiftMembers": {"2"},
"shiftStart": {"13:37"},
"members": {"aa@aa.com", "bb@bb.com"},
"addName": {"First", "Second", "Third"},
"addEmail": {"first@first.com", "second@second.com"},
"addTZ": {"America/Los_Angeles", "US/Eastern", "Asia/Tokyo"},
},
memberPool: []rotang.Member{
{
Email: "aa@aa.com",
},
{
Email: "bb@bb.com",
},
},
},
}
opts := Options{
URL: "http://localhost:8080",
Generators: &algo.Generators{},
}
setupStoreHandlers(&opts, datastore.New)
h, err := New(&opts)
if err != nil {
t.Fatalf("New failed: %v", err)
}
for _, tst := range tests {
t.Run(tst.name, func(t *testing.T) {
for _, m := range tst.memberPool {
if err := h.memberStore(ctx).CreateMember(ctx, &m); err != nil {
t.Fatalf("%s: s.CreateMember(_, _) failed: %v", tst.name, err)
}
defer h.memberStore(ctx).DeleteMember(ctx, m.Email)
}
tst.ctx.Context = templates.Use(tst.ctx.Context, &templates.Bundle{
Loader: templates.FileSystemLoader(templatesLocation),
}, nil)
if tst.user != "" {
tst.ctx.Context = auth.WithState(tst.ctx.Context, &authtest.FakeState{
Identity: identity.Identity("user:" + tst.user),
})
}
tst.ctx.Request = httptest.NewRequest("POST", "/createrota", nil)
tst.ctx.Request.Form = tst.values
h.HandleCreateRota(tst.ctx)
recorder := tst.ctx.Writer.(*httptest.ResponseRecorder)
if got, want := (recorder.Code != http.StatusOK), tst.fail; got != want {
t.Fatalf("%s: HandleCreateRota() = %t want: %t, res: %v", tst.name, got, want, recorder.Body)
}
if recorder.Code != http.StatusOK {
return
}
// Fetch the configuration.
config, err := h.configStore(ctx).RotaConfig(ctx, tst.values["Name"][0])
if err != nil {
t.Fatalf("%s: Rotation(ctx, %q) failed: %v", tst.name, tst.values["Name"][0], err)
}
defer h.configStore(ctx).DeleteRotaConfig(ctx, tst.values["Name"][0])
if diff := pretty.Compare(&tst.want, config[0]); diff != "" {
t.Fatalf("%s: HandleCreateRota(ctx) differ -want +got, %s", tst.name, diff)
}
})
}
}