package mdb import ( "errors" "fmt" "reflect" "strings" "testing" "git.crumpington.com/public/jldb/lib/errs" ) type DBTestCase struct { Name string Steps []DBTestStep } type DBTestStep struct { Name string Update func(t *testing.T, db TestDB, tx *Snapshot) error ExpectedUpdateError error State DBState } type DBState struct { UsersByID []User UsersByEmail []User UsersByName []User UsersByBlocked []User DataByID []UserDataItem DataByName []UserDataItem } var testDBTestCases = []DBTestCase{{ Name: "Insert update", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Update", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := db.Users.ByID.Get(tx, &User{ID: 1}) if user == nil { return errs.NotFound } user.Name = "Bob" user.Email = "b@c.com" return db.Users.Update(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Bob", Email: "b@c.com"}}, UsersByEmail: []User{{ID: 1, Name: "Bob", Email: "b@c.com"}}, UsersByName: []User{{ID: 1, Name: "Bob", Email: "b@c.com"}}, }, }}, }, { Name: "Insert delete", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Delete", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { return db.Users.Delete(tx, 1) }, State: DBState{}, }}, }, { Name: "Insert duplicate one tx (ID)", Steps: []DBTestStep{{ Name: "Insert with duplicate", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} if err := db.Users.Insert(tx, user); err != nil { return err } user2 := &User{ID: 1, Name: "Bob", Email: "b@c.com"} return db.Users.Insert(tx, user2) }, ExpectedUpdateError: errs.Duplicate, State: DBState{}, }}, }, { Name: "Insert duplicate one tx (email)", Steps: []DBTestStep{{ Name: "Insert with duplicate", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} if err := db.Users.Insert(tx, user); err != nil { return err } user2 := &User{ID: 2, Name: "Bob", Email: "a@b.com"} return db.Users.Insert(tx, user2) }, ExpectedUpdateError: errs.Duplicate, State: DBState{}, }}, }, { Name: "Insert duplicate two txs (ID)", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Insert duplicate", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Bob", Email: "b@c.com"} return db.Users.Insert(tx, user) }, ExpectedUpdateError: errs.Duplicate, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Insert duplicate two txs (email)", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Insert duplicate", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 2, Name: "Bob", Email: "a@b.com"} return db.Users.Insert(tx, user) }, ExpectedUpdateError: errs.Duplicate, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Insert read-only snapshot", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(db.Snapshot(), user) }, ExpectedUpdateError: errs.ReadOnly, }}, }, { Name: "Insert partial index", Steps: []DBTestStep{{ Name: "Insert Alice", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 5, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Insert Bob", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 2, Name: "Bob", Email: "b@c.com", Blocked: true} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{ {ID: 2, Name: "Bob", Email: "b@c.com", Blocked: true}, {ID: 5, Name: "Alice", Email: "a@b.com"}, }, UsersByEmail: []User{ {ID: 5, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com", Blocked: true}, }, UsersByName: []User{ {ID: 5, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com", Blocked: true}, }, UsersByBlocked: []User{ {ID: 2, Name: "Bob", Email: "b@c.com", Blocked: true}, }, }, }}, }, { Name: "Update not found", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 5, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Update", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 4, Name: "Alice", Email: "x@y.com"} return db.Users.Update(tx, user) }, ExpectedUpdateError: errs.NotFound, State: DBState{ UsersByID: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 5, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Update read-only snapshot", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Update", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := db.Users.ByID.Get(tx, &User{ID: 1}) if user == nil { return errs.NotFound } user.Name = "Bob" user.Email = "b@c.com" return db.Users.Update(db.Snapshot(), user) }, ExpectedUpdateError: errs.ReadOnly, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Insert into two collections", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} if err := db.Users.Insert(tx, user); err != nil { return err } data := &UserDataItem{ID: 1, UserID: user.ID, Name: "Item1", Data: "xyz"} return db.UserData.Insert(tx, data) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, DataByID: []UserDataItem{{ID: 1, UserID: 1, Name: "Item1", Data: "xyz"}}, DataByName: []UserDataItem{{ID: 1, UserID: 1, Name: "Item1", Data: "xyz"}}, }, }}, }, { Name: "Update into index", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Update", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true} return db.Users.Update(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, UsersByBlocked: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, }, }}, }, { Name: "Update out of index", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, UsersByBlocked: []User{{ID: 1, Name: "Alice", Email: "a@b.com", Blocked: true}}, }, }, { Name: "Update", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Update(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Update duplicate one tx", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user1 := &User{ID: 1, Name: "Alice", Email: "a@b.com"} if err := db.Users.Insert(tx, user1); err != nil { return err } user2 := &User{ID: 2, Name: "Bob", Email: "b@c.com"} if err := db.Users.Insert(tx, user2); err != nil { return err } user2.Email = "a@b.com" return db.Users.Update(tx, user2) }, ExpectedUpdateError: errs.Duplicate, State: DBState{}, }}, }, { Name: "Update duplicate two txs", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user1 := &User{ID: 1, Name: "Alice", Email: "a@b.com"} if err := db.Users.Insert(tx, user1); err != nil { return err } user2 := &User{ID: 2, Name: "Bob", Email: "b@c.com"} return db.Users.Insert(tx, user2) }, State: DBState{ UsersByID: []User{ {ID: 1, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com"}, }, UsersByEmail: []User{ {ID: 1, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com"}, }, UsersByName: []User{ {ID: 1, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com"}, }, }, }, { Name: "Update", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { u := db.Users.ByID.Get(tx, &User{ID: 2}) if u == nil { return errs.NotFound } u.Email = "a@b.com" return db.Users.Update(tx, u) }, ExpectedUpdateError: errs.Duplicate, State: DBState{ UsersByID: []User{ {ID: 1, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com"}, }, UsersByEmail: []User{ {ID: 1, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com"}, }, UsersByName: []User{ {ID: 1, Name: "Alice", Email: "a@b.com"}, {ID: 2, Name: "Bob", Email: "b@c.com"}, }, }, }}, }, { Name: "Delete read only", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Delete", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { return db.Users.Delete(db.Snapshot(), 1) }, ExpectedUpdateError: errs.ReadOnly, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Delete not found", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Delete", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { return db.Users.Delete(tx, 2) }, ExpectedUpdateError: errs.NotFound, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Index general", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { user := &User{ID: 1, Name: "Alice", Email: "a@b.com"} return db.Users.Insert(tx, user) }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Get found", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { expected := &User{ID: 1, Name: "Alice", Email: "a@b.com"} u := db.Users.ByID.Get(tx, &User{ID: 1}) if u == nil { return errs.NotFound } if !reflect.DeepEqual(u, expected) { return errors.New("Not equal (id)") } u = db.Users.ByEmail.Get(tx, &User{Email: "a@b.com"}) if u == nil { return errs.NotFound } if !reflect.DeepEqual(u, expected) { return errors.New("Not equal (email)") } return nil }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Get not found", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { if u := db.Users.ByID.Get(tx, &User{ID: 2}); u != nil { return errors.New("Found (id)") } if u := db.Users.ByEmail.Get(tx, &User{Email: "x@b.com"}); u != nil { return errors.New("Found (email)") } return nil }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Has (true)", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { if ok := db.Users.ByID.Has(tx, &User{ID: 1}); !ok { return errors.New("Not found (id)") } if ok := db.Users.ByEmail.Has(tx, &User{Email: "a@b.com"}); !ok { return errors.New("Not found (email)") } return nil }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }, { Name: "Has (false)", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { if ok := db.Users.ByID.Has(tx, &User{ID: 2}); ok { return errors.New("Found (id)") } if ok := db.Users.ByEmail.Has(tx, &User{Email: "x@b.com"}); ok { return errors.New("Found (email)") } return nil }, State: DBState{ UsersByID: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByEmail: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, UsersByName: []User{{ID: 1, Name: "Alice", Email: "a@b.com"}}, }, }}, }, { Name: "Mutate while iterating", Steps: []DBTestStep{{ Name: "Insert", Update: func(t *testing.T, db TestDB, tx *Snapshot) error { for i := 0; i < 4; i++ { user := &User{ ID: uint64(i) + 1, Name: fmt.Sprintf("User%d", i), Email: fmt.Sprintf("user.%d@x.com", i), } if err := db.Users.Insert(tx, user); err != nil { return err } } return nil }, State: DBState{ UsersByID: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1", Email: "user.1@x.com"}, {ID: 3, Name: "User2", Email: "user.2@x.com"}, {ID: 4, Name: "User3", Email: "user.3@x.com"}, }, UsersByEmail: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1", Email: "user.1@x.com"}, {ID: 3, Name: "User2", Email: "user.2@x.com"}, {ID: 4, Name: "User3", Email: "user.3@x.com"}, }, UsersByName: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1", Email: "user.1@x.com"}, {ID: 3, Name: "User2", Email: "user.2@x.com"}, {ID: 4, Name: "User3", Email: "user.3@x.com"}, }, }, }, { Name: "Modify while iterating", Update: func(t *testing.T, db TestDB, tx *Snapshot) (err error) { first := true pivot := User{Name: "User1"} db.Users.ByName.AscendAfter(tx, &pivot, func(u *User) bool { u.Name += "Mod" if err = db.Users.Update(tx, u); err != nil { return false } if first { first = false return true } prev := db.Users.ByID.Get(tx, &User{ID: u.ID - 1}) if prev == nil { err = errors.New("Previous user not found") return false } if !strings.HasSuffix(prev.Name, "Mod") { err = errors.New("Incorrect user name: " + prev.Name) return false } return true }) return nil }, State: DBState{ UsersByID: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1Mod", Email: "user.1@x.com"}, {ID: 3, Name: "User2Mod", Email: "user.2@x.com"}, {ID: 4, Name: "User3Mod", Email: "user.3@x.com"}, }, UsersByEmail: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1Mod", Email: "user.1@x.com"}, {ID: 3, Name: "User2Mod", Email: "user.2@x.com"}, {ID: 4, Name: "User3Mod", Email: "user.3@x.com"}, }, UsersByName: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1Mod", Email: "user.1@x.com"}, {ID: 3, Name: "User2Mod", Email: "user.2@x.com"}, {ID: 4, Name: "User3Mod", Email: "user.3@x.com"}, }, }, }, { Name: "Iterate after modifying", Update: func(t *testing.T, db TestDB, tx *Snapshot) (err error) { u := &User{ID: 5, Name: "User4Mod", Email: "user.4@x.com"} if err := db.Users.Insert(tx, u); err != nil { return err } first := true db.Users.ByName.DescendAfter(tx, &User{Name: "User5Mod"}, func(u *User) bool { u.Name = strings.TrimSuffix(u.Name, "Mod") if err = db.Users.Update(tx, u); err != nil { return false } if first { first = false return true } prev := db.Users.ByID.Get(tx, &User{ID: u.ID + 1}) if prev == nil { err = errors.New("Previous user not found") return false } if strings.HasSuffix(prev.Name, "Mod") { err = errors.New("Incorrect user name: " + prev.Name) return false } return true }) return nil }, State: DBState{ UsersByID: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1", Email: "user.1@x.com"}, {ID: 3, Name: "User2", Email: "user.2@x.com"}, {ID: 4, Name: "User3", Email: "user.3@x.com"}, {ID: 5, Name: "User4", Email: "user.4@x.com"}, }, UsersByEmail: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1", Email: "user.1@x.com"}, {ID: 3, Name: "User2", Email: "user.2@x.com"}, {ID: 4, Name: "User3", Email: "user.3@x.com"}, {ID: 5, Name: "User4", Email: "user.4@x.com"}, }, UsersByName: []User{ {ID: 1, Name: "User0", Email: "user.0@x.com"}, {ID: 2, Name: "User1", Email: "user.1@x.com"}, {ID: 3, Name: "User2", Email: "user.2@x.com"}, {ID: 4, Name: "User3", Email: "user.3@x.com"}, {ID: 5, Name: "User4", Email: "user.4@x.com"}, }, }, }}, }}