account_test.go (12702B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package bundb_test 19 20 import ( 21 "context" 22 "crypto/rand" 23 "crypto/rsa" 24 "errors" 25 "reflect" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/stretchr/testify/suite" 31 "github.com/superseriousbusiness/gotosocial/internal/ap" 32 "github.com/superseriousbusiness/gotosocial/internal/db" 33 "github.com/superseriousbusiness/gotosocial/internal/db/bundb" 34 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 35 "github.com/uptrace/bun" 36 ) 37 38 type AccountTestSuite struct { 39 BunDBStandardTestSuite 40 } 41 42 func (suite *AccountTestSuite) TestGetAccountStatuses() { 43 statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", false, false) 44 suite.NoError(err) 45 suite.Len(statuses, 5) 46 } 47 48 func (suite *AccountTestSuite) TestGetAccountStatusesPageDown() { 49 // get the first page 50 statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 2, false, false, "", "", false, false) 51 if err != nil { 52 suite.FailNow(err.Error()) 53 } 54 suite.Len(statuses, 2) 55 56 // get the second page 57 statuses, err = suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 2, false, false, statuses[len(statuses)-1].ID, "", false, false) 58 if err != nil { 59 suite.FailNow(err.Error()) 60 } 61 suite.Len(statuses, 2) 62 63 // get the third page 64 statuses, err = suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 2, false, false, statuses[len(statuses)-1].ID, "", false, false) 65 if err != nil { 66 suite.FailNow(err.Error()) 67 } 68 suite.Len(statuses, 1) 69 70 // try to get the last page (should be empty) 71 statuses, err = suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 2, false, false, statuses[len(statuses)-1].ID, "", false, false) 72 suite.ErrorIs(err, db.ErrNoEntries) 73 suite.Empty(statuses) 74 } 75 76 func (suite *AccountTestSuite) TestGetAccountStatusesExcludeRepliesAndReblogs() { 77 statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, true, true, "", "", false, false) 78 suite.NoError(err) 79 suite.Len(statuses, 5) 80 } 81 82 func (suite *AccountTestSuite) TestGetAccountStatusesExcludeRepliesAndReblogsPublicOnly() { 83 statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, true, true, "", "", false, true) 84 suite.NoError(err) 85 suite.Len(statuses, 1) 86 } 87 88 func (suite *AccountTestSuite) TestGetAccountStatusesMediaOnly() { 89 statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", true, false) 90 suite.NoError(err) 91 suite.Len(statuses, 1) 92 } 93 94 func (suite *AccountTestSuite) TestGetAccountBy() { 95 t := suite.T() 96 97 // Create a new context for this test. 98 ctx, cncl := context.WithCancel(context.Background()) 99 defer cncl() 100 101 // Sentinel error to mark avoiding a test case. 102 sentinelErr := errors.New("sentinel") 103 104 // isEqual checks if 2 account models are equal. 105 isEqual := func(a1, a2 gtsmodel.Account) bool { 106 // Clear populated sub-models. 107 a1.HeaderMediaAttachment = nil 108 a2.HeaderMediaAttachment = nil 109 a1.AvatarMediaAttachment = nil 110 a2.AvatarMediaAttachment = nil 111 a1.Emojis = nil 112 a2.Emojis = nil 113 114 // Clear database-set fields. 115 a1.CreatedAt = time.Time{} 116 a2.CreatedAt = time.Time{} 117 a1.UpdatedAt = time.Time{} 118 a2.UpdatedAt = time.Time{} 119 120 // Manually compare keys. 121 pk1 := a1.PublicKey 122 pv1 := a1.PrivateKey 123 pk2 := a2.PublicKey 124 pv2 := a2.PrivateKey 125 a1.PublicKey = nil 126 a1.PrivateKey = nil 127 a2.PublicKey = nil 128 a2.PrivateKey = nil 129 130 return reflect.DeepEqual(a1, a2) && 131 ((pk1 == nil && pk2 == nil) || pk1.Equal(pk2)) && 132 ((pv1 == nil && pv2 == nil) || pv1.Equal(pv2)) 133 } 134 135 for _, account := range suite.testAccounts { 136 for lookup, dbfunc := range map[string]func() (*gtsmodel.Account, error){ 137 "id": func() (*gtsmodel.Account, error) { 138 return suite.db.GetAccountByID(ctx, account.ID) 139 }, 140 141 "uri": func() (*gtsmodel.Account, error) { 142 return suite.db.GetAccountByURI(ctx, account.URI) 143 }, 144 145 "url": func() (*gtsmodel.Account, error) { 146 if account.URL == "" { 147 return nil, sentinelErr 148 } 149 return suite.db.GetAccountByURL(ctx, account.URL) 150 }, 151 152 "username@domain": func() (*gtsmodel.Account, error) { 153 return suite.db.GetAccountByUsernameDomain(ctx, account.Username, account.Domain) 154 }, 155 156 "username_upper@domain": func() (*gtsmodel.Account, error) { 157 return suite.db.GetAccountByUsernameDomain(ctx, strings.ToUpper(account.Username), account.Domain) 158 }, 159 160 "username_lower@domain": func() (*gtsmodel.Account, error) { 161 return suite.db.GetAccountByUsernameDomain(ctx, strings.ToLower(account.Username), account.Domain) 162 }, 163 164 "public_key_uri": func() (*gtsmodel.Account, error) { 165 if account.PublicKeyURI == "" { 166 return nil, sentinelErr 167 } 168 return suite.db.GetAccountByPubkeyID(ctx, account.PublicKeyURI) 169 }, 170 171 "inbox_uri": func() (*gtsmodel.Account, error) { 172 if account.InboxURI == "" { 173 return nil, sentinelErr 174 } 175 return suite.db.GetAccountByInboxURI(ctx, account.InboxURI) 176 }, 177 178 "outbox_uri": func() (*gtsmodel.Account, error) { 179 if account.OutboxURI == "" { 180 return nil, sentinelErr 181 } 182 return suite.db.GetAccountByOutboxURI(ctx, account.OutboxURI) 183 }, 184 185 "following_uri": func() (*gtsmodel.Account, error) { 186 if account.FollowingURI == "" { 187 return nil, sentinelErr 188 } 189 return suite.db.GetAccountByFollowingURI(ctx, account.FollowingURI) 190 }, 191 192 "followers_uri": func() (*gtsmodel.Account, error) { 193 if account.FollowersURI == "" { 194 return nil, sentinelErr 195 } 196 return suite.db.GetAccountByFollowersURI(ctx, account.FollowersURI) 197 }, 198 } { 199 200 // Clear database caches. 201 suite.state.Caches.Init() 202 203 t.Logf("checking database lookup %q", lookup) 204 205 // Perform database function. 206 checkAcc, err := dbfunc() 207 if err != nil { 208 if err == sentinelErr { 209 continue 210 } 211 212 t.Errorf("error encountered for database lookup %q: %v", lookup, err) 213 continue 214 } 215 216 // Check received account data. 217 if !isEqual(*checkAcc, *account) { 218 t.Errorf("account does not contain expected data: %+v", checkAcc) 219 continue 220 } 221 222 // Check that avatar attachment populated. 223 if account.AvatarMediaAttachmentID != "" && 224 (checkAcc.AvatarMediaAttachment == nil || checkAcc.AvatarMediaAttachment.ID != account.AvatarMediaAttachmentID) { 225 t.Errorf("account avatar media attachment not correctly populated for: %+v", account) 226 continue 227 } 228 229 // Check that header attachment populated. 230 if account.HeaderMediaAttachmentID != "" && 231 (checkAcc.HeaderMediaAttachment == nil || checkAcc.HeaderMediaAttachment.ID != account.HeaderMediaAttachmentID) { 232 t.Errorf("account header media attachment not correctly populated for: %+v", account) 233 continue 234 } 235 } 236 } 237 } 238 239 func (suite *AccountTestSuite) TestUpdateAccount() { 240 ctx := context.Background() 241 242 testAccount := suite.testAccounts["local_account_1"] 243 244 testAccount.DisplayName = "new display name!" 245 testAccount.EmojiIDs = []string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"} 246 247 err := suite.db.UpdateAccount(ctx, testAccount) 248 suite.NoError(err) 249 250 updated, err := suite.db.GetAccountByID(ctx, testAccount.ID) 251 suite.NoError(err) 252 suite.Equal("new display name!", updated.DisplayName) 253 suite.Equal([]string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}, updated.EmojiIDs) 254 suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second) 255 256 // get account without cache + make sure it's really in the db as desired 257 dbService, ok := suite.db.(*bundb.DBService) 258 if !ok { 259 panic("db was not *bundb.DBService") 260 } 261 262 noCache := >smodel.Account{} 263 err = dbService.GetConn(). 264 NewSelect(). 265 Model(noCache). 266 Where("? = ?", bun.Ident("account.id"), testAccount.ID). 267 Relation("AvatarMediaAttachment"). 268 Relation("HeaderMediaAttachment"). 269 Relation("Emojis"). 270 Scan(ctx) 271 272 suite.NoError(err) 273 suite.Equal("new display name!", noCache.DisplayName) 274 suite.Equal([]string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}, noCache.EmojiIDs) 275 suite.WithinDuration(time.Now(), noCache.UpdatedAt, 5*time.Second) 276 suite.NotNil(noCache.AvatarMediaAttachment) 277 suite.NotNil(noCache.HeaderMediaAttachment) 278 279 // update again to remove emoji associations 280 testAccount.EmojiIDs = []string{} 281 282 err = suite.db.UpdateAccount(ctx, testAccount) 283 suite.NoError(err) 284 285 updated, err = suite.db.GetAccountByID(ctx, testAccount.ID) 286 suite.NoError(err) 287 suite.Equal("new display name!", updated.DisplayName) 288 suite.Empty(updated.EmojiIDs) 289 suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second) 290 291 err = dbService.GetConn(). 292 NewSelect(). 293 Model(noCache). 294 Where("? = ?", bun.Ident("account.id"), testAccount.ID). 295 Relation("AvatarMediaAttachment"). 296 Relation("HeaderMediaAttachment"). 297 Relation("Emojis"). 298 Scan(ctx) 299 300 suite.NoError(err) 301 suite.Equal("new display name!", noCache.DisplayName) 302 suite.Empty(noCache.EmojiIDs) 303 suite.WithinDuration(time.Now(), noCache.UpdatedAt, 5*time.Second) 304 } 305 306 func (suite *AccountTestSuite) TestGetAccountLastPosted() { 307 lastPosted, err := suite.db.GetAccountLastPosted(context.Background(), suite.testAccounts["local_account_1"].ID, false) 308 suite.NoError(err) 309 suite.EqualValues(1653046675, lastPosted.Unix()) 310 } 311 312 func (suite *AccountTestSuite) TestGetAccountLastPostedWebOnly() { 313 lastPosted, err := suite.db.GetAccountLastPosted(context.Background(), suite.testAccounts["local_account_1"].ID, true) 314 suite.NoError(err) 315 suite.EqualValues(1634726437, lastPosted.Unix()) 316 } 317 318 func (suite *AccountTestSuite) TestInsertAccountWithDefaults() { 319 key, err := rsa.GenerateKey(rand.Reader, 2048) 320 suite.NoError(err) 321 322 newAccount := >smodel.Account{ 323 ID: "01FGP5P4VJ9SPFB0T3E36Q60DW", 324 Username: "test_service", 325 Domain: "example.org", 326 URI: "https://example.org/users/test_service", 327 URL: "https://example.org/@test_service", 328 ActorType: ap.ActorService, 329 PublicKey: &key.PublicKey, 330 PublicKeyURI: "https://example.org/users/test_service#main-key", 331 } 332 333 err = suite.db.Put(context.Background(), newAccount) 334 suite.NoError(err) 335 336 suite.Equal("en", newAccount.Language) 337 suite.WithinDuration(time.Now(), newAccount.CreatedAt, 30*time.Second) 338 suite.WithinDuration(time.Now(), newAccount.UpdatedAt, 30*time.Second) 339 suite.False(*newAccount.Memorial) 340 suite.False(*newAccount.Bot) 341 suite.False(*newAccount.Discoverable) 342 suite.False(*newAccount.Sensitive) 343 suite.False(*newAccount.HideCollections) 344 } 345 346 func (suite *AccountTestSuite) TestGetAccountPinnedStatusesSomeResults() { 347 testAccount := suite.testAccounts["admin_account"] 348 349 statuses, err := suite.db.GetAccountPinnedStatuses(context.Background(), testAccount.ID) 350 suite.NoError(err) 351 suite.Len(statuses, 2) // This account has 2 statuses pinned. 352 } 353 354 func (suite *AccountTestSuite) TestGetAccountPinnedStatusesNothingPinned() { 355 testAccount := suite.testAccounts["local_account_1"] 356 357 statuses, err := suite.db.GetAccountPinnedStatuses(context.Background(), testAccount.ID) 358 suite.ErrorIs(err, db.ErrNoEntries) 359 suite.Empty(statuses) // This account has nothing pinned. 360 } 361 362 func (suite *AccountTestSuite) TestCountAccountPinnedSomeResults() { 363 testAccount := suite.testAccounts["admin_account"] 364 365 pinned, err := suite.db.CountAccountPinned(context.Background(), testAccount.ID) 366 suite.NoError(err) 367 suite.Equal(pinned, 2) // This account has 2 statuses pinned. 368 } 369 370 func (suite *AccountTestSuite) TestCountAccountPinnedNothingPinned() { 371 testAccount := suite.testAccounts["local_account_1"] 372 373 pinned, err := suite.db.CountAccountPinned(context.Background(), testAccount.ID) 374 suite.NoError(err) 375 suite.Equal(pinned, 0) // This account has nothing pinned. 376 } 377 378 func TestAccountTestSuite(t *testing.T) { 379 suite.Run(t, new(AccountTestSuite)) 380 }