gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 := &gtsmodel.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 := &gtsmodel.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 }