tests(db): add additional tests & comments
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
9809a09d2e
commit
3a633235ea
@ -869,10 +869,10 @@ func (api *API) updateUser(user string, rawPassword *string, isAdmin *bool) erro
|
|||||||
updateParams.Admin = user.Admin
|
updateParams.Admin = user.Admin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Admins
|
// Check Admins - Disallow Demotion
|
||||||
if isLast, err := api.isLastAdmin(user); err != nil {
|
if isLast, err := api.isLastAdmin(user); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if isLast {
|
} else if isLast && !updateParams.Admin {
|
||||||
return fmt.Errorf("unable to demote %s - last admin", user)
|
return fmt.Errorf("unable to demote %s - last admin", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
api/utils.go
16
api/utils.go
@ -13,6 +13,7 @@ import (
|
|||||||
"reichard.io/antholume/metadata"
|
"reichard.io/antholume/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// getTimeZones returns a string slice of IANA timezones.
|
||||||
func getTimeZones() []string {
|
func getTimeZones() []string {
|
||||||
return []string{
|
return []string{
|
||||||
"Africa/Cairo",
|
"Africa/Cairo",
|
||||||
@ -52,6 +53,8 @@ func getTimeZones() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// niceSeconds takes in an int (in seconds) and returns a string readable
|
||||||
|
// representation. For example 1928371 -> "22d 7h 39m 31s".
|
||||||
func niceSeconds(input int64) (result string) {
|
func niceSeconds(input int64) (result string) {
|
||||||
if input == 0 {
|
if input == 0 {
|
||||||
return "N/A"
|
return "N/A"
|
||||||
@ -80,6 +83,8 @@ func niceSeconds(input int64) (result string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// niceNumbers takes in an int and returns a string representation. For example
|
||||||
|
// 19823 -> "19.8k".
|
||||||
func niceNumbers(input int64) string {
|
func niceNumbers(input int64) string {
|
||||||
if input == 0 {
|
if input == 0 {
|
||||||
return "0"
|
return "0"
|
||||||
@ -98,7 +103,8 @@ func niceNumbers(input int64) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert Database Array -> Int64 Array
|
// getSVGGraphData builds SVGGraphData from the provided stats, width and height.
|
||||||
|
// It is used exclusively in templates to generate the daily read stats graph.
|
||||||
func getSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, svgHeight int) graph.SVGGraphData {
|
func getSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, svgHeight int) graph.SVGGraphData {
|
||||||
var intData []int64
|
var intData []int64
|
||||||
for _, item := range inputData {
|
for _, item := range inputData {
|
||||||
@ -108,6 +114,8 @@ func getSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, sv
|
|||||||
return graph.GetSVGGraphData(intData, svgWidth, svgHeight)
|
return graph.GetSVGGraphData(intData, svgWidth, svgHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dict returns a map[string]any dict. Each pair of two is a key & value
|
||||||
|
// respectively. It's primarily utilized in templates.
|
||||||
func dict(values ...any) (map[string]any, error) {
|
func dict(values ...any) (map[string]any, error) {
|
||||||
if len(values)%2 != 0 {
|
if len(values)%2 != 0 {
|
||||||
return nil, errors.New("invalid dict call")
|
return nil, errors.New("invalid dict call")
|
||||||
@ -123,6 +131,8 @@ func dict(values ...any) (map[string]any, error) {
|
|||||||
return dict, nil
|
return dict, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fields returns a map[string]any of the provided struct. It's primarily
|
||||||
|
// utilized in templates.
|
||||||
func fields(value any) (map[string]any, error) {
|
func fields(value any) (map[string]any, error) {
|
||||||
v := reflect.Indirect(reflect.ValueOf(value))
|
v := reflect.Indirect(reflect.ValueOf(value))
|
||||||
if v.Kind() != reflect.Struct {
|
if v.Kind() != reflect.Struct {
|
||||||
@ -137,10 +147,13 @@ func fields(value any) (map[string]any, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slice returns a slice of the provided arguments. It's primarily utilized in
|
||||||
|
// templates.
|
||||||
func slice(elements ...any) []any {
|
func slice(elements ...any) []any {
|
||||||
return elements
|
return elements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deriveBaseFileName builds the base filename for a given MetadataInfo object.
|
||||||
func deriveBaseFileName(metadataInfo *metadata.MetadataInfo) string {
|
func deriveBaseFileName(metadataInfo *metadata.MetadataInfo) string {
|
||||||
// Derive New FileName
|
// Derive New FileName
|
||||||
var newFileName string
|
var newFileName string
|
||||||
@ -160,6 +173,7 @@ func deriveBaseFileName(metadataInfo *metadata.MetadataInfo) string {
|
|||||||
return "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, *metadataInfo.PartialMD5, metadataInfo.Type))
|
return "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, *metadataInfo.PartialMD5, metadataInfo.Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// importStatusPriority returns the order priority for import status in the UI.
|
||||||
func importStatusPriority(status importStatus) int {
|
func importStatusPriority(status importStatus) int {
|
||||||
switch status {
|
switch status {
|
||||||
case importFailed:
|
case importFailed:
|
||||||
|
@ -43,7 +43,7 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an initialized manager
|
// NewMgr Returns an initialized manager
|
||||||
func NewMgr(c *config.Config) *DBManager {
|
func NewMgr(c *config.Config) *DBManager {
|
||||||
// Create Manager
|
// Create Manager
|
||||||
dbm := &DBManager{
|
dbm := &DBManager{
|
||||||
@ -58,7 +58,7 @@ func NewMgr(c *config.Config) *DBManager {
|
|||||||
return dbm
|
return dbm
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init manager
|
// init loads the DB manager
|
||||||
func (dbm *DBManager) init() error {
|
func (dbm *DBManager) init() error {
|
||||||
// Build DB Location
|
// Build DB Location
|
||||||
var dbLocation string
|
var dbLocation string
|
||||||
@ -125,7 +125,7 @@ func (dbm *DBManager) init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload manager (close DB & reinit)
|
// Reload closes the DB & reinits
|
||||||
func (dbm *DBManager) Reload() error {
|
func (dbm *DBManager) Reload() error {
|
||||||
// Close handle
|
// Close handle
|
||||||
err := dbm.DB.Close()
|
err := dbm.DB.Close()
|
||||||
@ -141,6 +141,7 @@ func (dbm *DBManager) Reload() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CacheTempTables clears existing statistics and recalculates
|
||||||
func (dbm *DBManager) CacheTempTables() error {
|
func (dbm *DBManager) CacheTempTables() error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
user_streaks_sql := `
|
user_streaks_sql := `
|
||||||
@ -165,6 +166,8 @@ func (dbm *DBManager) CacheTempTables() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateSettings ensures that we're enforcing foreign keys and enable journal
|
||||||
|
// mode.
|
||||||
func (dbm *DBManager) updateSettings() error {
|
func (dbm *DBManager) updateSettings() error {
|
||||||
// Set SQLite PRAGMA Settings
|
// Set SQLite PRAGMA Settings
|
||||||
pragmaQuery := `
|
pragmaQuery := `
|
||||||
@ -188,6 +191,7 @@ func (dbm *DBManager) updateSettings() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// performMigrations runs all migrations
|
||||||
func (dbm *DBManager) performMigrations(isNew bool) error {
|
func (dbm *DBManager) performMigrations(isNew bool) error {
|
||||||
// Create context
|
// Create context
|
||||||
ctx := context.WithValue(context.Background(), "isNew", isNew) // nolint
|
ctx := context.WithValue(context.Background(), "isNew", isNew) // nolint
|
||||||
@ -204,7 +208,7 @@ func (dbm *DBManager) performMigrations(isNew bool) error {
|
|||||||
return goose.UpContext(ctx, dbm.DB, "migrations")
|
return goose.UpContext(ctx, dbm.DB, "migrations")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determines whether the database is empty
|
// isEmpty determines whether the database is empty
|
||||||
func isEmpty(db *sql.DB) (bool, error) {
|
func isEmpty(db *sql.DB) (bool, error) {
|
||||||
var tableCount int
|
var tableCount int
|
||||||
err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table';").Scan(&tableCount)
|
err := db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table';").Scan(&tableCount)
|
||||||
@ -214,7 +218,7 @@ func isEmpty(db *sql.DB) (bool, error) {
|
|||||||
return tableCount == 0, nil
|
return tableCount == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOCAL_TIME custom SQL function
|
// localTime is a custom SQL function that is registered as LOCAL_TIME in the init function
|
||||||
func localTime(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
|
func localTime(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
|
||||||
timeStr, ok := args[0].(string)
|
timeStr, ok := args[0].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -5,164 +5,191 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"reichard.io/antholume/config"
|
"reichard.io/antholume/config"
|
||||||
"reichard.io/antholume/utils"
|
"reichard.io/antholume/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type databaseTest struct {
|
var (
|
||||||
*testing.T
|
userID string = "testUser"
|
||||||
|
userPass string = "testPass"
|
||||||
|
deviceID string = "testDevice"
|
||||||
|
deviceName string = "testDeviceName"
|
||||||
|
documentID string = "testDocument"
|
||||||
|
documentTitle string = "testTitle"
|
||||||
|
documentAuthor string = "testAuthor"
|
||||||
|
documentWords int64 = 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
type DatabaseTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
dbm *DBManager
|
dbm *DBManager
|
||||||
}
|
}
|
||||||
|
|
||||||
var userID string = "testUser"
|
func TestDatabase(t *testing.T) {
|
||||||
var userPass string = "testPass"
|
suite.Run(t, new(DatabaseTestSuite))
|
||||||
var deviceID string = "testDevice"
|
}
|
||||||
var deviceName string = "testDeviceName"
|
|
||||||
var documentID string = "testDocument"
|
|
||||||
var documentTitle string = "testTitle"
|
|
||||||
var documentAuthor string = "testAuthor"
|
|
||||||
|
|
||||||
func TestNewMgr(t *testing.T) {
|
// PROGRESS - TODO:
|
||||||
|
// - (q *Queries) GetProgress
|
||||||
|
// - (q *Queries) UpdateProgress
|
||||||
|
|
||||||
|
func (suite *DatabaseTestSuite) SetupTest() {
|
||||||
cfg := config.Config{
|
cfg := config.Config{
|
||||||
DBType: "memory",
|
DBType: "memory",
|
||||||
}
|
}
|
||||||
|
|
||||||
dbm := NewMgr(&cfg)
|
suite.dbm = NewMgr(&cfg)
|
||||||
assert.NotNil(t, dbm, "should not have nil dbm")
|
|
||||||
|
|
||||||
t.Run("Database", func(t *testing.T) {
|
// Create User
|
||||||
dt := databaseTest{t, dbm}
|
rawAuthHash, _ := utils.GenerateToken(64)
|
||||||
dt.TestUser()
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
dt.TestDocument()
|
_, err := suite.dbm.Queries.CreateUser(suite.dbm.Ctx, CreateUserParams{
|
||||||
dt.TestDevice()
|
ID: userID,
|
||||||
dt.TestActivity()
|
Pass: &userPass,
|
||||||
dt.TestDailyReadStats()
|
AuthHash: &authHash,
|
||||||
})
|
})
|
||||||
}
|
suite.NoError(err)
|
||||||
|
|
||||||
func (dt *databaseTest) TestUser() {
|
// Create Document
|
||||||
dt.Run("User", func(t *testing.T) {
|
_, err = suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
||||||
// Generate Auth Hash
|
ID: documentID,
|
||||||
rawAuthHash, err := utils.GenerateToken(64)
|
Title: &documentTitle,
|
||||||
assert.Nil(t, err, "should have nil err")
|
Author: &documentAuthor,
|
||||||
|
Words: &documentWords,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
authHash := fmt.Sprintf("%x", rawAuthHash)
|
// Create Device
|
||||||
changed, err := dt.dbm.Queries.CreateUser(dt.dbm.Ctx, CreateUserParams{
|
_, err = suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
|
||||||
ID: userID,
|
ID: deviceID,
|
||||||
Pass: &userPass,
|
UserID: userID,
|
||||||
AuthHash: &authHash,
|
DeviceName: deviceName,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Create Activity
|
||||||
|
end := time.Now()
|
||||||
|
start := end.AddDate(0, 0, -9)
|
||||||
|
var counter int64 = 0
|
||||||
|
|
||||||
|
for d := start; d.After(end) == false; d = d.AddDate(0, 0, 1) {
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
// Add Item
|
||||||
|
activity, err := suite.dbm.Queries.AddActivity(suite.dbm.Ctx, AddActivityParams{
|
||||||
|
DocumentID: documentID,
|
||||||
|
DeviceID: deviceID,
|
||||||
|
UserID: userID,
|
||||||
|
StartTime: d.UTC().Format(time.RFC3339),
|
||||||
|
Duration: 60,
|
||||||
|
StartPercentage: float64(counter) / 100.0,
|
||||||
|
EndPercentage: float64(counter+1) / 100.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Nil(t, err, "should have nil err")
|
suite.Nil(err, fmt.Sprintf("[%d] should have nil err for add activity", counter))
|
||||||
assert.Equal(t, int64(1), changed)
|
suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
||||||
|
}
|
||||||
|
|
||||||
user, err := dt.dbm.Queries.GetUser(dt.dbm.Ctx, userID)
|
// Initiate Cache
|
||||||
|
err = suite.dbm.CacheTempTables()
|
||||||
assert.Nil(t, err, "should have nil err")
|
suite.NoError(err)
|
||||||
assert.Equal(t, userPass, *user.Pass)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dt *databaseTest) TestDocument() {
|
// DOCUMENT - TODO:
|
||||||
dt.Run("Document", func(t *testing.T) {
|
// - (q *Queries) DeleteDocument
|
||||||
doc, err := dt.dbm.Queries.UpsertDocument(dt.dbm.Ctx, UpsertDocumentParams{
|
// - (q *Queries) GetDeletedDocuments
|
||||||
ID: documentID,
|
// - (q *Queries) GetDocument
|
||||||
Title: &documentTitle,
|
// - (q *Queries) GetDocumentProgress
|
||||||
Author: &documentAuthor,
|
// - (q *Queries) GetDocumentWithStats
|
||||||
})
|
// - (q *Queries) GetDocuments
|
||||||
|
// - (q *Queries) GetDocumentsSize
|
||||||
|
// - (q *Queries) GetDocumentsWithStats
|
||||||
|
// - (q *Queries) GetMissingDocuments
|
||||||
|
// - (q *Queries) GetWantedDocuments
|
||||||
|
// - (q *Queries) UpsertDocument
|
||||||
|
func (suite *DatabaseTestSuite) TestDocument() {
|
||||||
|
testDocID := "docid1"
|
||||||
|
|
||||||
assert.Nil(t, err, "should have nil err")
|
doc, err := suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
||||||
assert.Equal(t, documentID, doc.ID, "should have document id")
|
ID: testDocID,
|
||||||
assert.Equal(t, documentTitle, *doc.Title, "should have document title")
|
Title: &documentTitle,
|
||||||
assert.Equal(t, documentAuthor, *doc.Author, "should have document author")
|
Author: &documentAuthor,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Equal(testDocID, doc.ID, "should have document id")
|
||||||
|
suite.Equal(documentTitle, *doc.Title, "should have document title")
|
||||||
|
suite.Equal(documentAuthor, *doc.Author, "should have document author")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dt *databaseTest) TestDevice() {
|
// DEVICES - TODO:
|
||||||
dt.Run("Device", func(t *testing.T) {
|
// - (q *Queries) GetDevice
|
||||||
device, err := dt.dbm.Queries.UpsertDevice(dt.dbm.Ctx, UpsertDeviceParams{
|
// - (q *Queries) GetDevices
|
||||||
ID: deviceID,
|
// - (q *Queries) UpsertDevice
|
||||||
UserID: userID,
|
func (suite *DatabaseTestSuite) TestDevice() {
|
||||||
DeviceName: deviceName,
|
testDevice := "dev123"
|
||||||
})
|
device, err := suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
|
||||||
|
ID: testDevice,
|
||||||
assert.Nil(t, err, "should have nil err")
|
UserID: userID,
|
||||||
assert.Equal(t, deviceID, device.ID, "should have device id")
|
DeviceName: deviceName,
|
||||||
assert.Equal(t, userID, device.UserID, "should have user id")
|
|
||||||
assert.Equal(t, deviceName, device.DeviceName, "should have device name")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Equal(testDevice, device.ID, "should have device id")
|
||||||
|
suite.Equal(userID, device.UserID, "should have user id")
|
||||||
|
suite.Equal(deviceName, device.DeviceName, "should have device name")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dt *databaseTest) TestActivity() {
|
// ACTIVITY - TODO:
|
||||||
dt.Run("Progress", func(t *testing.T) {
|
// - (q *Queries) AddActivity
|
||||||
// 10 Activities, 10 Days
|
// - (q *Queries) GetActivity
|
||||||
end := time.Now()
|
// - (q *Queries) GetLastActivity
|
||||||
start := end.AddDate(0, 0, -9)
|
func (suite *DatabaseTestSuite) TestActivity() {
|
||||||
var counter int64 = 0
|
// Validate Exists
|
||||||
|
existsRows, err := suite.dbm.Queries.GetActivity(suite.dbm.Ctx, GetActivityParams{
|
||||||
for d := start; d.After(end) == false; d = d.AddDate(0, 0, 1) {
|
UserID: userID,
|
||||||
counter += 1
|
Offset: 0,
|
||||||
|
Limit: 50,
|
||||||
// Add Item
|
|
||||||
activity, err := dt.dbm.Queries.AddActivity(dt.dbm.Ctx, AddActivityParams{
|
|
||||||
DocumentID: documentID,
|
|
||||||
DeviceID: deviceID,
|
|
||||||
UserID: userID,
|
|
||||||
StartTime: d.UTC().Format(time.RFC3339),
|
|
||||||
Duration: 60,
|
|
||||||
StartPercentage: float64(counter) / 100.0,
|
|
||||||
EndPercentage: float64(counter+1) / 100.0,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Nil(t, err, fmt.Sprintf("[%d] should have nil err for add activity", counter))
|
|
||||||
assert.Equal(t, counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initiate Cache
|
|
||||||
dt.dbm.CacheTempTables()
|
|
||||||
|
|
||||||
// Validate Exists
|
|
||||||
existsRows, err := dt.dbm.Queries.GetActivity(dt.dbm.Ctx, GetActivityParams{
|
|
||||||
UserID: userID,
|
|
||||||
Offset: 0,
|
|
||||||
Limit: 50,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Nil(t, err, "should have nil err for get activity")
|
|
||||||
assert.Len(t, existsRows, 10, "should have correct number of rows get activity")
|
|
||||||
|
|
||||||
// Validate Doesn't Exist
|
|
||||||
doesntExistsRows, err := dt.dbm.Queries.GetActivity(dt.dbm.Ctx, GetActivityParams{
|
|
||||||
UserID: userID,
|
|
||||||
DocumentID: "unknownDoc",
|
|
||||||
DocFilter: true,
|
|
||||||
Offset: 0,
|
|
||||||
Limit: 50,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Nil(t, err, "should have nil err for get activity")
|
|
||||||
assert.Len(t, doesntExistsRows, 0, "should have no rows")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
suite.Nil(err, "should have nil err for get activity")
|
||||||
|
suite.Len(existsRows, 10, "should have correct number of rows get activity")
|
||||||
|
|
||||||
|
// Validate Doesn't Exist
|
||||||
|
doesntExistsRows, err := suite.dbm.Queries.GetActivity(suite.dbm.Ctx, GetActivityParams{
|
||||||
|
UserID: userID,
|
||||||
|
DocumentID: "unknownDoc",
|
||||||
|
DocFilter: true,
|
||||||
|
Offset: 0,
|
||||||
|
Limit: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Nil(err, "should have nil err for get activity")
|
||||||
|
suite.Len(doesntExistsRows, 0, "should have no rows")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dt *databaseTest) TestDailyReadStats() {
|
// MISC - TODO:
|
||||||
dt.Run("DailyReadStats", func(t *testing.T) {
|
// - (q *Queries) AddMetadata
|
||||||
readStats, err := dt.dbm.Queries.GetDailyReadStats(dt.dbm.Ctx, userID)
|
// - (q *Queries) GetDailyReadStats
|
||||||
|
// - (q *Queries) GetDatabaseInfo
|
||||||
|
// - (q *Queries) UpdateSettings
|
||||||
|
func (suite *DatabaseTestSuite) TestGetDailyReadStats() {
|
||||||
|
readStats, err := suite.dbm.Queries.GetDailyReadStats(suite.dbm.Ctx, userID)
|
||||||
|
|
||||||
assert.Nil(t, err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
assert.Len(t, readStats, 30, "should have length of 30")
|
suite.Len(readStats, 30, "should have length of 30")
|
||||||
|
|
||||||
// Validate 1 Minute / Day - Last 10 Days
|
// Validate 1 Minute / Day - Last 10 Days
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
stat := readStats[i]
|
stat := readStats[i]
|
||||||
assert.Equal(t, int64(1), stat.MinutesRead, "should have one minute read")
|
suite.Equal(int64(1), stat.MinutesRead, "should have one minute read")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate 0 Minute / Day - Remaining 20 Days
|
// Validate 0 Minute / Day - Remaining 20 Days
|
||||||
for i := 10; i < 30; i++ {
|
for i := 10; i < 30; i++ {
|
||||||
stat := readStats[i]
|
stat := readStats[i]
|
||||||
assert.Equal(t, int64(0), stat.MinutesRead, "should have zero minutes read")
|
suite.Equal(int64(0), stat.MinutesRead, "should have zero minutes read")
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
204
database/users_test.go
Normal file
204
database/users_test.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"reichard.io/antholume/config"
|
||||||
|
"reichard.io/antholume/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testUserID string = "testUser"
|
||||||
|
testUserPass string = "testPass"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UsersTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
dbm *DBManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsers(t *testing.T) {
|
||||||
|
suite.Run(t, new(UsersTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) SetupTest() {
|
||||||
|
cfg := config.Config{
|
||||||
|
DBType: "memory",
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.dbm = NewMgr(&cfg)
|
||||||
|
|
||||||
|
// Create User
|
||||||
|
rawAuthHash, _ := utils.GenerateToken(64)
|
||||||
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
|
_, err := suite.dbm.Queries.CreateUser(suite.dbm.Ctx, CreateUserParams{
|
||||||
|
ID: testUserID,
|
||||||
|
Pass: &testUserPass,
|
||||||
|
AuthHash: &authHash,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Create Document
|
||||||
|
_, err = suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
||||||
|
ID: documentID,
|
||||||
|
Title: &documentTitle,
|
||||||
|
Author: &documentAuthor,
|
||||||
|
Words: &documentWords,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Create Device
|
||||||
|
_, err = suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
|
||||||
|
ID: deviceID,
|
||||||
|
UserID: testUserID,
|
||||||
|
DeviceName: deviceName,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) TestGetUser() {
|
||||||
|
user, err := suite.dbm.Queries.GetUser(suite.dbm.Ctx, testUserID)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Equal(testUserPass, *user.Pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) TestCreateUser() {
|
||||||
|
testUser := "user1"
|
||||||
|
testPass := "pass1"
|
||||||
|
|
||||||
|
// Generate Auth Hash
|
||||||
|
rawAuthHash, err := utils.GenerateToken(64)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
|
||||||
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
|
changed, err := suite.dbm.Queries.CreateUser(suite.dbm.Ctx, CreateUserParams{
|
||||||
|
ID: testUser,
|
||||||
|
Pass: &testPass,
|
||||||
|
AuthHash: &authHash,
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Equal(int64(1), changed)
|
||||||
|
|
||||||
|
user, err := suite.dbm.Queries.GetUser(suite.dbm.Ctx, testUser)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Equal(testPass, *user.Pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) TestDeleteUser() {
|
||||||
|
changed, err := suite.dbm.Queries.DeleteUser(suite.dbm.Ctx, testUserID)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Equal(int64(1), changed, "should have one changed row")
|
||||||
|
|
||||||
|
_, err = suite.dbm.Queries.GetUser(suite.dbm.Ctx, testUserID)
|
||||||
|
suite.ErrorIs(err, sql.ErrNoRows, "should have no rows error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) TestGetUsers() {
|
||||||
|
users, err := suite.dbm.Queries.GetUsers(suite.dbm.Ctx)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Len(users, 1, "should have single user")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) TestUpdateUser() {
|
||||||
|
newPassword := "newPass123"
|
||||||
|
user, err := suite.dbm.Queries.UpdateUser(suite.dbm.Ctx, UpdateUserParams{
|
||||||
|
UserID: testUserID,
|
||||||
|
Password: &newPassword,
|
||||||
|
})
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Equal(newPassword, *user.Pass, "should have new password")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) TestGetUserStatistics() {
|
||||||
|
err := suite.dbm.CacheTempTables()
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Ensure Zero Items
|
||||||
|
userStats, err := suite.dbm.Queries.GetUserStatistics(suite.dbm.Ctx)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Empty(userStats, "should be empty")
|
||||||
|
|
||||||
|
// Create Activity
|
||||||
|
end := time.Now()
|
||||||
|
start := end.AddDate(0, 0, -9)
|
||||||
|
var counter int64 = 0
|
||||||
|
|
||||||
|
for d := start; d.After(end) == false; d = d.AddDate(0, 0, 1) {
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
// Add Item
|
||||||
|
activity, err := suite.dbm.Queries.AddActivity(suite.dbm.Ctx, AddActivityParams{
|
||||||
|
DocumentID: documentID,
|
||||||
|
DeviceID: deviceID,
|
||||||
|
UserID: testUserID,
|
||||||
|
StartTime: d.UTC().Format(time.RFC3339),
|
||||||
|
Duration: 60,
|
||||||
|
StartPercentage: float64(counter) / 100.0,
|
||||||
|
EndPercentage: float64(counter+1) / 100.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Nil(err, fmt.Sprintf("[%d] should have nil err for add activity", counter))
|
||||||
|
suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = suite.dbm.CacheTempTables()
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Ensure One Item
|
||||||
|
userStats, err = suite.dbm.Queries.GetUserStatistics(suite.dbm.Ctx)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Len(userStats, 1, "should have length of one")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UsersTestSuite) TestGetUsersStreaks() {
|
||||||
|
err := suite.dbm.CacheTempTables()
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Ensure Zero Items
|
||||||
|
userStats, err := suite.dbm.Queries.GetUserStreaks(suite.dbm.Ctx, testUserID)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Empty(userStats, "should be empty")
|
||||||
|
|
||||||
|
// Create Activity
|
||||||
|
end := time.Now()
|
||||||
|
start := end.AddDate(0, 0, -9)
|
||||||
|
var counter int64 = 0
|
||||||
|
|
||||||
|
for d := start; d.After(end) == false; d = d.AddDate(0, 0, 1) {
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
// Add Item
|
||||||
|
activity, err := suite.dbm.Queries.AddActivity(suite.dbm.Ctx, AddActivityParams{
|
||||||
|
DocumentID: documentID,
|
||||||
|
DeviceID: deviceID,
|
||||||
|
UserID: testUserID,
|
||||||
|
StartTime: d.UTC().Format(time.RFC3339),
|
||||||
|
Duration: 60,
|
||||||
|
StartPercentage: float64(counter) / 100.0,
|
||||||
|
EndPercentage: float64(counter+1) / 100.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Nil(err, fmt.Sprintf("[%d] should have nil err for add activity", counter))
|
||||||
|
suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = suite.dbm.CacheTempTables()
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Ensure Two Item
|
||||||
|
userStats, err = suite.dbm.Queries.GetUserStreaks(suite.dbm.Ctx, testUserID)
|
||||||
|
suite.Nil(err, "should have nil err")
|
||||||
|
suite.Len(userStats, 2, "should have length of two")
|
||||||
|
|
||||||
|
// Ensure Streak Stats
|
||||||
|
dayStats := userStats[0]
|
||||||
|
weekStats := userStats[1]
|
||||||
|
suite.Equal(int64(10), dayStats.CurrentStreak, "should be 10 days")
|
||||||
|
suite.Greater(weekStats.CurrentStreak, int64(1), "should be 2 or 3")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user