Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
475 lines
13 KiB
Go
475 lines
13 KiB
Go
package tests
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cortezaproject/corteza/server/pkg/filter"
|
|
"github.com/cortezaproject/corteza/server/pkg/id"
|
|
"github.com/cortezaproject/corteza/server/pkg/rand"
|
|
"github.com/cortezaproject/corteza/server/store"
|
|
"github.com/cortezaproject/corteza/server/system/types"
|
|
_ "github.com/joho/godotenv/autoload"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func testUsers(t *testing.T, s store.Users) {
|
|
var (
|
|
dummyID uint64 = 0
|
|
ctx = context.Background()
|
|
|
|
makeNew = func(nn ...string) *types.User {
|
|
// minimum data set for new user
|
|
name := strings.Join(nn, "")
|
|
dummyID = dummyID + 1
|
|
return &types.User{
|
|
ID: dummyID,
|
|
CreatedAt: time.Now(),
|
|
Email: "user-crud+" + name + "@crust.test",
|
|
Username: "username_" + name,
|
|
Handle: "handle_" + name,
|
|
}
|
|
}
|
|
|
|
truncAndCreate = func(t *testing.T) (*require.Assertions, *types.User) {
|
|
req := require.New(t)
|
|
req.NoError(s.TruncateUsers(ctx))
|
|
user := makeNew()
|
|
req.NoError(s.CreateUser(ctx, user))
|
|
return req, user
|
|
}
|
|
|
|
truncAndFill = func(t *testing.T, l int) (*require.Assertions, types.UserSet) {
|
|
req := require.New(t)
|
|
req.NoError(s.TruncateUsers(ctx))
|
|
|
|
set := make([]*types.User, l)
|
|
|
|
for i := 0; i < l; i++ {
|
|
set[i] = makeNew(string(rand.Bytes(10)))
|
|
}
|
|
|
|
req.NoError(s.CreateUser(ctx, set...))
|
|
return req, set
|
|
}
|
|
|
|
stringifySetRange = func(set types.UserSet) string {
|
|
if len(set) == 0 {
|
|
return ""
|
|
}
|
|
|
|
var out = set[0].Handle[7:]
|
|
|
|
if len(set) > 1 {
|
|
out += ".." + set[len(set)-1].Handle[7:]
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
// in case we need some quick old-school debugging
|
|
//dbg := func(uu ...*types.User) {
|
|
// for i, u := range uu {
|
|
// fmt.Printf(" => #%2d %s\n", i+1, u.Handle)
|
|
// }
|
|
//}
|
|
)
|
|
|
|
t.Run("create", func(t *testing.T) {
|
|
req := require.New(t)
|
|
req.NoError(s.TruncateUsers(ctx))
|
|
req.NoError(store.CreateUser(ctx, s, makeNew()))
|
|
})
|
|
|
|
t.Run("create with duplicate email", func(t *testing.T) {
|
|
req := require.New(t)
|
|
req.NoError(s.TruncateUsers(ctx))
|
|
|
|
req.NoError(s.CreateUser(ctx, makeNew("a")))
|
|
req.EqualError(s.CreateUser(ctx, makeNew("a")), store.ErrNotUnique.Error())
|
|
})
|
|
|
|
t.Run("create with duplicate ID", func(t *testing.T) {
|
|
req := require.New(t)
|
|
req.NoError(s.TruncateUsers(ctx))
|
|
user := makeNew("a")
|
|
req.NoError(s.CreateUser(ctx, user))
|
|
req.EqualError(s.CreateUser(ctx, user), store.ErrNotUnique.Error())
|
|
})
|
|
|
|
t.Run("lookup by ID", func(t *testing.T) {
|
|
req, user := truncAndCreate(t)
|
|
fetched, err := store.LookupUserByID(ctx, s, user.ID)
|
|
req.NoError(err)
|
|
req.Equal(user.Email, fetched.Email)
|
|
req.Equal(user.Username, fetched.Username)
|
|
req.Equal(user.Handle, fetched.Handle)
|
|
req.Equal(user.ID, fetched.ID)
|
|
req.NotNil(fetched.CreatedAt)
|
|
req.Nil(fetched.UpdatedAt)
|
|
req.Nil(fetched.DeletedAt)
|
|
req.Nil(fetched.SuspendedAt)
|
|
})
|
|
|
|
t.Run("update", func(t *testing.T) {
|
|
req, user := truncAndCreate(t)
|
|
req.NoError(s.UpdateUser(ctx, user))
|
|
})
|
|
|
|
t.Run("lookup by email", func(t *testing.T) {
|
|
req, user := truncAndCreate(t)
|
|
fetched, err := store.LookupUserByEmail(ctx, s, user.Email)
|
|
req.NoError(err)
|
|
req.Equal(user.Email, fetched.Email)
|
|
})
|
|
|
|
t.Run("lookup by handle", func(t *testing.T) {
|
|
req, user := truncAndCreate(t)
|
|
fetched, err := store.LookupUserByHandle(ctx, s, user.Handle)
|
|
req.NoError(err)
|
|
req.Equal(user.ID, fetched.ID)
|
|
})
|
|
|
|
t.Run("lookup by nonexisting handle", func(t *testing.T) {
|
|
req, _ := truncAndCreate(t)
|
|
fetched, err := store.LookupUserByHandle(ctx, s, "no such handle")
|
|
req.EqualError(err, "not found")
|
|
req.Nil(fetched)
|
|
})
|
|
|
|
t.Run("lookup by username", func(t *testing.T) {
|
|
req, user := truncAndCreate(t)
|
|
fetched, err := store.LookupUserByUsername(ctx, s, user.Username)
|
|
req.NoError(err)
|
|
req.Equal(user.ID, fetched.ID)
|
|
})
|
|
|
|
t.Run("create and update deleted with existing email", func(t *testing.T) {
|
|
req, user := truncAndCreate(t)
|
|
deletedUser := makeNew("copy")
|
|
deletedUser.DeletedAt = now()
|
|
deletedUser.Email = user.Email
|
|
req.NoError(store.CreateUser(ctx, s, deletedUser))
|
|
req.NoError(store.UpdateUser(ctx, s, deletedUser))
|
|
})
|
|
|
|
t.Run("search", func(t *testing.T) {
|
|
t.Run("by ID", func(t *testing.T) {
|
|
req, prefill := truncAndFill(t, 5)
|
|
set, f, err := store.SearchUsers(ctx, s, types.UserFilter{UserID: id.Strings(prefill[0].ID)})
|
|
req.NoError(err)
|
|
req.Equal(id.Strings(prefill[0].ID), f.UserID)
|
|
req.Len(set, 1)
|
|
//req.Equal(set[0].ID, user.ID)
|
|
})
|
|
|
|
t.Run("by email", func(t *testing.T) {
|
|
req, prefill := truncAndFill(t, 5)
|
|
set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Email: prefill[0].Email})
|
|
req.NoError(err)
|
|
req.Len(set, 1)
|
|
})
|
|
|
|
t.Run("by username", func(t *testing.T) {
|
|
req, prefill := truncAndFill(t, 5)
|
|
set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Username: prefill[0].Username})
|
|
req.NoError(err)
|
|
req.Len(set, 1)
|
|
})
|
|
|
|
t.Run("by query", func(t *testing.T) {
|
|
req, prefill := truncAndFill(t, 5)
|
|
set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Query: prefill[0].Handle})
|
|
req.NoError(err)
|
|
req.Len(set, 1)
|
|
})
|
|
|
|
t.Run("by username", func(t *testing.T) {
|
|
req, _ := truncAndFill(t, 5)
|
|
set, _, err := store.SearchUsers(ctx, s, types.UserFilter{Username: "no such username"})
|
|
req.NoError(err)
|
|
req.Len(set, 0)
|
|
})
|
|
|
|
t.Run("with check", func(t *testing.T) {
|
|
req, prefill := truncAndFill(t, 5)
|
|
set, _, err := store.SearchUsers(ctx, s, types.UserFilter{
|
|
Check: func(user *types.User) (bool, error) {
|
|
// simple check that matches with the first user from prefill
|
|
return user.ID == prefill[0].ID, nil
|
|
},
|
|
})
|
|
req.NoError(err)
|
|
req.Len(set, 1)
|
|
req.Equal(prefill[0].ID, set[0].ID)
|
|
})
|
|
})
|
|
|
|
t.Run("paging and sorting", func(t *testing.T) {
|
|
require.NoError(t, s.TruncateUsers(ctx))
|
|
require.NoError(t, s.CreateUser(ctx,
|
|
makeNew("01"),
|
|
makeNew("02"),
|
|
makeNew("03"),
|
|
makeNew("04"),
|
|
makeNew("05"),
|
|
makeNew("06"),
|
|
makeNew("07"),
|
|
makeNew("08"),
|
|
makeNew("09"),
|
|
makeNew("10"),
|
|
))
|
|
|
|
{
|
|
tcc := []struct {
|
|
sort string
|
|
rval string
|
|
}{
|
|
{"email", "01..10"},
|
|
{"email DESC", "10..01"},
|
|
{"id, email", "01..10"},
|
|
{"id DESC, email DESC", "10..01"},
|
|
{"id DESC, email", "10..01"},
|
|
{"id, email DESC", "01..10"},
|
|
}
|
|
|
|
for _, tc := range tcc {
|
|
t.Run(tc.sort, func(t *testing.T) {
|
|
var (
|
|
req = require.New(t)
|
|
|
|
f = types.UserFilter{}
|
|
set types.UserSet
|
|
err error
|
|
)
|
|
|
|
f.Sort.Set(tc.sort)
|
|
set, f, err = store.SearchUsers(ctx, s, f)
|
|
req.NoError(err)
|
|
req.Equal(tc.rval, stringifySetRange(set))
|
|
req.Nil(f.PrevPage)
|
|
req.Nil(f.NextPage)
|
|
})
|
|
}
|
|
}
|
|
|
|
{
|
|
prevCur := 1
|
|
nextCur := 2
|
|
bothCur := prevCur | nextCur
|
|
|
|
// tests if cursors are properly set/unset by inspecting req. bits
|
|
testCursors := func(req *require.Assertions, b int, f types.UserFilter) {
|
|
if b&prevCur == 0 {
|
|
req.Nil(f.PrevPage)
|
|
} else {
|
|
req.NotNil(f.PrevPage)
|
|
}
|
|
|
|
if b&nextCur == 0 {
|
|
req.Nil(f.NextPage)
|
|
} else {
|
|
req.NotNil(f.NextPage)
|
|
}
|
|
}
|
|
|
|
tcc := []struct {
|
|
// how data is sorted
|
|
sort string
|
|
|
|
// expected data
|
|
rval []string
|
|
|
|
// how cursors should be set when moving forward/backward
|
|
curr []int
|
|
}{
|
|
{
|
|
"id",
|
|
[]string{"01..03", "04..06", "07..09", "10"},
|
|
[]int{nextCur, bothCur, bothCur, prevCur},
|
|
},
|
|
{
|
|
"id DESC",
|
|
[]string{"10..08", "07..05", "04..02", "01"},
|
|
[]int{nextCur, bothCur, bothCur, prevCur},
|
|
},
|
|
{
|
|
"email",
|
|
[]string{"01..03", "04..06", "07..09", "10"},
|
|
[]int{nextCur, bothCur, bothCur, prevCur},
|
|
},
|
|
{
|
|
"email DESC",
|
|
[]string{"10..08", "07..05", "04..02", "01"},
|
|
[]int{nextCur, bothCur, bothCur, prevCur},
|
|
},
|
|
{
|
|
"id DESC, email",
|
|
[]string{"10..08", "07..05", "04..02", "01"},
|
|
[]int{nextCur, bothCur, bothCur, prevCur},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tcc {
|
|
t.Run("crawling: "+tc.sort+"x", func(t *testing.T) {
|
|
|
|
var (
|
|
req = require.New(t)
|
|
|
|
f = types.UserFilter{}
|
|
set types.UserSet
|
|
err error
|
|
)
|
|
|
|
f.Sort.Set(tc.sort)
|
|
f.Limit = 3 // 3, 3, 3, 1
|
|
|
|
t.Log("going from page 1 to 4")
|
|
for p := 0; p < 4; p++ {
|
|
t.Logf("0123 next page cursor: %35s", f.PageCursor)
|
|
set, f, err = store.SearchUsers(ctx, s, f)
|
|
req.NoError(err)
|
|
req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+","))
|
|
|
|
req.Equal(tc.rval[p], stringifySetRange(set))
|
|
|
|
testCursors(req, tc.curr[p], f)
|
|
|
|
// advance to next page
|
|
f.PageCursor = f.NextPage
|
|
}
|
|
|
|
t.Log("and back from 3 to 1")
|
|
f.PageCursor = f.PrevPage
|
|
for p := 2; p >= 0; p-- {
|
|
f.Sort = nil
|
|
t.Logf("_210 next page cursor: %35s", f.PageCursor)
|
|
set, f, err = store.SearchUsers(ctx, s, f)
|
|
t.Log(stringifySetRange(set))
|
|
req.NoError(err)
|
|
//req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+","), "search should not return altered sort on filter")
|
|
|
|
req.Equal(tc.rval[p], stringifySetRange(set))
|
|
testCursors(req, tc.curr[p], f)
|
|
|
|
// reverse to previous page
|
|
f.PageCursor = f.PrevPage
|
|
}
|
|
|
|
t.Log("and again all the way to 4th page")
|
|
f.PageCursor = f.NextPage
|
|
for p := 1; p < 4; p++ {
|
|
set, f, err = store.SearchUsers(ctx, s, f)
|
|
req.NoError(err)
|
|
req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+","))
|
|
|
|
req.Equal(tc.rval[p], stringifySetRange(set))
|
|
testCursors(req, tc.curr[p], f)
|
|
|
|
// advance to next page
|
|
f.PageCursor = f.NextPage
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
t.Run("with incompatible sort", func(t *testing.T) {
|
|
req := require.New(t)
|
|
req.NoError(s.TruncateUsers(ctx))
|
|
|
|
set := []*types.User{
|
|
makeNew("01"),
|
|
makeNew("02"),
|
|
makeNew("03"),
|
|
makeNew("04"),
|
|
makeNew("05"),
|
|
}
|
|
|
|
req.NoError(s.CreateUser(ctx, set...))
|
|
f := types.UserFilter{}
|
|
f.Sort = filter.SortExprSet{&filter.SortExpr{Column: "email", Descending: true}, &filter.SortExpr{Column: "handle", Descending: true}}
|
|
|
|
f.Limit = uint(len(set))
|
|
set, f, err := store.SearchUsers(ctx, s, f)
|
|
req.Len(set, int(f.Limit))
|
|
req.NoError(err)
|
|
|
|
f.Limit = 1
|
|
set, f, err = store.SearchUsers(ctx, s, f)
|
|
req.NoError(err)
|
|
|
|
// go to next page with different sorting
|
|
f.PageCursor = f.NextPage
|
|
f.Sort = filter.SortExprSet{&filter.SortExpr{Column: "username", Descending: false}}
|
|
set, f, err = store.SearchUsers(ctx, s, f)
|
|
req.EqualError(err, filter.ErrIncompatibleSort.Error())
|
|
})
|
|
})
|
|
|
|
t.Run("count", func(t *testing.T) {
|
|
var (
|
|
req = require.New(t)
|
|
|
|
f = types.UserFilter{}
|
|
c1, c2 uint
|
|
err error
|
|
user = &types.User{ID: id.Next(), CreatedAt: time.Now(), Email: fmt.Sprintf("user-crud+%s@crust.test", time.Now().String())}
|
|
)
|
|
|
|
c1, err = store.CountUsers(ctx, s, f)
|
|
req.NoError(err)
|
|
|
|
req.NoError(s.CreateUser(ctx, user))
|
|
|
|
c2, err = store.CountUsers(ctx, s, f)
|
|
req.NoError(err)
|
|
req.Equal(c1+1, c2)
|
|
|
|
req.NoError(s.DeleteUserByID(ctx, user.ID))
|
|
|
|
c2, err = store.CountUsers(ctx, s, f)
|
|
req.NoError(err)
|
|
req.Equal(c1, c2)
|
|
})
|
|
|
|
t.Run("metrics", func(t *testing.T) {
|
|
var (
|
|
req = require.New(t)
|
|
|
|
oct, _ = time.Parse(time.RFC3339, "2020-10-02T10:01:10Z")
|
|
nov, _ = time.Parse(time.RFC3339, "2020-11-02T20:02:10Z")
|
|
|
|
octu = uint(oct.Truncate(time.Hour * 24).Unix())
|
|
novu = uint(nov.Truncate(time.Hour * 24).Unix())
|
|
|
|
e = &types.UserMetrics{
|
|
Total: 8,
|
|
Valid: 2,
|
|
Deleted: 3,
|
|
Suspended: 3,
|
|
DailyCreated: []uint{octu, 7, novu, 1},
|
|
DailyUpdated: []uint{octu, 2},
|
|
DailySuspended: []uint{octu, 2, novu, 1},
|
|
DailyDeleted: []uint{novu, 3},
|
|
}
|
|
)
|
|
|
|
req.NoError(s.TruncateUsers(ctx))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: oct, Email: "user-metrics-1@crust.test", UpdatedAt: &oct}))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: oct, Email: "user-metrics-2@crust.test", UpdatedAt: &oct}))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: oct, Email: "user-metrics-3@crust.test", SuspendedAt: &oct}))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: oct, Email: "user-metrics-4@crust.test", SuspendedAt: &oct}))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: oct, Email: "user-metrics-5@crust.test", SuspendedAt: &nov}))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: oct, Email: "user-metrics-6@crust.test", DeletedAt: &nov}))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: oct, Email: "user-metrics-7@crust.test", DeletedAt: &nov}))
|
|
req.NoError(s.CreateUser(ctx, &types.User{ID: id.Next(), CreatedAt: nov, Email: "user-metrics-8@crust.test", DeletedAt: &nov}))
|
|
|
|
m, err := store.UserMetrics(ctx, s)
|
|
req.NoError(err)
|
|
req.Equal(e, m)
|
|
})
|
|
}
|