From 63ddeb0879f1c77923f9656ba9764d346eba7f5c Mon Sep 17 00:00:00 2001 From: kim Date: Wed, 27 Nov 2024 14:44:01 +0000 Subject: [PATCH] add StatusEdit{} test models --- .../20241121121623_enum_strings_to_ints.go | 94 -------- internal/db/bundb/migrations/util.go | 94 ++++++++ testrig/testmodels.go | 223 +++++++++++++++++- 3 files changed, 316 insertions(+), 95 deletions(-) diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go index 10ae95c17..7621ddc6c 100644 --- a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go +++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go @@ -19,12 +19,9 @@ package migrations import ( "context" - "errors" old_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" new_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/util" "github.com/uptrace/bun" @@ -128,97 +125,6 @@ func init() { } } -// convertEnums performs a transaction that converts -// a table's column of our old-style enums (strings) to -// more performant and space-saving integer types. -func convertEnums[OldType ~string, NewType ~int16]( - ctx context.Context, - tx bun.Tx, - table string, - column string, - mapping map[OldType]NewType, - defaultValue *NewType, -) error { - if len(mapping) == 0 { - return errors.New("empty mapping") - } - - // Generate new column name. - newColumn := column + "_new" - - log.Infof(ctx, "converting %s.%s enums; "+ - "this may take a while, please don't interrupt!", - table, column, - ) - - // Ensure a default value. - if defaultValue == nil { - var zero NewType - defaultValue = &zero - } - - // Add new column to database. - if _, err := tx.NewAddColumn(). - Table(table). - ColumnExpr("? SMALLINT NOT NULL DEFAULT ?", - bun.Ident(newColumn), - *defaultValue). - Exec(ctx); err != nil { - return gtserror.Newf("error adding new column: %w", err) - } - - // Get a count of all in table. - total, err := tx.NewSelect(). - Table(table). - Count(ctx) - if err != nil { - return gtserror.Newf("error selecting total count: %w", err) - } - - var updated int - for old, new := range mapping { - - // Update old to new values. - res, err := tx.NewUpdate(). - Table(table). - Where("? = ?", bun.Ident(column), old). - Set("? = ?", bun.Ident(newColumn), new). - Exec(ctx) - if err != nil { - return gtserror.Newf("error updating old column values: %w", err) - } - - // Count number items updated. - n, _ := res.RowsAffected() - updated += int(n) - } - - // Check total updated. - if total != updated { - log.Warnf(ctx, "total=%d does not match updated=%d", total, updated) - } - - // Drop the old column from table. - if _, err := tx.NewDropColumn(). - Table(table). - ColumnExpr("?", bun.Ident(column)). - Exec(ctx); err != nil { - return gtserror.Newf("error dropping old column: %w", err) - } - - // Rename new to old name. - if _, err := tx.NewRaw( - "ALTER TABLE ? RENAME COLUMN ? TO ?", - bun.Ident(table), - bun.Ident(newColumn), - bun.Ident(column), - ).Exec(ctx); err != nil { - return gtserror.Newf("error renaming new column: %w", err) - } - - return nil -} - // visibilityEnumMapping maps old Visibility enum values to their newer integer type. func visibilityEnumMapping[T ~string]() map[T]new_gtsmodel.Visibility { return map[T]new_gtsmodel.Visibility{ diff --git a/internal/db/bundb/migrations/util.go b/internal/db/bundb/migrations/util.go index 40b707019..b917c566b 100644 --- a/internal/db/bundb/migrations/util.go +++ b/internal/db/bundb/migrations/util.go @@ -19,12 +19,15 @@ package migrations import ( "context" + "errors" "fmt" "reflect" "strconv" "strings" "codeberg.org/gruf/go-byteutil" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect" "github.com/uptrace/bun/dialect/feature" @@ -32,6 +35,97 @@ import ( "github.com/uptrace/bun/schema" ) +// convertEnums performs a transaction that converts +// a table's column of our old-style enums (strings) to +// more performant and space-saving integer types. +func convertEnums[OldType ~string, NewType ~int16]( + ctx context.Context, + tx bun.Tx, + table string, + column string, + mapping map[OldType]NewType, + defaultValue *NewType, +) error { + if len(mapping) == 0 { + return errors.New("empty mapping") + } + + // Generate new column name. + newColumn := column + "_new" + + log.Infof(ctx, "converting %s.%s enums; "+ + "this may take a while, please don't interrupt!", + table, column, + ) + + // Ensure a default value. + if defaultValue == nil { + var zero NewType + defaultValue = &zero + } + + // Add new column to database. + if _, err := tx.NewAddColumn(). + Table(table). + ColumnExpr("? SMALLINT NOT NULL DEFAULT ?", + bun.Ident(newColumn), + *defaultValue). + Exec(ctx); err != nil { + return gtserror.Newf("error adding new column: %w", err) + } + + // Get a count of all in table. + total, err := tx.NewSelect(). + Table(table). + Count(ctx) + if err != nil { + return gtserror.Newf("error selecting total count: %w", err) + } + + var updated int + for old, new := range mapping { + + // Update old to new values. + res, err := tx.NewUpdate(). + Table(table). + Where("? = ?", bun.Ident(column), old). + Set("? = ?", bun.Ident(newColumn), new). + Exec(ctx) + if err != nil { + return gtserror.Newf("error updating old column values: %w", err) + } + + // Count number items updated. + n, _ := res.RowsAffected() + updated += int(n) + } + + // Check total updated. + if total != updated { + log.Warnf(ctx, "total=%d does not match updated=%d", total, updated) + } + + // Drop the old column from table. + if _, err := tx.NewDropColumn(). + Table(table). + ColumnExpr("?", bun.Ident(column)). + Exec(ctx); err != nil { + return gtserror.Newf("error dropping old column: %w", err) + } + + // Rename new to old name. + if _, err := tx.NewRaw( + "ALTER TABLE ? RENAME COLUMN ? TO ?", + bun.Ident(table), + bun.Ident(newColumn), + bun.Ident(column), + ).Exec(ctx); err != nil { + return gtserror.Newf("error renaming new column: %w", err) + } + + return nil +} + // getBunColumnDef generates a column definition string for the SQL table represented by // Go type, with the SQL column represented by the given Go field name. This ensures when // adding a new column for table by migration that it will end up as bun would create it. diff --git a/testrig/testmodels.go b/testrig/testmodels.go index 856381cd8..2076cf6e1 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -1043,6 +1043,25 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment { Header: util.Ptr(false), Cached: util.Ptr(true), }, + "local_account_2_status_9_attachment_1": { + ID: "01JDQ164HM08SGJ7ZEK9003Z4B", + StatusID: "01JDPZEZ77X1NX0TY9M10BK1HM", + URL: "http://localhost:8080/fileserver/01FHMQX3GAABWSM0S2VZEC2SWC/attachment/original/01HE88YG74PVAB81PX2XA9F3FG.mp3", + RemoteURL: "http://example.org/fileserver/01HE7Y659ZWZ02JM4AWYJZ176Q/attachment/original/01HE892Y8ZS68TQCNPX7J888P3.mp3", + CreatedAt: TimeMustParse("2024-11-01T10:01:00+02:00"), + UpdatedAt: TimeMustParse("2024-11-01T10:01:00+02:00"), + Type: gtsmodel.FileTypeUnknown, + FileMeta: gtsmodel.FileMeta{}, + AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", + Description: "Jolly salsa song, public domain.", + Blurhash: "", + Processing: gtsmodel.ProcessingStatusProcessed, + File: gtsmodel.File{}, + Thumbnail: gtsmodel.Thumbnail{RemoteURL: ""}, + Avatar: util.Ptr(false), + Header: util.Ptr(false), + Cached: util.Ptr(false), + }, "remote_account_1_status_1_attachment_1": { ID: "01FVW7RXPQ8YJHTEXYPE7Q8ZY0", StatusID: "01FVW7JHQFSFK166WWKR8CBA6M", @@ -1739,6 +1758,32 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Federated: util.Ptr(true), ActivityStreamsType: ap.ObjectNote, }, + "local_account_1_status_9": { + ID: "01JDPZC707CKDN8N4QVWM4Z1NR", + URI: "http://localhost:8080/users/the_mighty_zork/statuses/01JDPZC707CKDN8N4QVWM4Z1NR", + URL: "http://localhost:8080/@the_mighty_zork/statuses/01JDPZC707CKDN8N4QVWM4Z1NR", + Content: "

this is the latest revision of the status, with a content-warning

", + Text: "this is the latest revision of the status, with a content-warning", + ContentWarning: "edited status", + AttachmentIDs: nil, + CreatedAt: TimeMustParse("2024-11-01T11:00:00+02:00"), + UpdatedAt: TimeMustParse("2024-11-01T11:02:00+02:00"), + Local: util.Ptr(true), + AccountURI: "http://localhost:8080/users/the_mighty_zork", + AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", + InReplyToID: "", + InReplyToAccountID: "", + InReplyToURI: "", + BoostOfID: "", + ThreadID: "", + EditIDs: []string{"01JDPZCZ2Y9KSGZW0R7ZG8T8Y2", "01JDPZDADMD1T9HKF94RECF7PP"}, + Visibility: gtsmodel.VisibilityPublic, + Sensitive: util.Ptr(false), + Language: "en", + CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG", + Federated: util.Ptr(true), + ActivityStreamsType: ap.ObjectNote, + }, "local_account_2_status_1": { ID: "01F8MHBQCBTDKN6X5VHGMMN4MA", URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA", @@ -1967,6 +2012,32 @@ func NewTestStatuses() map[string]*gtsmodel.Status { PollID: "01HEN2QB5NR4NCEHGYC3HN84K6", PendingApproval: util.Ptr(false), }, + "local_account_2_status_9": { + ID: "01JDPZEZ77X1NX0TY9M10BK1HM", + URI: "http://localhost:8080/users/1happyturtle/statuses/01JDPZEZ77X1NX0TY9M10BK1HM", + URL: "http://localhost:8080/@1happyturtle/statuses/01JDPZEZ77X1NX0TY9M10BK1HM", + Content: "

now edited to bring back the previous edit's media!

", + Text: "now edited to bring back the previous edit's media!", + ContentWarning: "edit with media attachments", + AttachmentIDs: []string{"01JDQ164HM08SGJ7ZEK9003Z4B"}, + CreatedAt: TimeMustParse("2024-11-01T10:00:00+02:00"), + UpdatedAt: TimeMustParse("2024-11-01T10:03:00+02:00"), + Local: util.Ptr(true), + AccountURI: "http://localhost:8080/users/the_mighty_zork", + AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", + InReplyToID: "", + InReplyToAccountID: "", + InReplyToURI: "", + BoostOfID: "", + ThreadID: "", + EditIDs: []string{"01JDPZPBXAX0M02YSEPB21KX4R", "01JDPZPJHKP7E3M0YQXEXPS1YT", "01JDPZPY3F85Y7B78ETRXEMWD9"}, + Visibility: gtsmodel.VisibilityPublic, + Sensitive: util.Ptr(false), + Language: "en", + CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ", + Federated: util.Ptr(true), + ActivityStreamsType: ap.ObjectNote, + }, "remote_account_1_status_1": { ID: "01FVW7JHQFSFK166WWKR8CBA6M", URI: "http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M", @@ -2042,6 +2113,33 @@ func NewTestStatuses() map[string]*gtsmodel.Status { PollID: "01HEWV1GW2D49R919NPEDXPTZ5", PendingApproval: util.Ptr(false), }, + "remote_account_1_status_4": { + ID: "01JDQ07JZTX9CMDJP67CNA71YD", + URI: "http://fossbros-anonymous.io/users/foss_satan/statuses/______", + URL: "http://fossbros-anonymous.io/@foss_satan/statuses/______", + Content: "

this is the latest status edit without poll change

", + Text: "this is the latest status edit without poll change", + ContentWarning: "", + AttachmentIDs: nil, + CreatedAt: TimeMustParse("2024-11-01T09:00:00+02:00"), + UpdatedAt: TimeMustParse("2024-11-01T09:02:00+02:00"), + Local: util.Ptr(false), + AccountURI: "http://fossbros-anonymous.io/users/foss_satan", + AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX", + InReplyToID: "", + InReplyToAccountID: "", + InReplyToURI: "", + BoostOfID: "", + ThreadID: "", + EditIDs: []string{"01JDQ07ZZ4FGP13YN8TF63P5A6", "01JDQ08AYQC0G6413VAHA51CV9"}, + PollID: "01JDQ0EZ5HM9T4WXRQ5WSVD40J", + Visibility: gtsmodel.VisibilityPublic, + Sensitive: util.Ptr(false), + Language: "en", + CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ", + Federated: util.Ptr(true), + ActivityStreamsType: ap.ObjectNote, + }, "remote_account_2_status_1": { ID: "01HE7XJ1CG84TBKH5V9XKBVGF5", URI: "http://example.org/users/Some_User/statuses/01HE7XJ1CG84TBKH5V9XKBVGF5", @@ -2125,6 +2223,19 @@ func NewTestPolls() map[string]*gtsmodel.Poll { ClosedAt: time.Time{}, Closing: false, }, + "remote_account_1_status_4_poll": { + ID: "01JDQ0EZ5HM9T4WXRQ5WSVD40J", + Multiple: util.Ptr(false), + HideCounts: util.Ptr(false), + Options: []string{"yes", "no", "maybe", "i don't know", "can you repeat the question"}, + Votes: []int{0, 0, 0, 0, 2}, + Voters: util.Ptr(2), + StatusID: "01JDQ07JZTX9CMDJP67CNA71YD", + // empty expiry AND closed date, i.e. no end + ExpiresAt: time.Time{}, + ClosedAt: time.Time{}, + Closing: false, + }, } } @@ -2184,6 +2295,24 @@ func NewTestPollVotes() map[string]*gtsmodel.PollVote { Poll: nil, CreatedAt: TimeMustParse("2021-09-11T11:47:37+02:00"), }, + "remote_account_1_status_4_poll_vote_local_account_1": { + ID: "01JDQ0SX9QVVFHS7P8M1PA3SVG", + Choices: []int{4}, + AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", + Account: nil, + PollID: "01JDQ0EZ5HM9T4WXRQ5WSVD40J", + Poll: nil, + CreatedAt: TimeMustParse("2024-11-01T09:01:30+02:00"), + }, + "remote_account_1_status_4_poll_vote_local_account_2": { + ID: "01JDQ0T3EEDN7SAVBQMQP4PR12", + Choices: []int{4}, + AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", + Account: nil, + PollID: "01JDQ0EZ5HM9T4WXRQ5WSVD40J", + Poll: nil, + CreatedAt: TimeMustParse("2024-11-01T09:02:30+02:00"), + }, } } @@ -3485,7 +3614,99 @@ func NewTestInteractionRequests() map[string]*gtsmodel.InteractionRequest { } func NewTestStatusEdits() map[string]*gtsmodel.StatusEdit { - panic("TODO") + return map[string]*gtsmodel.StatusEdit{ + "local_account_1_status_9_edit_1": { + ID: "01JDPZCZ2Y9KSGZW0R7ZG8T8Y2", + Content: "

this is the original status

", + ContentWarning: "", + Text: "this is the original status", + Language: "en", + Sensitive: util.Ptr(false), + AttachmentIDs: nil, + PollOptions: nil, + PollVotes: nil, + StatusID: "01JDPZC707CKDN8N4QVWM4Z1NR", + CreatedAt: TimeMustParse("2024-11-01T11:00:00+02:00"), + }, + "local_account_1_status_9_edit_2": { + ID: "01JDPZDADMD1T9HKF94RECF7PP", + Content: "

this is the first status edit! now with content-warning

", + ContentWarning: "edited status", + Text: "this is the first status edit! now with content-warning", + Language: "en", + Sensitive: util.Ptr(false), + AttachmentIDs: nil, + PollOptions: nil, + PollVotes: nil, + StatusID: "01JDPZC707CKDN8N4QVWM4Z1NR", + CreatedAt: TimeMustParse("2024-11-01T11:01:00+02:00"), + }, + "local_account_2_status_9_edit_1": { + ID: "01JDPZPBXAX0M02YSEPB21KX4R", + Content: "

this is the original status

", + ContentWarning: "", + Text: "this is the original status", + Language: "en", + Sensitive: util.Ptr(false), + AttachmentIDs: nil, + PollOptions: nil, + PollVotes: nil, + StatusID: "01JDPZEZ77X1NX0TY9M10BK1HM", + CreatedAt: TimeMustParse("2024-11-01T10:00:00+02:00"), + }, + "local_account_2_status_9_edit_2": { + ID: "01JDPZPJHKP7E3M0YQXEXPS1YT", + Content: "

now edited to have some media!

", + ContentWarning: "edit with media attachments", + Text: "now edited to have some media!", + Language: "en", + Sensitive: util.Ptr(true), + AttachmentIDs: []string{"01JDQ164HM08SGJ7ZEK9003Z4B"}, + PollOptions: nil, + PollVotes: nil, + StatusID: "01JDPZEZ77X1NX0TY9M10BK1HM", + CreatedAt: TimeMustParse("2024-11-01T10:01:00+02:00"), + }, + "local_account_2_status_9_edit_3": { + ID: "01JDPZPY3F85Y7B78ETRXEMWD9", + Content: "

now edited to remove the media

", + ContentWarning: "edit missing previous media attachments", + Text: "now edited to remove the media", + Language: "en", + Sensitive: util.Ptr(false), + AttachmentIDs: nil, + PollOptions: nil, + PollVotes: nil, + StatusID: "01JDPZEZ77X1NX0TY9M10BK1HM", + CreatedAt: TimeMustParse("2024-11-01T10:02:00+02:00"), + }, + "remote_account_1_status_4_edit_1": { + ID: "01JDQ07ZZ4FGP13YN8TF63P5A6", + Content: "

this is the original status, with a poll!

", + ContentWarning: "", + Text: "this is the original status, with a poll!", + Language: "en", + Sensitive: util.Ptr(false), + AttachmentIDs: nil, + PollOptions: []string{"yes", "no", "spiderman"}, + PollVotes: []int{42, 42, 69}, + StatusID: "01JDQ07JZTX9CMDJP67CNA71YD", + CreatedAt: TimeMustParse("2024-11-01T09:00:00+02:00"), + }, + "remote_account_1_status_4_edit_2": { + ID: "01JDQ08AYQC0G6413VAHA51CV9", + Content: "

this is the first status edit! now with a different poll!

", + ContentWarning: "edited status", + Text: "this is the first status edit! now with a different poll!", + Language: "en", + Sensitive: util.Ptr(false), + AttachmentIDs: nil, + PollOptions: []string{"yes", "no", "maybe", "i don't know", "can you repeat the question"}, + PollVotes: []int{0, 0, 0, 0, 1}, + StatusID: "01JDQ07JZTX9CMDJP67CNA71YD", + CreatedAt: TimeMustParse("2024-11-01T09:01:00+02:00"), + }, + } } // GetSignatureForActivity prepares a mock HTTP request as if it were going to deliver activity to destination signed for privkey and pubKeyID, signs the request and returns the header values.