Compare commits

...

9 Commits

7 changed files with 740 additions and 19 deletions

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.22.0
require (
github.com/davecgh/go-spew v1.1.1
github.com/lib/pq v1.10.9
github.com/matoous/go-nanoid/v2 v2.1.0
github.com/rubenv/sql-migrate v1.7.0
github.com/sirupsen/logrus v1.9.3

View File

@ -67,7 +67,9 @@ func Connect(ctx context.Context, config models.DatabaseConfig) error {
}
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s", config.Endpoint, config.Username, config.Password, config.Database, config.Port, localSSL, config.Timezone)
sqlDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
sqlDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
TranslateError: true,
})
if err != nil {
return utils.HandleError(ctx, span, localLogger, err)
}
@ -110,13 +112,11 @@ func migrateDatabase(ctx context.Context, dbPool *gorm.DB, config models.Databas
return utils.HandleError(ctx, span, localLogger, err)
}
if config.Debug {
if n != 0 {
localLogger.Debug("applied %d migrations!", n)
localLogger.Debugf("applied %d migrations!", n)
} else {
localLogger.Debug("nothing to migrate")
}
}
utils.HandleEvent(span, localLogger, "Database migration completed successfully")
return nil

View File

@ -87,7 +87,7 @@ CREATE TABLE "UserSource"
deleted_at TIMESTAMP NULL NULL,
user_id TEXT REFERENCES "User" (id),
source_id TEXT REFERENCES "Source" (id),
scrape_time_interval INT,
scrape_time_interval TEXT,
account_username TEXT,
account_id TEXT,
last_scrape_time TIMESTAMP,

View File

@ -16,14 +16,6 @@ func CreateUserSource(ctx context.Context, userSource models.UserSource) (models
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "CreateUserSource")
defer span.End()
localLogger = localLogger.WithFields(log.Fields{
"user_source_id": userSource.ID,
})
span.SetAttributes(
attribute.String("user_source_id", string(userSource.SourceID)),
)
utils.HandleEvent(span, localLogger, "Starting user source creation")
if client == nil {
@ -38,10 +30,25 @@ func CreateUserSource(ctx context.Context, userSource models.UserSource) (models
return models.UserSource{}, utils.HandleError(ctx, span, localLogger, result.Error)
}
localLogger = localLogger.WithFields(log.Fields{
"user_source_id": userSource.ID,
})
span.SetAttributes(
attribute.String("user_source_id", string(userSource.SourceID)),
)
utils.HandleEvent(span, localLogger, "User source created successfully")
return userSource, nil
}
// UpdateUserSource updates the user source information in the database.
// Only a few parameter can be updated:
// - AccountID
// - ScrapeTimeInterval
// - AccountUsername
// - LastScrapeTime
// - AccountValidate
func UpdateUserSource(ctx context.Context, userSource models.UserSource) error {
ctx, span, localLogger := utils.SetupTracing(ctx, tracer, "UpdateUserSource")
defer span.End()

View File

@ -0,0 +1,498 @@
package database
import (
"context"
"fmt"
"reflect"
"testing"
"time"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/test"
"go.opentelemetry.io/contrib/bridges/otellogrus"
"go.opentelemetry.io/otel"
"gorm.io/gorm"
)
func TestCreateUserSource(t *testing.T) {
// Setup trow away container
ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx)
if err != nil {
logger.Fatalf("Could not start PostgreSQL container: %v", err)
}
client = gormDB
// Setup open telemetry
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
defer container.Terminate(ctx)
// -- -- Setup Tests
// -- Create User ot test with
validUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: models.UserID(fmt.Sprintf("%025s", "User1"))}}
validUser, err = CreateUser(ctx, validUser)
if err != nil {
t.Fatalf("CreateUser err: %v", err)
}
// --
// -- Create Source to test with
validSource := models.Source{
DisplayName: "e621",
Domain: "e621.net",
Icon: "e621.net/icon.png",
}
validSource, err = CreateSource(ctx, validSource)
if err != nil {
t.Fatalf("CreateSource err: %v", err)
}
// --
// -- Create UserSource model
validUSerSource := models.UserSource{
BaseModel: models.BaseModel[models.UserSourceID]{
ID: models.UserSourceID(fmt.Sprintf("%025s", "UserSourceId1")),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: gorm.DeletedAt{},
},
User: models.User{},
UserID: validUser.ID,
Source: models.Source{},
SourceID: validSource.ID,
ScrapeTimeInterval: "P1D",
AccountUsername: "marry",
AccountID: "poppens",
LastScrapeTime: time.Now(),
AccountValidate: false,
AccountValidationKey: "im-a-key",
}
// --
// -- -- Tests
type args struct {
ctx context.Context
userSource models.UserSource
}
tests := []struct {
name string
args args
want models.UserSource
wantErr bool
}{
{
name: "Test 01: Valid User Source",
args: args{
ctx: ctx,
userSource: validUSerSource,
},
want: validUSerSource,
wantErr: false,
},
{
name: "Test 02: Invalid User Source",
args: args{
ctx: ctx,
userSource: models.UserSource{},
},
want: models.UserSource{},
wantErr: true,
},
{
name: "Test 03: Duplicate User Source",
args: args{
ctx: ctx,
userSource: validUSerSource,
},
want: models.UserSource{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CreateUserSource(tt.args.ctx, tt.args.userSource)
if (err != nil) != tt.wantErr {
t.Errorf("CreateUserSource() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CreateUserSource() got = %v, want %v", got, tt.want)
}
})
}
}
func TestUpdateUserSource(t *testing.T) {
// Setup trow away container
ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx)
if err != nil {
logger.Fatalf("Could not start PostgreSQL container: %v", err)
}
client = gormDB
// Setup open telemetry
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
defer container.Terminate(ctx)
// -- -- Setup Tests
// -- Create User ot test with
validUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: models.UserID(fmt.Sprintf("%025s", "User1"))}}
validUser, err = CreateUser(ctx, validUser)
if err != nil {
t.Fatalf("CreateUser err: %v", err)
}
// --
// -- Create Source to test with
validSource := models.Source{
DisplayName: "e621",
Domain: "e621.net",
Icon: "e621.net/icon.png",
}
validSource, err = CreateSource(ctx, validSource)
if err != nil {
t.Fatalf("CreateSource err: %v", err)
}
// --
// -- Create UserSource model
validUserSource := models.UserSource{
BaseModel: models.BaseModel[models.UserSourceID]{
ID: models.UserSourceID(fmt.Sprintf("%025s", "UserSourceId1")),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: gorm.DeletedAt{},
},
User: models.User{},
UserID: validUser.ID,
Source: models.Source{},
SourceID: validSource.ID,
ScrapeTimeInterval: "P1D",
AccountUsername: "marry",
AccountID: "poppens",
LastScrapeTime: time.Now(),
AccountValidate: false,
AccountValidationKey: "im-a-key",
}
validUserSource, err = CreateUserSource(ctx, validUserSource)
if err != nil {
t.Fatalf("CreateUserSource err: %v", err)
}
// --
// -- Create Updates models for UserSource
validUpdateSourceUser := validUserSource
validUpdateSourceUser.AccountID = "1234"
validUpdateSourceUser.ScrapeTimeInterval = "P2D"
validUpdateSourceUser.AccountUsername = "Update_Username"
validUpdateSourceUser.LastScrapeTime = time.Now()
validUpdateSourceUser.AccountValidate = true
invalidUpdateSourceUser := models.UserSource{}
// --
// -- -- Tests
type args struct {
ctx context.Context
userSource models.UserSource
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Test 01: Valid Update for UserSource",
args: args{
ctx: ctx,
userSource: validUpdateSourceUser,
},
wantErr: false,
},
{
name: "Test 02: Invalid Update for UserSource",
args: args{
ctx: ctx,
userSource: invalidUpdateSourceUser,
},
wantErr: true,
},
{
name: "Test 03: Empty ID for Update for UserSource",
args: args{
ctx: ctx,
userSource: models.UserSource{BaseModel: models.BaseModel[models.UserSourceID]{ID: ""}},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := UpdateUserSource(tt.args.ctx, tt.args.userSource); (err != nil) != tt.wantErr {
t.Errorf("UpdateUserSource() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestGetUserSourceByID(t *testing.T) {
// Setup trow away container
ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx)
if err != nil {
logger.Fatalf("Could not start PostgreSQL container: %v", err)
}
client = gormDB
// Setup open telemetry
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
defer container.Terminate(ctx)
// -- -- Setup Tests
// -- Create User ot test with
validUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: models.UserID(fmt.Sprintf("%025s", "User1"))}}
validUser, err = CreateUser(ctx, validUser)
if err != nil {
t.Fatalf("CreateUser err: %v", err)
}
// --
// -- Create Source to test with
validSource := models.Source{
DisplayName: "e621",
Domain: "e621.net",
Icon: "e621.net/icon.png",
}
validSource, err = CreateSource(ctx, validSource)
if err != nil {
t.Fatalf("CreateSource err: %v", err)
}
// --
// -- Create UserSource model
validUserSource := models.UserSource{
BaseModel: models.BaseModel[models.UserSourceID]{
ID: models.UserSourceID(fmt.Sprintf("%025s", "UserSourceId1")),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: gorm.DeletedAt{},
},
User: models.User{},
UserID: validUser.ID,
Source: models.Source{},
SourceID: validSource.ID,
ScrapeTimeInterval: "P1D",
AccountUsername: "marry",
AccountID: "poppens",
LastScrapeTime: time.Now(),
AccountValidate: false,
AccountValidationKey: "im-a-key",
}
validUserSource, err = CreateUserSource(ctx, validUserSource)
if err != nil {
t.Fatalf("CreateUserSource err: %v", err)
}
// --
// -- -- Tests
type args struct {
ctx context.Context
id models.UserSourceID
}
tests := []struct {
name string
args args
want models.UserSource
wantErr bool
}{
{
name: "Test 01: Valid UserSource ID",
args: args{
ctx: ctx,
id: validUserSource.ID,
},
want: validUserSource,
wantErr: false,
},
{
name: "Test 03: Empty UserSourceID",
args: args{
ctx: ctx,
id: "",
},
wantErr: true,
},
{
name: "Test 04: Short UserSourceID",
args: args{
ctx: ctx,
id: "111",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetUserSourceByID(tt.args.ctx, tt.args.id)
if (err != nil) != tt.wantErr {
t.Errorf("GetUserSourceByID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkUserSourceID(got, tt.want) {
t.Errorf("GetUserSourceByID() got = %v, want %v", got, tt.want)
}
})
}
}
func TestDeleteUserSource(t *testing.T) {
// Setup trow away container
ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx)
if err != nil {
logger.Fatalf("Could not start PostgreSQL container: %v", err)
}
client = gormDB
// Setup open telemetry
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
defer container.Terminate(ctx)
// -- -- Setup Tests
// -- Create User ot test with
validUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: models.UserID(fmt.Sprintf("%025s", "User1"))}}
validUser, err = CreateUser(ctx, validUser)
if err != nil {
t.Fatalf("CreateUser err: %v", err)
}
// --
// -- Create Source to test with
validSource := models.Source{
DisplayName: "e621",
Domain: "e621.net",
Icon: "e621.net/icon.png",
}
validSource, err = CreateSource(ctx, validSource)
if err != nil {
t.Fatalf("CreateSource err: %v", err)
}
// --
// -- Create UserSource model
validUSerSource := models.UserSource{
BaseModel: models.BaseModel[models.UserSourceID]{
ID: models.UserSourceID(fmt.Sprintf("%025s", "UserSourceId1")),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: gorm.DeletedAt{},
},
User: models.User{},
UserID: validUser.ID,
Source: models.Source{},
SourceID: validSource.ID,
ScrapeTimeInterval: "P1D",
AccountUsername: "marry",
AccountID: "poppens",
LastScrapeTime: time.Now(),
AccountValidate: false,
AccountValidationKey: "im-a-key",
}
validUSerSource, err = CreateUserSource(ctx, validUSerSource)
if err != nil {
t.Fatalf("CreateUserSource err: %v", err)
}
// --
// -- -- Tests
type args struct {
ctx context.Context
id models.UserSourceID
}
var tests = []struct {
name string
args args
wantErr bool
}{
{
name: "Test 01: Delete Valid UserSource",
args: args{
ctx: ctx,
id: validUSerSource.ID,
},
wantErr: false,
},
{
name: "Test 02: Delete not existed UserSource",
args: args{
ctx: ctx,
id: validUSerSource.ID,
},
wantErr: false,
},
{
name: "Test 03: Empty UserSourceID",
args: args{
ctx: ctx,
id: "",
},
wantErr: true,
},
{
name: "Test 04: Short UserSourceID",
args: args{
ctx: ctx,
id: "111",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := DeleteUserSource(tt.args.ctx, tt.args.id); (err != nil) != tt.wantErr {
t.Errorf("DeleteUserSource() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func checkUserSourceID(got models.UserSource, want models.UserSource) bool {
if got.ID != want.ID {
return false
}
return true
}

214
pkg/database/user_test.go Normal file
View File

@ -0,0 +1,214 @@
package database
import (
"context"
"fmt"
"testing"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/test"
"go.opentelemetry.io/contrib/bridges/otellogrus"
"go.opentelemetry.io/otel"
)
func TestCreateUser(t *testing.T) {
// Setup trow away container
ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx)
if err != nil {
logger.Fatalf("Could not start PostgreSQL container: %v", err)
}
client = gormDB
// Setup open telemetry
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
defer container.Terminate(ctx)
// Setup Tests
validUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: models.UserID(fmt.Sprintf("%025s", "User1"))}}
// Tests
type args struct {
ctx context.Context
user models.User
}
tests := []struct {
name string
args args
want models.User
wantErr bool
}{
{
name: "Test 01: Create Valid User",
args: args{
ctx: ctx,
user: validUser,
},
want: validUser,
wantErr: false,
},
{
name: "Test 02: Duplicate User",
args: args{
ctx: ctx,
user: validUser,
},
want: models.User{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CreateUser(tt.args.ctx, tt.args.user)
if (err != nil) != tt.wantErr {
t.Errorf("CreateUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkUserID(got, tt.want) {
t.Errorf("CreateUser() got = %v, want %v", got, tt.want)
}
})
}
}
func TestGetUserByID(t *testing.T) {
// Setup trow away container
ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx)
if err != nil {
logger.Fatalf("Could not start PostgreSQL container: %v", err)
}
client = gormDB
// Setup open telemetry
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
defer container.Terminate(ctx)
// Setup Tests
validUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: models.UserID(fmt.Sprintf("%025s", "User1"))}}
invalidUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: "invalid"}}
// Tests
type args struct {
ctx context.Context
id models.UserID
}
tests := []struct {
name string
args args
want models.User
wantErr bool
}{
{
name: "Test 01: Get Valid User",
args: args{
ctx: ctx,
id: validUser.ID,
},
want: validUser,
wantErr: false,
},
{
name: "Test 02: Get not existing User",
args: args{
ctx: ctx,
id: invalidUser.ID,
},
want: models.User{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetUserByID(tt.args.ctx, tt.args.id)
if (err != nil) != tt.wantErr {
t.Errorf("GetUserByID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !checkUserID(got, tt.want) {
t.Errorf("GetUserByID() got = %v, want %v", got, tt.want)
}
})
}
}
func TestDeleteUser(t *testing.T) {
// Setup trow away container
ctx := context.Background()
container, gormDB, err := test.StartPostgresContainer(ctx)
if err != nil {
logger.Fatalf("Could not start PostgreSQL container: %v", err)
}
client = gormDB
// Setup open telemetry
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
defer container.Terminate(ctx)
// Setup Tests
validUser := models.User{BaseModel: models.BaseModel[models.UserID]{ID: models.UserID(fmt.Sprintf("%025s", "User1"))}}
_, err = CreateUser(ctx, validUser)
if err != nil {
t.Fatal(err)
}
// Test
type args struct {
ctx context.Context
id models.UserID
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Test 01: Delete Existing User",
args: args{
ctx: ctx,
id: validUser.ID,
},
wantErr: false,
},
{
name: "Test 02: Delete not existing User",
args: args{
ctx: ctx,
id: validUser.ID,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := DeleteUser(tt.args.ctx, tt.args.id); (err != nil) != tt.wantErr {
t.Errorf("DeleteUser() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func checkUserID(got models.User, want models.User) bool {
if got.ID != want.ID {
return false
}
return true
}

View File

@ -15,6 +15,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
_ "github.com/lib/pq"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
@ -28,8 +29,7 @@ const (
func StartPostgresContainer(ctx context.Context) (*postgrescontainer.PostgresContainer, *gorm.DB, error) {
pgContainer, err := postgrescontainer.RunContainer(ctx,
testcontainers.WithImage("postgres:alpine"),
pgContainer, err := postgrescontainer.Run(ctx, "postgres:alpine",
postgrescontainer.WithDatabase(databaseName),
postgrescontainer.WithUsername(databaseUser),
postgrescontainer.WithPassword(databasePassword),
@ -81,6 +81,7 @@ func migrateDatabase(connectionString string) error {
func getGormDB(connectionString string) (*gorm.DB, error) {
return gorm.Open(postgres.Open(connectionString), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
TranslateError: true,
})
}