1140 lines
28 KiB
Go
1140 lines
28 KiB
Go
//nolint:goconst // Not gonna use constants here
|
|
package storagetesting
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.maronato.dev/maronato/goshort/internal/errs"
|
|
"git.maronato.dev/maronato/goshort/internal/storage"
|
|
"git.maronato.dev/maronato/goshort/internal/storage/models"
|
|
bcryptpasswords "git.maronato.dev/maronato/goshort/internal/util/passwords/bcrypt"
|
|
"github.com/stretchr/testify/assert"
|
|
_ "modernc.org/sqlite" // Import the SQLite driver.
|
|
)
|
|
|
|
var ErrPlaceholder = errors.New("placeholder error")
|
|
|
|
func ITestComplete(t *testing.T, getStg func() storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
test func(t *testing.T, stg storage.Storage)
|
|
dontStart bool
|
|
}{
|
|
{
|
|
name: "ImplementsInterface",
|
|
test: ITestImplements,
|
|
},
|
|
// Lifecycle
|
|
{
|
|
name: "Start",
|
|
test: ITestStart,
|
|
dontStart: true,
|
|
},
|
|
{
|
|
name: "Stop",
|
|
test: ITestStop,
|
|
dontStart: true,
|
|
},
|
|
{
|
|
name: "Ping",
|
|
test: ITestPing,
|
|
},
|
|
// Short storage.Storage
|
|
{
|
|
name: "CreateShort",
|
|
test: ITestCreateShort,
|
|
},
|
|
{
|
|
name: "FindShort",
|
|
test: ITestFindShort,
|
|
},
|
|
{
|
|
name: "FindShortByID",
|
|
test: ITestFindShortByID,
|
|
},
|
|
{
|
|
name: "DeleteShort",
|
|
test: ITestDeleteShort,
|
|
},
|
|
{
|
|
name: "ListShorts",
|
|
test: ITestListShorts,
|
|
},
|
|
// ShortLog storage.Storage
|
|
{
|
|
name: "CreateShortLog",
|
|
test: ITestCreateShortLog,
|
|
},
|
|
{
|
|
name: "ListShortLogs",
|
|
test: ITestListShortLogs,
|
|
},
|
|
// User storage.Storage
|
|
{
|
|
name: "CreateUser",
|
|
test: ITestCreateUser,
|
|
},
|
|
{
|
|
name: "FindUser",
|
|
test: ITestFindUser,
|
|
},
|
|
{
|
|
name: "FindUserByID",
|
|
test: ITestFindUserByID,
|
|
},
|
|
{
|
|
name: "DeleteUser",
|
|
test: ITestDeleteUser,
|
|
},
|
|
// Token storage.Storage
|
|
{
|
|
name: "CreateToken",
|
|
test: ITestCreateToken,
|
|
},
|
|
{
|
|
name: "FindToken",
|
|
test: ITestFindToken,
|
|
},
|
|
{
|
|
name: "FindTokenByID",
|
|
test: ITestFindTokenByID,
|
|
},
|
|
{
|
|
name: "DeleteToken",
|
|
test: ITestDeleteToken,
|
|
},
|
|
{
|
|
name: "ListTokens",
|
|
test: ITestListTokens,
|
|
},
|
|
{
|
|
name: "ChangeTokenName",
|
|
test: ITestChangeTokenName,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
stg := getStg()
|
|
if !test.dontStart {
|
|
_ = stg.Start(context.Background())
|
|
}
|
|
|
|
test.test(t, stg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func IBenchmarkComplete(b *testing.B, getStg func() storage.Storage) {
|
|
b.Helper()
|
|
|
|
tests := []struct {
|
|
name string
|
|
test func(b *testing.B, stg storage.Storage)
|
|
}{
|
|
{
|
|
name: "CreateShort",
|
|
test: IBenchmarkCreateShort,
|
|
},
|
|
{
|
|
name: "CreateShortLog",
|
|
test: IBenchmarkCreateShortLog,
|
|
},
|
|
{
|
|
name: "CreateUser",
|
|
test: IBenchmarkCreateUser,
|
|
},
|
|
{
|
|
name: "CreateToken",
|
|
test: IBenchmarkCreateToken,
|
|
},
|
|
{
|
|
name: "FindShort",
|
|
test: IBenchmarkFindShort,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
b.Run(test.name, func(b *testing.B) {
|
|
stg := getStg()
|
|
_ = stg.Start(context.Background())
|
|
test.test(b, stg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func ITestImplements(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
assert.Implements(t, (*storage.Storage)(nil), stg, "Should implement the storage.Storage interface")
|
|
}
|
|
|
|
func ITestStart(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
err := stg.Start(ctx)
|
|
assert.Nil(t, err, "Should not return an error when starting the storage")
|
|
|
|
err = stg.Start(ctx)
|
|
assert.ErrorIs(t, err, errs.ErrStorageStarted, "Should return an error when starting an already started storage")
|
|
|
|
_ = stg.Stop(ctx)
|
|
}
|
|
|
|
func ITestStop(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
err := stg.Stop(ctx)
|
|
assert.ErrorIs(t, err, errs.ErrStorageNotStarted, "Should return an error when stopping a storage that wasn't started")
|
|
|
|
_ = stg.Start(ctx)
|
|
|
|
err = stg.Stop(ctx)
|
|
assert.Nil(t, err, "Should not return an error when stopping the storage")
|
|
|
|
err = stg.Stop(ctx)
|
|
assert.ErrorIs(t, err, errs.ErrStorageNotStarted, "Should return an error when stopping an already stopped storage")
|
|
}
|
|
|
|
func ITestPing(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
err := stg.Ping(ctx)
|
|
assert.Nil(t, err, "Should not return an error when pinging the storage")
|
|
|
|
_ = stg.Stop(ctx)
|
|
|
|
err = stg.Ping(ctx)
|
|
assert.NotNil(t, err, "Should return an error when pinging a closed storage")
|
|
}
|
|
|
|
func ITestCreateShort(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userStr := "user"
|
|
|
|
tests := []struct {
|
|
name string
|
|
short *models.Short
|
|
newShort *models.Short
|
|
err error
|
|
}{
|
|
{
|
|
name: "Should create a short",
|
|
short: &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: nil,
|
|
},
|
|
newShort: &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: nil,
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should create a short with a user",
|
|
short: &models.Short{
|
|
Name: "myshort2",
|
|
URL: "https://example.com",
|
|
UserID: &userStr,
|
|
},
|
|
newShort: &models.Short{
|
|
Name: "myshort2",
|
|
URL: "https://example.com",
|
|
UserID: &userStr,
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should not use the given ID",
|
|
short: &models.Short{
|
|
ID: "myid",
|
|
Name: "myshort3",
|
|
URL: "https://example.com",
|
|
UserID: nil,
|
|
},
|
|
newShort: &models.Short{
|
|
Name: "myshort3",
|
|
URL: "https://example.com",
|
|
UserID: nil,
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should not use the given CreatedAt",
|
|
short: &models.Short{
|
|
CreatedAt: time.Now().Add(-time.Hour),
|
|
Name: "myshort4",
|
|
URL: "https://example.com",
|
|
UserID: nil,
|
|
},
|
|
newShort: &models.Short{
|
|
CreatedAt: time.Now().Add(-time.Hour),
|
|
Name: "myshort4",
|
|
URL: "https://example.com",
|
|
UserID: nil,
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should return an error when the short is nil",
|
|
short: nil,
|
|
newShort: nil,
|
|
err: errs.ErrInvalidShort,
|
|
},
|
|
{
|
|
name: "Should return an error when using the same name",
|
|
short: &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: nil,
|
|
},
|
|
newShort: nil,
|
|
err: errs.ErrShortExists,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
newShort, err := stg.CreateShort(ctx, test.short)
|
|
|
|
if errors.Is(test.err, ErrPlaceholder) {
|
|
assert.Error(t, err, "Should return an error")
|
|
} else {
|
|
assert.ErrorIs(t, err, test.err, "Should return the same error")
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if test.short.ID != "" {
|
|
assert.NotEqual(t, test.short.ID, newShort.ID, "Should not return the same ID")
|
|
}
|
|
|
|
if !test.short.CreatedAt.IsZero() {
|
|
assert.NotEqual(t, test.short.CreatedAt, newShort.CreatedAt, "Should not return the same CreatedAt")
|
|
}
|
|
|
|
assert.NotZero(t, newShort.CreatedAt, "Should return a non-zero CreatedAt")
|
|
assert.NotZero(t, newShort.ID, "Should return a non-zero ID")
|
|
assert.Equal(t, test.newShort.Name, newShort.Name, "Should return the same Name")
|
|
assert.Equal(t, test.newShort.URL, newShort.URL, "Should return the same URL")
|
|
assert.Equal(t, test.newShort.UserID, newShort.UserID, "Should return the same UserID")
|
|
})
|
|
}
|
|
}
|
|
|
|
func ITestFindShort(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
baseShort := &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: &userID,
|
|
}
|
|
|
|
found, err := stg.FindShort(ctx, baseShort.Name)
|
|
assert.ErrorIs(t, err, errs.ErrShortDoesNotExist, "Should return an error when the short doesn't exist")
|
|
assert.Nil(t, found, "Should not find the short")
|
|
|
|
short, _ := stg.CreateShort(ctx, baseShort)
|
|
|
|
found, err = stg.FindShort(ctx, baseShort.Name)
|
|
assert.Nil(t, err, "Should not return an error when finding the short")
|
|
assert.Equal(t, short, found, "Should return the same short")
|
|
|
|
_ = stg.DeleteShort(ctx, short)
|
|
|
|
found, err = stg.FindShort(ctx, baseShort.Name)
|
|
assert.ErrorIs(t, err, errs.ErrShortDoesNotExist, "Should return an error when the short is deleted")
|
|
assert.Nil(t, found, "Should not find the short")
|
|
}
|
|
|
|
func ITestFindShortByID(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
baseShort := &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: &userID,
|
|
}
|
|
|
|
found, err := stg.FindShortByID(ctx, "myid")
|
|
assert.ErrorIs(t, err, errs.ErrShortDoesNotExist, "Should return an error when the short doesn't exist")
|
|
assert.Nil(t, found, "Should not find the short")
|
|
|
|
short, _ := stg.CreateShort(ctx, baseShort)
|
|
|
|
found, err = stg.FindShortByID(ctx, short.ID)
|
|
assert.Nil(t, err, "Should not return an error when finding the short")
|
|
assert.Equal(t, short, found, "Should return the same short")
|
|
|
|
_ = stg.DeleteShort(ctx, short)
|
|
|
|
found, err = stg.FindShort(ctx, baseShort.Name)
|
|
assert.ErrorIs(t, err, errs.ErrShortDoesNotExist, "Should return an error when the short is deleted")
|
|
assert.Nil(t, found, "Should not find the short")
|
|
}
|
|
|
|
func ITestDeleteShort(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
newShort, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: &userID,
|
|
})
|
|
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: newShort.ID,
|
|
})
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: newShort.ID,
|
|
})
|
|
|
|
found, _ := stg.FindShort(ctx, "myshort")
|
|
assert.NotNil(t, found, "Should find the short")
|
|
|
|
shortLogs, _ := stg.ListShortLogs(ctx, newShort)
|
|
assert.Len(t, shortLogs, 2, "Should have 2 short logs") //nolint:gomnd // It's a test
|
|
|
|
err := stg.DeleteShort(ctx, newShort)
|
|
assert.Nil(t, err, "Should not return an error when deleting the short")
|
|
|
|
found, err = stg.FindShort(ctx, "myshort")
|
|
assert.ErrorIs(t, err, errs.ErrShortDoesNotExist, "Should return an error when finding the short")
|
|
assert.Nil(t, found, "Should not find the short")
|
|
|
|
shortLogs, _ = stg.ListShortLogs(ctx, newShort)
|
|
assert.Len(t, shortLogs, 0, "Should not have any short logs")
|
|
|
|
err = stg.DeleteShort(ctx, newShort)
|
|
assert.Nil(t, err, "Should not return an error when deleting a deleted short")
|
|
}
|
|
|
|
func ITestListShorts(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
userID2 := "user2"
|
|
|
|
short1, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: &userID,
|
|
})
|
|
|
|
short2, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort2",
|
|
URL: "https://example.com",
|
|
UserID: &userID,
|
|
})
|
|
|
|
deletedShort, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "deletedshort",
|
|
URL: "https://example.com",
|
|
UserID: &userID,
|
|
})
|
|
_ = stg.DeleteShort(ctx, deletedShort)
|
|
|
|
short3, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort3",
|
|
URL: "https://example.com",
|
|
UserID: &userID2,
|
|
})
|
|
|
|
shorts, err := stg.ListShorts(ctx, &models.User{
|
|
ID: userID,
|
|
})
|
|
|
|
assert.Nil(t, err, "Should not return an error when listing the shorts")
|
|
assert.Len(t, shorts, 2, "Should return 2 shorts") //nolint:gomnd // It's a test
|
|
assert.Contains(t, shorts, short1, "Should return the first short")
|
|
assert.Contains(t, shorts, short2, "Should return the second short")
|
|
|
|
shorts2, err := stg.ListShorts(ctx, &models.User{
|
|
ID: userID2,
|
|
})
|
|
|
|
assert.Nil(t, err, "Should not return an error when listing the shorts")
|
|
assert.Len(t, shorts2, 1, "Should return 1 short")
|
|
assert.Contains(t, shorts2, short3, "Should return the third short")
|
|
}
|
|
|
|
func ITestCreateShortLog(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
short, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
})
|
|
|
|
baseShortLog := &models.ShortLog{
|
|
ShortID: short.ID,
|
|
IPAddress: "myip",
|
|
UserAgent: "myuseragent",
|
|
Referer: "myreferer",
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
err := stg.CreateShortLog(ctx, baseShortLog)
|
|
|
|
assert.Nil(t, err, "Should not return an error when creating the short log")
|
|
|
|
err = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short.ID,
|
|
})
|
|
|
|
assert.Nil(t, err, "Should not return an error when creating a short log twice")
|
|
|
|
shortLogs, err := stg.ListShortLogs(ctx, short)
|
|
|
|
assert.Nil(t, err, "Should not return an error when listing the short logs")
|
|
assert.Len(t, shortLogs, 2, "Should return 2 short logs") //nolint:gomnd // It's a test
|
|
|
|
shortLog := shortLogs[0]
|
|
|
|
assert.Equal(t, baseShortLog.ShortID, shortLog.ShortID, "Should return the same ShortID")
|
|
assert.Equal(t, baseShortLog.IPAddress, shortLog.IPAddress, "Should return the same IPAddress")
|
|
assert.Equal(t, baseShortLog.UserAgent, shortLog.UserAgent, "Should return the same UserAgent")
|
|
assert.Equal(t, baseShortLog.Referer, shortLog.Referer, "Should return the same Referer")
|
|
assert.NotZero(t, shortLog.ID, "Should return a non-zero ID")
|
|
|
|
assert.NotZero(t, shortLogs[1].ID, "Should return a non-zero ID")
|
|
assert.NotZero(t, shortLogs[1].CreatedAt, "Should return a non-zero CreatedAt")
|
|
}
|
|
|
|
func ITestListShortLogs(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
short, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
})
|
|
|
|
short2, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort2",
|
|
URL: "https://example.com",
|
|
})
|
|
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short.ID,
|
|
})
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short.ID,
|
|
})
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short2.ID,
|
|
})
|
|
|
|
shortLogs, err := stg.ListShortLogs(ctx, short)
|
|
|
|
assert.Nil(t, err, "Should not return an error when listing the short logs")
|
|
assert.Len(t, shortLogs, 2, "Should return 2 short log") //nolint:gomnd // It's a test
|
|
|
|
shortLogs2, err := stg.ListShortLogs(ctx, short2)
|
|
|
|
assert.Nil(t, err, "Should not return an error when listing the short logs")
|
|
assert.Len(t, shortLogs2, 1, "Should return 1 short log")
|
|
}
|
|
|
|
func ITestCreateUser(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
bh := bcryptpasswords.NewBcryptHasher()
|
|
|
|
tests := []struct {
|
|
name string
|
|
user *models.User
|
|
password string
|
|
newUser *models.User
|
|
err error
|
|
}{
|
|
{
|
|
name: "Should create a user",
|
|
user: &models.User{
|
|
Username: "myusername",
|
|
},
|
|
password: "mypassword",
|
|
newUser: &models.User{
|
|
Username: "myusername",
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should not use the given ID",
|
|
user: &models.User{
|
|
ID: "myid",
|
|
Username: "myusername2",
|
|
},
|
|
newUser: &models.User{
|
|
Username: "myusername2",
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should error when the username was already taken",
|
|
user: &models.User{
|
|
Username: "myusername",
|
|
},
|
|
password: "mypassword",
|
|
newUser: nil,
|
|
err: errs.ErrUserExists,
|
|
},
|
|
{
|
|
name: "Should error if the user is nil",
|
|
user: nil,
|
|
err: errs.ErrInvalidUser,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if test.password != "" {
|
|
_ = test.user.SetPassword(bh, test.password)
|
|
}
|
|
|
|
newUser, err := stg.CreateUser(ctx, test.user)
|
|
|
|
if errors.Is(test.err, ErrPlaceholder) {
|
|
assert.Error(t, err, "Should return an error")
|
|
} else {
|
|
assert.ErrorIs(t, err, test.err, "Should return the same error")
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
assert.NotZero(t, newUser.CreatedAt, "Should return a non-zero CreatedAt")
|
|
assert.NotZero(t, newUser.ID, "Should return a non-zero ID")
|
|
assert.Equal(t, test.newUser.Username, newUser.Username, "Should return the same Username")
|
|
|
|
if test.password != "" {
|
|
v, _ := bh.Verify(test.password, newUser.GetPasswordHash())
|
|
assert.True(t, v, "Should have the same password")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func ITestFindUser(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
bh := bcryptpasswords.NewBcryptHasher()
|
|
|
|
baseUser := &models.User{
|
|
Username: "myusername",
|
|
}
|
|
_ = baseUser.SetPassword(bh, "mypassword")
|
|
|
|
found, err := stg.FindUser(ctx, baseUser.Username)
|
|
assert.ErrorIs(t, err, errs.ErrUserDoesNotExist, "Should return an error when the user doesn't exist")
|
|
assert.Nil(t, found, "Should not find the user")
|
|
|
|
user, _ := stg.CreateUser(ctx, baseUser)
|
|
|
|
found, err = stg.FindUser(ctx, user.Username)
|
|
assert.Nil(t, err, "Should not return an error when finding the user")
|
|
assert.Equal(t, user, found, "Should return the same user")
|
|
|
|
v, _ := bh.Verify("mypassword", found.GetPasswordHash())
|
|
assert.True(t, v, "Should have the same password")
|
|
|
|
_ = stg.DeleteUser(ctx, user)
|
|
|
|
found, err = stg.FindUser(ctx, user.Username)
|
|
assert.ErrorIs(t, err, errs.ErrUserDoesNotExist, "Should return an error when the user is deleted")
|
|
assert.Nil(t, found, "Should not find the user")
|
|
}
|
|
|
|
func ITestFindUserByID(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
bh := bcryptpasswords.NewBcryptHasher()
|
|
|
|
baseUser := &models.User{
|
|
Username: "myusername",
|
|
}
|
|
_ = baseUser.SetPassword(bh, "mypassword")
|
|
|
|
found, err := stg.FindUserByID(ctx, "my id")
|
|
assert.ErrorIs(t, err, errs.ErrUserDoesNotExist, "Should return an error when the user doesn't exist")
|
|
assert.Nil(t, found, "Should not find the user")
|
|
|
|
user, _ := stg.CreateUser(ctx, baseUser)
|
|
|
|
found, err = stg.FindUserByID(ctx, user.ID)
|
|
assert.Nil(t, err, "Should not return an error when finding the user")
|
|
assert.Equal(t, user, found, "Should return the same user")
|
|
|
|
v, _ := bh.Verify("mypassword", found.GetPasswordHash())
|
|
assert.True(t, v, "Should have the same password")
|
|
|
|
_ = stg.DeleteUser(ctx, user)
|
|
|
|
found, err = stg.FindUserByID(ctx, user.ID)
|
|
assert.ErrorIs(t, err, errs.ErrUserDoesNotExist, "Should return an error when the user is deleted")
|
|
assert.Nil(t, found, "Should not find the user")
|
|
}
|
|
|
|
func ITestDeleteUser(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
bh := bcryptpasswords.NewBcryptHasher()
|
|
|
|
baseUser := &models.User{
|
|
Username: "myusername",
|
|
}
|
|
_ = baseUser.SetPassword(bh, "mypassword")
|
|
|
|
user, _ := stg.CreateUser(ctx, baseUser)
|
|
short1, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: &user.ID,
|
|
})
|
|
short2, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort2",
|
|
URL: "https://example.com",
|
|
UserID: &user.ID,
|
|
})
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short1.ID,
|
|
})
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short1.ID,
|
|
})
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short2.ID,
|
|
})
|
|
_, _ = stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &user.ID,
|
|
})
|
|
|
|
found, _ := stg.FindUser(ctx, user.Username)
|
|
assert.NotNil(t, found, "Should find the user")
|
|
|
|
err := stg.DeleteUser(ctx, user)
|
|
assert.Nil(t, err, "Should not return an error when deleting the user")
|
|
|
|
found, err = stg.FindUser(ctx, user.Username)
|
|
assert.ErrorIs(t, err, errs.ErrUserDoesNotExist, "Should return an error when finding the user")
|
|
assert.Nil(t, found, "Should not find the user")
|
|
|
|
shorts, _ := stg.ListShorts(ctx, user)
|
|
assert.Len(t, shorts, 0, "Should not have any shorts")
|
|
|
|
shortLogs1, _ := stg.ListShortLogs(ctx, short1)
|
|
shortLogs2, _ := stg.ListShortLogs(ctx, short2)
|
|
assert.Len(t, append(shortLogs1, shortLogs2...), 0, "Should not have any short logs")
|
|
|
|
tokens, _ := stg.ListTokens(ctx, user)
|
|
assert.Len(t, tokens, 0, "Should not have any tokens")
|
|
|
|
err = stg.DeleteUser(ctx, user)
|
|
assert.Nil(t, err, "Should not return an error when deleting a deleted user")
|
|
}
|
|
|
|
func ITestCreateToken(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
|
|
tests := []struct {
|
|
name string
|
|
token *models.Token
|
|
err error
|
|
}{
|
|
{
|
|
name: "Should create a token",
|
|
token: &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &userID,
|
|
Name: "mytoken",
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should not use the given ID",
|
|
token: &models.Token{
|
|
ID: "myid",
|
|
Value: "myvalue2",
|
|
UserID: &userID,
|
|
Name: "mytoken2",
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should not use the given CreatedAt",
|
|
token: &models.Token{
|
|
CreatedAt: time.Now().Add(-time.Hour),
|
|
Value: "myvalue3",
|
|
UserID: &userID,
|
|
Name: "mytoken3",
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
name: "Should return an error when the token is nil",
|
|
token: nil,
|
|
err: errs.ErrInvalidToken,
|
|
},
|
|
{
|
|
name: "Should return an error if the value was already taken",
|
|
token: &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &userID,
|
|
Name: "mytoken4",
|
|
},
|
|
err: errs.ErrTokenExists,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
newTok, err := stg.CreateToken(ctx, test.token)
|
|
|
|
if errors.Is(test.err, ErrPlaceholder) {
|
|
assert.Error(t, err, "Should return an error")
|
|
} else {
|
|
assert.ErrorIs(t, err, test.err, "Should return the same error")
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
assert.NotZero(t, newTok.CreatedAt, "Should return a non-zero CreatedAt")
|
|
assert.NotZero(t, newTok.ID, "Should return a non-zero ID")
|
|
assert.NotEqual(t, test.token.CreatedAt, newTok.CreatedAt, "Should not return the same CreatedAt")
|
|
assert.NotEqual(t, test.token.ID, newTok.ID, "Should not return the same ID")
|
|
assert.Equal(t, test.token.Value, newTok.Value, "Should return the same Value")
|
|
assert.Equal(t, test.token.Name, newTok.Name, "Should return the same Name")
|
|
assert.Equal(t, test.token.UserID, newTok.UserID, "Should return the same UserID")
|
|
})
|
|
}
|
|
}
|
|
|
|
func ITestFindToken(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
|
|
found, err := stg.FindToken(ctx, "myvalue")
|
|
assert.ErrorIs(t, err, errs.ErrTokenDoesNotExist, "Should return an error when the token doesn't exist")
|
|
assert.Nil(t, found, "Should not find the token")
|
|
|
|
token, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &userID,
|
|
Name: "mytoken",
|
|
})
|
|
|
|
found, err = stg.FindToken(ctx, token.Value)
|
|
assert.Nil(t, err, "Should not return an error when finding the token")
|
|
assert.Equal(t, token, found, "Should return the same token")
|
|
|
|
_ = stg.DeleteToken(ctx, token)
|
|
|
|
found, err = stg.FindToken(ctx, token.Value)
|
|
assert.ErrorIs(t, err, errs.ErrTokenDoesNotExist, "Should return an error when the token is deleted")
|
|
assert.Nil(t, found, "Should not find the token")
|
|
}
|
|
|
|
func ITestFindTokenByID(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
|
|
found, err := stg.FindTokenByID(ctx, "myid")
|
|
assert.ErrorIs(t, err, errs.ErrTokenDoesNotExist, "Should return an error when the token doesn't exist")
|
|
assert.Nil(t, found, "Should not find the token")
|
|
|
|
token, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &userID,
|
|
Name: "mytoken",
|
|
})
|
|
|
|
found, err = stg.FindTokenByID(ctx, token.ID)
|
|
assert.Nil(t, err, "Should not return an error when finding the token")
|
|
assert.Equal(t, token, found, "Should return the same token")
|
|
|
|
_ = stg.DeleteToken(ctx, token)
|
|
|
|
found, err = stg.FindToken(ctx, token.Value)
|
|
assert.ErrorIs(t, err, errs.ErrTokenDoesNotExist, "Should return an error when the token is deleted")
|
|
assert.Nil(t, found, "Should not find the token")
|
|
}
|
|
|
|
func ITestDeleteToken(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
|
|
newToken, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &userID,
|
|
Name: "mytoken",
|
|
})
|
|
|
|
found, _ := stg.FindToken(ctx, newToken.Value)
|
|
assert.NotNil(t, found, "Should find the token")
|
|
|
|
err := stg.DeleteToken(ctx, newToken)
|
|
assert.Nil(t, err, "Should not return an error when deleting the token")
|
|
|
|
found, err = stg.FindToken(ctx, newToken.Value)
|
|
assert.ErrorIs(t, err, errs.ErrTokenDoesNotExist, "Should return an error when finding the token")
|
|
assert.Nil(t, found, "Should not find the token")
|
|
|
|
err = stg.DeleteToken(ctx, newToken)
|
|
assert.Nil(t, err, "Should not return an error when deleting a deleted token")
|
|
}
|
|
|
|
func ITestListTokens(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
userID2 := "user2"
|
|
|
|
token1, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &userID,
|
|
})
|
|
token2, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue2",
|
|
UserID: &userID,
|
|
})
|
|
deletedToken, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue3",
|
|
UserID: &userID,
|
|
})
|
|
|
|
_ = stg.DeleteToken(ctx, deletedToken)
|
|
|
|
token3, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue4",
|
|
UserID: &userID2,
|
|
})
|
|
|
|
tokens, err := stg.ListTokens(ctx, &models.User{
|
|
ID: userID,
|
|
})
|
|
assert.Nil(t, err, "Should not return an error when listing the tokens")
|
|
assert.Len(t, tokens, 2, "Should return 2 tokens") //nolint:gomnd // It's a test
|
|
assert.Contains(t, tokens, token1, "Should return the first token")
|
|
assert.Contains(t, tokens, token2, "Should return the second token")
|
|
|
|
tokens2, err := stg.ListTokens(ctx, &models.User{
|
|
ID: userID2,
|
|
})
|
|
assert.Nil(t, err, "Should not return an error when listing the tokens")
|
|
assert.Len(t, tokens2, 1, "Should return 1 token")
|
|
assert.Contains(t, tokens2, token3, "Should return the third token")
|
|
}
|
|
|
|
func ITestChangeTokenName(t *testing.T, stg storage.Storage) {
|
|
t.Helper()
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
|
|
token, _ := stg.CreateToken(ctx, &models.Token{
|
|
Value: "myvalue",
|
|
UserID: &userID,
|
|
Name: "mytoken",
|
|
})
|
|
|
|
newToken, err := stg.ChangeTokenName(ctx, token, "mytoken2")
|
|
assert.Nil(t, err, "Should not return an error when changing the token name")
|
|
assert.Equal(t, "mytoken2", newToken.Name, "Should return the new name")
|
|
assert.Equal(t, token.ID, newToken.ID, "Should return the same ID")
|
|
assert.Equal(t, token.Value, newToken.Value, "Should return the same value")
|
|
assert.Equal(t, token.UserID, newToken.UserID, "Should return the same UserID")
|
|
assert.Equal(t, token.CreatedAt, newToken.CreatedAt, "Should return the same CreatedAt")
|
|
}
|
|
|
|
func IBenchmarkCreateShort(b *testing.B, stg storage.Storage) {
|
|
b.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
url := "https://example.com"
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := stg.CreateShort(ctx, &models.Short{
|
|
Name: fmt.Sprintf("myshort%d", i),
|
|
URL: url,
|
|
UserID: &userID,
|
|
})
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func IBenchmarkCreateShortLog(b *testing.B, stg storage.Storage) {
|
|
b.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
short, _ := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
})
|
|
ipAddress := "myip"
|
|
userAgent := "myuseragent"
|
|
referer := "myreferer"
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_ = stg.CreateShortLog(ctx, &models.ShortLog{
|
|
ShortID: short.ID,
|
|
IPAddress: ipAddress,
|
|
UserAgent: userAgent,
|
|
Referer: referer,
|
|
})
|
|
}
|
|
}
|
|
|
|
func IBenchmarkCreateUser(b *testing.B, stg storage.Storage) {
|
|
b.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := stg.CreateUser(ctx, &models.User{
|
|
Username: fmt.Sprintf("myusername%d", i),
|
|
})
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func IBenchmarkCreateToken(b *testing.B, stg storage.Storage) {
|
|
b.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := stg.CreateToken(ctx, &models.Token{
|
|
Value: fmt.Sprintf("myvalue%d", i),
|
|
UserID: &userID,
|
|
})
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func IBenchmarkFindShort(b *testing.B, stg storage.Storage) {
|
|
b.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := "user"
|
|
|
|
short, err := stg.CreateShort(ctx, &models.Short{
|
|
Name: "myshort",
|
|
URL: "https://example.com",
|
|
UserID: &userID,
|
|
})
|
|
|
|
b.Run("Existing", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err = stg.FindShort(ctx, short.Name)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("Non-existing", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err = stg.FindShort(ctx, "non-existing")
|
|
if err != nil && !errors.Is(err, errs.ErrShortDoesNotExist) {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
}
|