diff --git a/model/model_test.go b/model/model_test.go index 3fb489f1..2c933964 100644 --- a/model/model_test.go +++ b/model/model_test.go @@ -2,16 +2,19 @@ package model_test import ( + "context" "strconv" "strings" "testing" "time" + "github.com/Defacto2/server/internal/postgres/models" "github.com/Defacto2/server/model" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" ) func TestValidDateIssue(t *testing.T) { @@ -295,3 +298,315 @@ func TestValidNewV7(t *testing.T) { err = uuid.Validate(unid.String()) require.NoError(t, err) } + +func TestArtifacts(t *testing.T) { + t.Parallel() + ctx := context.TODO() + a := model.Artifacts{} + err := a.Public(ctx, nil) + assert.Error(t, err) + x, err := a.ByKey(ctx, nil, -1, -1) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ByNewest(ctx, nil, -1, -1) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ByUpdated(ctx, nil, -1, -1) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ByHidden(ctx, nil, -1, -1) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ByForApproval(ctx, nil, -1, -1) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ByMagicErr(ctx, nil, true) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ByTextPlatform(ctx, nil) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ByUnwanted(ctx, nil, -1, -1) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.Description(ctx, nil, nil) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.Filename(ctx, nil, nil) + assert.Nil(t, x) + assert.Error(t, err) + x, err = a.ID(ctx, nil, nil) + assert.Nil(t, x) + assert.Error(t, err) +} + +func TestCount(t *testing.T) { + t.Parallel() + ctx := context.TODO() + _, _, _, err := model.Counts(ctx, nil) + assert.Error(t, err) + _, err = model.CategoryCount(ctx, nil, "") + assert.Error(t, err) + _, err = model.CategoryByteSum(ctx, nil, "") + assert.Error(t, err) + _, err = model.ClassificationCount(ctx, nil, "", "") + assert.Error(t, err) + _, err = model.PlatformCount(ctx, nil, "") + assert.Error(t, err) + _, err = model.PlatformByteSum(ctx, nil, "") + assert.Error(t, err) + _, err = model.ReleaserByteSum(ctx, nil, "") + assert.Error(t, err) +} + +func TestDelete(t *testing.T) { + t.Parallel() + ctx := context.TODO() + err := model.DeleteOne(ctx, nil, -1) + assert.Error(t, err) +} + +func TestExists(t *testing.T) { + t.Parallel() + ctx := context.TODO() + _, err := model.DemozooExists(ctx, nil, -1) + assert.Error(t, err) + _, err = model.PouetExists(ctx, nil, -1) + assert.Error(t, err) + _, err = model.SHA384Exists(ctx, nil, nil) + assert.Error(t, err) + _, err = model.HashExists(ctx, nil, "") + assert.Error(t, err) +} + +func TestFilter(t *testing.T) { + t.Parallel() + ctx := context.TODO() + + // Define a slice of structs that implement Stat and List methods + models := []interface { + Stat(context.Context, boil.ContextExecutor) error + List(context.Context, boil.ContextExecutor, int, int) (models.FileSlice, error) + }{ + &model.Advert{}, + &model.Announcement{}, + &model.Ansi{}, + &model.AnsiBrand{}, + &model.AnsiBBS{}, + &model.AnsiFTP{}, + &model.AnsiNfo{}, + &model.AnsiPack{}, + &model.BBS{}, + &model.BBStro{}, + &model.BBSImage{}, + &model.BBSText{}, + &model.Database{}, + &model.Demoscene{}, + &model.Drama{}, + &model.FTP{}, + &model.Hack{}, + &model.HowTo{}, + &model.HTML{}, + &model.Image{}, + &model.ImagePack{}, + &model.Intro{}, + &model.IntroMsDos{}, + &model.IntroWindows{}, + &model.Installer{}, + &model.Java{}, + &model.JobAdvert{}, + &model.Linux{}, + &model.Magazine{}, + &model.Macos{}, + &model.MsDosPack{}, + &model.Music{}, + &model.NewsArticle{}, + &model.Nfo{}, + &model.NfoTool{}, + &model.PDF{}, + &model.Proof{}, + &model.Restrict{}, + &model.Script{}, + &model.Standard{}, + &model.Takedown{}, + &model.Text{}, + &model.TextAmiga{}, + &model.TextApple2{}, + &model.TextAtariST{}, + &model.TextPack{}, + &model.Tool{}, + &model.TrialCrackme{}, + &model.Video{}, + &model.Windows{}, + &model.WindowsPack{}, + } + for _, m := range models { + err := m.Stat(ctx, nil) + assert.Error(t, err) + _, err = m.List(ctx, nil, -1, -1) + assert.Error(t, err) + } +} + +func TestInsert(t *testing.T) { + t.Parallel() + ctx := context.TODO() + _, _, err := model.InsertDemozoo(ctx, nil, -1) + assert.Error(t, err) + _, _, err = model.InsertPouet(ctx, nil, -1) + assert.Error(t, err) + _, _, err = model.InsertUpload(ctx, nil, nil, "") + assert.Error(t, err) +} + +func TestModel(t *testing.T) { + t.Parallel() + _, err := model.JsDosBinary(nil) + assert.Error(t, err) + _, err = model.JsDosConfig(nil) + assert.Error(t, err) + _, err = model.JsDosCommand(nil) + assert.Error(t, err) +} + +func TestOne(t *testing.T) { + t.Parallel() + ctx := context.TODO() + _, err := model.One(ctx, nil, false, -1) + assert.Error(t, err) + _, err = model.OneEditByKey(ctx, nil, "") + assert.Error(t, err) + _, err = model.OneByUUID(ctx, nil, false, "") + assert.Error(t, err) + _, err = model.OneFile(ctx, nil, -1) + assert.Error(t, err) + _, err = model.OneFileByKey(ctx, nil, "") + assert.Error(t, err) + _, _, err = model.OneDemozoo(ctx, nil, -1) + assert.Error(t, err) + _, _, err = model.OnePouet(ctx, nil, -1) + assert.Error(t, err) +} + +func TestReleaser(t *testing.T) { + t.Parallel() + ctx := context.TODO() + r := model.ReleaserNames{} + err := r.Distinct(ctx, nil) + assert.Error(t, err) + err = r.DistinctGroups(ctx, nil) + assert.Error(t, err) + rls := model.Releasers{} + _, err = rls.Where(ctx, nil, "") + assert.Error(t, err) + err = rls.Limit(ctx, nil, 0, -1, -1) + assert.Error(t, err) + err = rls.Similar(ctx, nil, 0) + assert.Error(t, err) + err = rls.SimilarMagazine(ctx, nil, 0) + assert.Error(t, err) + err = rls.FTP(ctx, nil) + assert.Error(t, err) + err = rls.MagazineAZ(ctx, nil) + assert.Error(t, err) + err = rls.Magazine(ctx, nil) + assert.Error(t, err) + rls.Slugs() +} + +func TestScener(t *testing.T) { + t.Parallel() + ctx := context.TODO() + s := model.Sceners{} + err := s.Distinct(ctx, nil) + assert.Error(t, err) + err = s.Writer(ctx, nil) + assert.Error(t, err) + err = s.Artist(ctx, nil) + assert.Error(t, err) + err = s.Coder(ctx, nil) + assert.Error(t, err) + err = s.Musician(ctx, nil) + assert.Error(t, err) + x := s.Sort() + assert.Empty(t, x) + var o model.Scener + _, err = o.Where(ctx, nil, "") + assert.Error(t, err) +} + +func TestSummary(t *testing.T) { + t.Parallel() + ctx := context.TODO() + s := model.Summary{} + err := s.ByDescription(ctx, nil, nil) + assert.Error(t, err) + err = s.ByFilename(ctx, nil, nil) + assert.Error(t, err) + err = s.ByForApproval(ctx, nil) + assert.Error(t, err) + err = s.ByHidden(ctx, nil) + assert.Error(t, err) + err = s.ByPublic(ctx, nil) + assert.Error(t, err) + err = s.ByReleaser(ctx, nil, "") + assert.Error(t, err) + err = s.ByUnwanted(ctx, nil) + assert.Error(t, err) + + err = s.ByMatch(ctx, nil, "") + assert.Error(t, err) + for uri := range s.Matches() { + err = s.ByMatch(ctx, nil, uri) + assert.Error(t, err) + } +} + +func TestUpdateBoolFrom(t *testing.T) { + t.Parallel() + err := model.UpdateBoolFrom(nil, -1, -1, false) + assert.Error(t, err) + err = model.UpdateEmulateRunProgram(nil, -1, "") + assert.Error(t, err) + err = model.UpdateEmulateMachine(nil, -1, "") + assert.Error(t, err) + err = model.UpdateEmulateCPU(nil, -1, "") + assert.Error(t, err) + err = model.UpdateEmulateSfx(nil, -1, "") + assert.Error(t, err) + err = model.UpdateInt64From(nil, -1, -1, "") + assert.Error(t, err) + err = model.UpdateStringFrom(nil, -1, -1, "") + assert.Error(t, err) + err = model.UpdateCreators(nil, -1, "", "", "", "") + assert.Error(t, err) + err = model.UpdateLinks(nil, -1, "", "", "", "", "", -1, -1) + assert.Error(t, err) + err = model.UpdateClassification(nil, -1, "", "") + assert.Error(t, err) + err = model.UpdateDateIssued(nil, -1, "", "", "") + assert.Error(t, err) + err = model.UpdateOffline(nil, -1) + assert.Error(t, err) + err = model.UpdatePlatform(nil, -1, "") + assert.Error(t, err) + err = model.UpdateOnline(nil, -1) + assert.Error(t, err) + err = model.UpdateReleasers(nil, -1, "") + assert.Error(t, err) + x := null.Int16From(-1) + ctx := context.TODO() + err = model.UpdateYMD(ctx, nil, -1, x, x, x) + assert.Error(t, err) + err = model.UpdateMagic(ctx, nil, -1, "") + assert.Error(t, err) + fu := model.FileUpload{} + err = fu.Update(ctx, nil, 1) + assert.Error(t, err) +} + +func TestValidate(t *testing.T) { + t.Parallel() + err := model.Validate(nil) + assert.Error(t, err) +} diff --git a/model/one.go b/model/one.go index 37df04b5..1869c753 100644 --- a/model/one.go +++ b/model/one.go @@ -19,6 +19,9 @@ import ( // One retrieves a single file record from the database using the record key. // This function can return records that have been marked as deleted. func One(ctx context.Context, exec boil.ContextExecutor, deleted bool, key int) (*models.File, error) { + if invalidExec(exec) { + return nil, fmt.Errorf("model one: %w", ErrDB) + } if key < -1 { return nil, fmt.Errorf("key value %d: %w", key, ErrKey) } @@ -65,6 +68,9 @@ func OneByUUID(ctx context.Context, exec boil.ContextExecutor, deleted bool, uid // OneFile retrieves a single file record from the database using the record key. // This function will also return records that have been marked as deleted. func OneFile(ctx context.Context, exec boil.ContextExecutor, id int64) (*models.File, error) { + if invalidExec(exec) { + return nil, fmt.Errorf("model one: %w", ErrDB) + } f, err := models.Files(models.FileWhere.ID.EQ(id), qm.WithDeleted()).One(ctx, exec) if err != nil { return nil, fmt.Errorf("models file one %d: %w", id, err) @@ -81,6 +87,9 @@ func OneFileByKey(ctx context.Context, exec boil.ContextExecutor, key string) (* // This function will also return records that have been marked as deleted and flag those with the boolean. // If the record is not found then the function will return an ID of 0 but without an error. func OneDemozoo(ctx context.Context, exec boil.ContextExecutor, id int64) (bool, int64, error) { + if invalidExec(exec) { + return false, 0, fmt.Errorf("model one demozoo: %w", ErrDB) + } f, err := models.Files( qm.Select("id", "deletedat"), models.FileWhere.WebIDDemozoo.EQ(null.Int64From(id)), diff --git a/model/summary.go b/model/summary.go index 689fb0f1..d687f05a 100644 --- a/model/summary.go +++ b/model/summary.go @@ -152,10 +152,10 @@ func (s *Summary) Update(c, b, y0, y1 int) { s.MaxYear = sql.NullInt16{Int16: int16(y1)} } -// ByMatch returns the summary statistics for the named uri. -func (s *Summary) ByMatch(ctx context.Context, exec boil.ContextExecutor, uri string) error { - type statFunc func(context.Context, boil.ContextExecutor) error - stat := map[string]statFunc{ +type statFunc func(context.Context, boil.ContextExecutor) error + +func (s *Summary) Matches() map[string]statFunc { + return map[string]statFunc{ "text-amiga": s.textAmiga, "text-apple2": s.textApple2, "text-atari-st": s.textAtariST, @@ -209,6 +209,11 @@ func (s *Summary) ByMatch(ctx context.Context, exec boil.ContextExecutor, uri st "intro-msdos": s.introMsdos, "intro-windows": s.introWindows, } +} + +// ByMatch returns the summary statistics for the named uri. +func (s *Summary) ByMatch(ctx context.Context, exec boil.ContextExecutor, uri string) error { + stat := s.Matches() if update, match := stat[uri]; match { return update(ctx, exec) } diff --git a/model/update.go b/model/update.go index 9a5bde0e..104af6c2 100644 --- a/model/update.go +++ b/model/update.go @@ -63,10 +63,13 @@ func UpdateEmulateBroken(db *sql.DB, id int64, val bool) error { // UpdateBoolFrom updates the column bool from value with val. // The boolFrom columns are table columns that can either be null, empty, or have a smallint value. func UpdateBoolFrom(db *sql.DB, column boolFrom, id int64, val bool) error { + if db == nil { + return fmt.Errorf("update bool from: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { - return fmt.Errorf("updateboolfrom: %w", err) + return fmt.Errorf("update bool from: %w", err) } f, err := OneFile(ctx, tx, id) if err != nil { @@ -97,6 +100,9 @@ func UpdateBoolFrom(db *sql.DB, column boolFrom, id int64, val bool) error { } func UpdateEmulateRunProgram(db *sql.DB, id int64, val string) error { + if db == nil { + return fmt.Errorf("update emulate run program: %w", ErrDB) + } s := strings.TrimSpace(strings.ToUpper(val)) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -302,6 +308,9 @@ func UpdateYouTube(db *sql.DB, id int64, val string) error { // The demozooProd and pouetProd values are validated to be within a sane range // and a zero value will set their column's to null. func UpdateInt64From(db *sql.DB, column int64From, id int64, val string) error { + if db == nil { + return fmt.Errorf("update int64 from: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { @@ -369,6 +378,9 @@ const ( // UpdateStringFrom updates the column string from value with val. // The stringFrom columns are table columns that can either be null, empty, or have a string value. func UpdateStringFrom(db *sql.DB, column stringFrom, id int64, val string) error { + if db == nil { + return fmt.Errorf("update string from: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { @@ -437,24 +449,27 @@ func updateStringCases(f *models.File, column stringFrom, val string) error { // // UpdateCreators updates the text, illustration, program, and audio credit columns with the values provided. func UpdateCreators(db *sql.DB, id int64, text, ill, prog, audio string) error { + if db == nil { + return fmt.Errorf("update creators: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { - return fmt.Errorf("updatecreators: %w", err) + return fmt.Errorf("update creators: %w", err) } f, err := OneFile(ctx, tx, id) if err != nil { - return fmt.Errorf("updatecreators find file, %d: %w", id, err) + return fmt.Errorf("update creators find file, %d: %w", id, err) } f.CreditText = null.StringFrom(text) f.CreditIllustration = null.StringFrom(ill) f.CreditProgram = null.StringFrom(prog) f.CreditAudio = null.StringFrom(audio) if _, err = f.Update(ctx, tx, boil.Infer()); err != nil { - return fmt.Errorf("%s: %w", "updatecreators", err) + return fmt.Errorf("%s: %w", "update creators", err) } if err = tx.Commit(); err != nil { - return fmt.Errorf("updatecreators: %w", err) + return fmt.Errorf("update creators: %w", err) } return nil } @@ -464,6 +479,9 @@ func UpdateLinks(db *sql.DB, id int64, youtube, colors16, github, relations, sites string, demozoo, pouet int64, ) error { + if db == nil { + return fmt.Errorf("update links: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { @@ -493,6 +511,9 @@ func UpdateLinks(db *sql.DB, id int64, // It takes an ID, platform, and tag as parameters and returns an error if any. // Both platform and tag must be valid values. func UpdateClassification(db *sql.DB, id int64, platform, tag string) error { + if db == nil { + return fmt.Errorf("update classification: %w", ErrDB) + } p, t := tags.TagByURI(platform), tags.TagByURI(tag) if p == -1 { return fmt.Errorf("%s: %w", platform, ErrPlatform) @@ -529,6 +550,9 @@ func UpdateClassification(db *sql.DB, id int64, platform, tag string) error { // UpdateDateIssued updates the date issued year, month and day columns with the values provided. // Columns updated are DateIssuedYear, DateIssuedMonth, and DateIssuedDay. func UpdateDateIssued(db *sql.DB, id int64, y, m, d string) error { + if db == nil { + return fmt.Errorf("update date issued: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { @@ -553,6 +577,9 @@ func UpdateDateIssued(db *sql.DB, id int64, y, m, d string) error { // UpdateOffline updates the record to be offline and inaccessible to the public. func UpdateOffline(db *sql.DB, id int64) error { + if db == nil { + return fmt.Errorf("update offline: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { @@ -576,6 +603,9 @@ func UpdateOffline(db *sql.DB, id int64) error { // UpdateOnline updates the record to be online and public. func UpdateOnline(db *sql.DB, id int64) error { + if db == nil { + return fmt.Errorf("update online: %w", ErrDB) + } ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { @@ -600,6 +630,9 @@ func UpdateOnline(db *sql.DB, id int64) error { // Two releases can be separated by a + (plus) character. // The columns updated are GroupBrandFor and GroupBrandBy. func UpdateReleasers(db *sql.DB, id int64, val string) error { + if db == nil { + return fmt.Errorf("update releasers: %w", ErrDB) + } const max = 2 val = strings.TrimSpace(val) s := strings.Split(val, "+") @@ -640,6 +673,9 @@ func UpdateReleasers(db *sql.DB, id int64, val string) error { // UpdateYMD updates the date issued year, month and day columns with the values provided. func UpdateYMD(ctx context.Context, exec boil.ContextExecutor, id int64, y, m, d null.Int16) error { + if invalidExec(exec) { + return fmt.Errorf("updateymd %w: %d", ErrDB, id) + } if id <= 0 { return fmt.Errorf("updateymd id value %w: %d", ErrKey, id) } @@ -667,6 +703,9 @@ func UpdateYMD(ctx context.Context, exec boil.ContextExecutor, id int64, y, m, d // UpdateMagic updates the file magictype (magic number) column with the magic value provided. func UpdateMagic(ctx context.Context, exec boil.ContextExecutor, id int64, magic string) error { + if invalidExec(exec) { + return fmt.Errorf("updatemagic %w: %d", ErrDB, id) + } if id <= 0 { return fmt.Errorf("updatemagic id value %w: %d", ErrKey, id) }