2023-10-13 09:43:27 +00:00
|
|
|
package mdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
2023-10-16 08:50:19 +00:00
|
|
|
|
|
|
|
"git.crumpington.com/public/jldb/lib/errs"
|
2023-10-13 09:43:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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 {
|
2023-11-21 16:41:55 +00:00
|
|
|
user := db.Users.ByID.Get(tx, &User{ID: 1})
|
|
|
|
if user == nil {
|
2023-10-16 08:50:19 +00:00
|
|
|
return errs.NotFound
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.Duplicate,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.Duplicate,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.Duplicate,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.Duplicate,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.ReadOnly,
|
2023-10-13 09:43:27 +00:00
|
|
|
}},
|
|
|
|
}, {
|
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.NotFound,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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 {
|
2023-11-21 16:41:55 +00:00
|
|
|
user := db.Users.ByID.Get(tx, &User{ID: 1})
|
|
|
|
if user == nil {
|
2023-10-16 08:50:19 +00:00
|
|
|
return errs.NotFound
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
user.Name = "Bob"
|
|
|
|
user.Email = "b@c.com"
|
|
|
|
return db.Users.Update(db.Snapshot(), user)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.ReadOnly,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.Duplicate,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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 {
|
2023-11-21 16:41:55 +00:00
|
|
|
u := db.Users.ByID.Get(tx, &User{ID: 2})
|
|
|
|
if u == nil {
|
2023-10-16 08:50:19 +00:00
|
|
|
return errs.NotFound
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
u.Email = "a@b.com"
|
|
|
|
return db.Users.Update(tx, u)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.Duplicate,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.ReadOnly,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
},
|
|
|
|
|
2023-10-16 08:50:19 +00:00
|
|
|
ExpectedUpdateError: errs.NotFound,
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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"}
|
|
|
|
|
2023-11-21 16:41:55 +00:00
|
|
|
u := db.Users.ByID.Get(tx, &User{ID: 1})
|
|
|
|
if u == nil {
|
2023-10-16 08:50:19 +00:00
|
|
|
return errs.NotFound
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(u, expected) {
|
|
|
|
return errors.New("Not equal (id)")
|
|
|
|
}
|
|
|
|
|
2023-11-21 16:41:55 +00:00
|
|
|
u = db.Users.ByEmail.Get(tx, &User{Email: "a@b.com"})
|
|
|
|
if u == nil {
|
2023-10-16 08:50:19 +00:00
|
|
|
return errs.NotFound
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
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 {
|
2023-11-21 16:41:55 +00:00
|
|
|
if u := db.Users.ByID.Get(tx, &User{ID: 2}); u != nil {
|
2023-10-13 09:43:27 +00:00
|
|
|
return errors.New("Found (id)")
|
|
|
|
}
|
|
|
|
|
2023-11-21 16:41:55 +00:00
|
|
|
if u := db.Users.ByEmail.Get(tx, &User{Email: "x@b.com"}); u != nil {
|
2023-10-13 09:43:27 +00:00
|
|
|
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"}
|
2023-12-24 19:41:43 +00:00
|
|
|
for u := range db.Users.ByName.AscendAfter(tx, &pivot) {
|
2023-10-13 09:43:27 +00:00
|
|
|
u.Name += "Mod"
|
|
|
|
if err = db.Users.Update(tx, u); err != nil {
|
2023-12-24 19:41:43 +00:00
|
|
|
return err
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
if first {
|
|
|
|
first = false
|
2023-12-24 19:41:43 +00:00
|
|
|
continue
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
|
2023-11-21 16:41:55 +00:00
|
|
|
prev := db.Users.ByID.Get(tx, &User{ID: u.ID - 1})
|
|
|
|
if prev == nil {
|
2023-12-24 19:41:43 +00:00
|
|
|
return errors.New("Previous user not found")
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasSuffix(prev.Name, "Mod") {
|
2023-12-24 19:41:43 +00:00
|
|
|
return errors.New("Incorrect user name: " + prev.Name)
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
2023-12-24 19:41:43 +00:00
|
|
|
}
|
2023-10-13 09:43:27 +00:00
|
|
|
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
|
2023-12-24 19:41:43 +00:00
|
|
|
for u := range db.Users.ByName.DescendAfter(tx, &User{Name: "User5Mod"}) {
|
2023-10-13 09:43:27 +00:00
|
|
|
u.Name = strings.TrimSuffix(u.Name, "Mod")
|
|
|
|
if err = db.Users.Update(tx, u); err != nil {
|
2023-12-24 19:41:43 +00:00
|
|
|
return err
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
if first {
|
|
|
|
first = false
|
2023-12-24 19:41:43 +00:00
|
|
|
continue
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
|
2023-11-21 16:41:55 +00:00
|
|
|
prev := db.Users.ByID.Get(tx, &User{ID: u.ID + 1})
|
|
|
|
if prev == nil {
|
2023-12-24 19:41:43 +00:00
|
|
|
return errors.New("Previous user not found")
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasSuffix(prev.Name, "Mod") {
|
2023-12-24 19:41:43 +00:00
|
|
|
return errors.New("Incorrect user name: " + prev.Name)
|
2023-10-13 09:43:27 +00:00
|
|
|
}
|
2023-12-24 19:41:43 +00:00
|
|
|
}
|
2023-10-13 09:43:27 +00:00
|
|
|
|
|
|
|
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"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}}
|