tests(db): add additional tests & comments
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Evan Reichard 2024-06-16 20:00:41 -04:00
parent 9809a09d2e
commit 3a633235ea
5 changed files with 383 additions and 134 deletions

View File

@ -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)
} }

View File

@ -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:

View File

@ -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 {

View File

@ -5,98 +5,71 @@ 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) {
dt := databaseTest{t, dbm}
dt.TestUser()
dt.TestDocument()
dt.TestDevice()
dt.TestActivity()
dt.TestDailyReadStats()
})
}
func (dt *databaseTest) TestUser() {
dt.Run("User", func(t *testing.T) {
// Generate Auth Hash
rawAuthHash, err := utils.GenerateToken(64)
assert.Nil(t, err, "should have nil err")
// Create User
rawAuthHash, _ := utils.GenerateToken(64)
authHash := fmt.Sprintf("%x", rawAuthHash) authHash := fmt.Sprintf("%x", rawAuthHash)
changed, err := dt.dbm.Queries.CreateUser(dt.dbm.Ctx, CreateUserParams{ _, err := suite.dbm.Queries.CreateUser(suite.dbm.Ctx, CreateUserParams{
ID: userID, ID: userID,
Pass: &userPass, Pass: &userPass,
AuthHash: &authHash, AuthHash: &authHash,
}) })
suite.NoError(err)
assert.Nil(t, err, "should have nil err") // Create Document
assert.Equal(t, int64(1), changed) _, err = suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
user, err := dt.dbm.Queries.GetUser(dt.dbm.Ctx, userID)
assert.Nil(t, err, "should have nil err")
assert.Equal(t, userPass, *user.Pass)
})
}
func (dt *databaseTest) TestDocument() {
dt.Run("Document", func(t *testing.T) {
doc, err := dt.dbm.Queries.UpsertDocument(dt.dbm.Ctx, UpsertDocumentParams{
ID: documentID, ID: documentID,
Title: &documentTitle, Title: &documentTitle,
Author: &documentAuthor, Author: &documentAuthor,
Words: &documentWords,
}) })
suite.NoError(err)
assert.Nil(t, err, "should have nil err") // Create Device
assert.Equal(t, documentID, doc.ID, "should have document id") _, err = suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
assert.Equal(t, documentTitle, *doc.Title, "should have document title")
assert.Equal(t, documentAuthor, *doc.Author, "should have document author")
})
}
func (dt *databaseTest) TestDevice() {
dt.Run("Device", func(t *testing.T) {
device, err := dt.dbm.Queries.UpsertDevice(dt.dbm.Ctx, UpsertDeviceParams{
ID: deviceID, ID: deviceID,
UserID: userID, UserID: userID,
DeviceName: deviceName, DeviceName: deviceName,
}) })
suite.NoError(err)
assert.Nil(t, err, "should have nil err") // Create Activity
assert.Equal(t, deviceID, device.ID, "should have device id")
assert.Equal(t, userID, device.UserID, "should have user id")
assert.Equal(t, deviceName, device.DeviceName, "should have device name")
})
}
func (dt *databaseTest) TestActivity() {
dt.Run("Progress", func(t *testing.T) {
// 10 Activities, 10 Days
end := time.Now() end := time.Now()
start := end.AddDate(0, 0, -9) start := end.AddDate(0, 0, -9)
var counter int64 = 0 var counter int64 = 0
@ -105,7 +78,7 @@ func (dt *databaseTest) TestActivity() {
counter += 1 counter += 1
// Add Item // Add Item
activity, err := dt.dbm.Queries.AddActivity(dt.dbm.Ctx, AddActivityParams{ activity, err := suite.dbm.Queries.AddActivity(suite.dbm.Ctx, AddActivityParams{
DocumentID: documentID, DocumentID: documentID,
DeviceID: deviceID, DeviceID: deviceID,
UserID: userID, UserID: userID,
@ -115,25 +88,77 @@ func (dt *databaseTest) TestActivity() {
EndPercentage: float64(counter+1) / 100.0, EndPercentage: float64(counter+1) / 100.0,
}) })
assert.Nil(t, err, fmt.Sprintf("[%d] should have nil err for add activity", counter)) suite.Nil(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)) suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
} }
// Initiate Cache // Initiate Cache
dt.dbm.CacheTempTables() err = suite.dbm.CacheTempTables()
suite.NoError(err)
}
// DOCUMENT - TODO:
// - 󰊕 (q *Queries) DeleteDocument
// - 󰊕 (q *Queries) GetDeletedDocuments
// - 󰊕 (q *Queries) GetDocument
// - 󰊕 (q *Queries) GetDocumentProgress
// - 󰊕 (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"
doc, err := suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
ID: testDocID,
Title: &documentTitle,
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")
}
// DEVICES - TODO:
// - 󰊕 (q *Queries) GetDevice
// - 󰊕 (q *Queries) GetDevices
// - 󰊕 (q *Queries) UpsertDevice
func (suite *DatabaseTestSuite) TestDevice() {
testDevice := "dev123"
device, err := suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
ID: testDevice,
UserID: userID,
DeviceName: deviceName,
})
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")
}
// ACTIVITY - TODO:
// - 󰊕 (q *Queries) AddActivity
// - 󰊕 (q *Queries) GetActivity
// - 󰊕 (q *Queries) GetLastActivity
func (suite *DatabaseTestSuite) TestActivity() {
// Validate Exists // Validate Exists
existsRows, err := dt.dbm.Queries.GetActivity(dt.dbm.Ctx, GetActivityParams{ existsRows, err := suite.dbm.Queries.GetActivity(suite.dbm.Ctx, GetActivityParams{
UserID: userID, UserID: userID,
Offset: 0, Offset: 0,
Limit: 50, Limit: 50,
}) })
assert.Nil(t, err, "should have nil err for get activity") suite.Nil(err, "should have nil err for get activity")
assert.Len(t, existsRows, 10, "should have correct number of rows get activity") suite.Len(existsRows, 10, "should have correct number of rows get activity")
// Validate Doesn't Exist // Validate Doesn't Exist
doesntExistsRows, err := dt.dbm.Queries.GetActivity(dt.dbm.Ctx, GetActivityParams{ doesntExistsRows, err := suite.dbm.Queries.GetActivity(suite.dbm.Ctx, GetActivityParams{
UserID: userID, UserID: userID,
DocumentID: "unknownDoc", DocumentID: "unknownDoc",
DocFilter: true, DocFilter: true,
@ -141,28 +166,30 @@ func (dt *databaseTest) TestActivity() {
Limit: 50, Limit: 50,
}) })
assert.Nil(t, err, "should have nil err for get activity") suite.Nil(err, "should have nil err for get activity")
assert.Len(t, doesntExistsRows, 0, "should have no rows") 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
View 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")
}