gtsocial-umbx

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

testmodels.go (160167B)


      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 testrig
     19 
     20 import (
     21 	"bytes"
     22 	"crypto/rand"
     23 	"crypto/rsa"
     24 	"crypto/x509"
     25 	"encoding/base64"
     26 	"encoding/json"
     27 	"encoding/pem"
     28 	"fmt"
     29 	"net"
     30 	"net/http"
     31 	"net/url"
     32 	"os"
     33 	"sort"
     34 	"strings"
     35 	"time"
     36 
     37 	"github.com/superseriousbusiness/activity/pub"
     38 	"github.com/superseriousbusiness/activity/streams"
     39 	"github.com/superseriousbusiness/activity/streams/vocab"
     40 	"github.com/superseriousbusiness/gotosocial/internal/ap"
     41 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     42 	"github.com/superseriousbusiness/gotosocial/internal/transport"
     43 )
     44 
     45 // TrueBool just returns a pointer to boolean true.
     46 func TrueBool() *bool {
     47 	b := new(bool)
     48 	*b = true
     49 	return b
     50 }
     51 
     52 // FalseBool just returns a pointer to boolean false.
     53 func FalseBool() *bool {
     54 	return new(bool)
     55 }
     56 
     57 // StringPtr returns a pointer to the given string.
     58 func StringPtr(in string) *string {
     59 	return &in
     60 }
     61 
     62 func Float32Ptr(in float32) *float32 {
     63 	return &in
     64 }
     65 
     66 func Uint64Ptr(in uint64) *uint64 {
     67 	return &in
     68 }
     69 
     70 // NewTestTokens returns a map of tokens keyed according to which account the token belongs to.
     71 func NewTestTokens() map[string]*gtsmodel.Token {
     72 	tokens := map[string]*gtsmodel.Token{
     73 		"local_account_1": {
     74 			ID:              "01F8MGTQW4DKTDF8SW5CT9HYGA",
     75 			ClientID:        "01F8MGV8AC3NGSJW0FE8W1BV70",
     76 			UserID:          "01F8MGVGPHQ2D3P3X0454H54Z5",
     77 			RedirectURI:     "http://localhost:8080",
     78 			Scope:           "read write follow push",
     79 			Access:          "NZAZOTC0OWITMDU0NC0ZODG4LWE4NJITMWUXM2M4MTRHZDEX",
     80 			AccessCreateAt:  TimeMustParse("2022-06-10T15:22:08Z"),
     81 			AccessExpiresAt: TimeMustParse("2050-01-01T15:22:08Z"),
     82 		},
     83 		"local_account_1_client_application_token": {
     84 			ID:              "01P9SVWS9J3SPHZQ3KCMBEN70N",
     85 			ClientID:        "01F8MGV8AC3NGSJW0FE8W1BV70",
     86 			RedirectURI:     "http://localhost:8080",
     87 			Access:          "ZTK1MWMWZDGTMGMXOS0ZY2UXLWI5ZWETMWEZYZZIYTLHMZI4",
     88 			AccessCreateAt:  TimeMustParse("2022-06-10T15:22:08Z"),
     89 			AccessExpiresAt: TimeMustParse("2050-01-01T15:22:08Z"),
     90 		},
     91 		"local_account_1_user_authorization_token": {
     92 			ID:            "01G574M2VTV66YZBC9AZ7HY3FV",
     93 			ClientID:      "01F8MGV8AC3NGSJW0FE8W1BV70",
     94 			UserID:        "01F8MGVGPHQ2D3P3X0454H54Z5",
     95 			RedirectURI:   "http://localhost:8080",
     96 			Code:          "ZJYYMZQ0MTQTZTU1NC0ZNJK4LWE2ZWITYTM1MDHHOTAXNJHL",
     97 			CodeCreateAt:  TimeMustParse("2022-06-10T15:22:08Z"),
     98 			CodeExpiresAt: TimeMustParse("2050-01-01T15:22:08Z"),
     99 		},
    100 		"local_account_2": {
    101 			ID:              "01F8MGVVM1EDVYET710J27XY5R",
    102 			ClientID:        "01F8MGW47HN8ZXNHNZ7E47CDMQ",
    103 			UserID:          "01F8MH1VYJAE00TVVGMM5JNJ8X",
    104 			RedirectURI:     "http://localhost:8080",
    105 			Scope:           "read write follow push",
    106 			Access:          "PIPINALKNNNFNF98717NAMNAMNFKIJKJ881818KJKJAKJJJA",
    107 			AccessCreateAt:  TimeMustParse("2022-06-10T15:22:08Z"),
    108 			AccessExpiresAt: TimeMustParse("2050-01-01T15:22:08Z"),
    109 		},
    110 		"admin_account": {
    111 			ID:              "01FS4TP8ANA5VE92EAPA9E0M7Q",
    112 			ClientID:        "01F8MGWSJCND9BWBD4WGJXBM93",
    113 			UserID:          "01F8MGWYWKVKS3VS8DV1AMYPGE",
    114 			RedirectURI:     "http://localhost:8080",
    115 			Scope:           "read write follow push admin",
    116 			Access:          "AININALKNENFNF98717NAMG4LWE4NJITMWUXM2M4MTRHZDEX",
    117 			AccessCreateAt:  TimeMustParse("2022-06-10T15:22:08Z"),
    118 			AccessExpiresAt: TimeMustParse("2050-01-01T15:22:08Z"),
    119 		},
    120 	}
    121 	return tokens
    122 }
    123 
    124 // NewTestClients returns a map of Clients keyed according to which account they are used by.
    125 func NewTestClients() map[string]*gtsmodel.Client {
    126 	clients := map[string]*gtsmodel.Client{
    127 		"admin_account": {
    128 			ID:     "01F8MGWSJCND9BWBD4WGJXBM93",
    129 			Secret: "dda8e835-2c9c-4bd2-9b8b-77c2e26d7a7a",
    130 			Domain: "http://localhost:8080",
    131 			UserID: "01F8MGWYWKVKS3VS8DV1AMYPGE", // admin_account
    132 		},
    133 		"local_account_1": {
    134 			ID:     "01F8MGV8AC3NGSJW0FE8W1BV70",
    135 			Secret: "c3724c74-dc3b-41b2-a108-0ea3d8399830",
    136 			Domain: "http://localhost:8080",
    137 			UserID: "01F8MGVGPHQ2D3P3X0454H54Z5", // local_account_1
    138 		},
    139 		"local_account_2": {
    140 			ID:     "01F8MGW47HN8ZXNHNZ7E47CDMQ",
    141 			Secret: "8f5603a5-c721-46cd-8f1b-2e368f51379f",
    142 			Domain: "http://localhost:8080",
    143 			UserID: "01F8MH1VYJAE00TVVGMM5JNJ8X", // local_account_2
    144 		},
    145 	}
    146 	return clients
    147 }
    148 
    149 // NewTestApplications returns a map of applications keyed to which number application they are.
    150 func NewTestApplications() map[string]*gtsmodel.Application {
    151 	apps := map[string]*gtsmodel.Application{
    152 		"admin_account": {
    153 			ID:           "01F8MGXQRHYF5QPMTMXP78QC2F",
    154 			Name:         "superseriousbusiness",
    155 			Website:      "https://superserious.business",
    156 			RedirectURI:  "http://localhost:8080",
    157 			ClientID:     "01F8MGWSJCND9BWBD4WGJXBM93",           // admin client
    158 			ClientSecret: "dda8e835-2c9c-4bd2-9b8b-77c2e26d7a7a", // admin client
    159 			Scopes:       "read write follow push",
    160 		},
    161 		"application_1": {
    162 			ID:           "01F8MGY43H3N2C8EWPR2FPYEXG",
    163 			Name:         "really cool gts application",
    164 			Website:      "https://reallycool.app",
    165 			RedirectURI:  "http://localhost:8080",
    166 			ClientID:     "01F8MGV8AC3NGSJW0FE8W1BV70",           // client_1
    167 			ClientSecret: "c3724c74-dc3b-41b2-a108-0ea3d8399830", // client_1
    168 			Scopes:       "read write follow push",
    169 		},
    170 		"application_2": {
    171 			ID:           "01F8MGYG9E893WRHW0TAEXR8GJ",
    172 			Name:         "kindaweird",
    173 			Website:      "https://kindaweird.app",
    174 			RedirectURI:  "http://localhost:8080",
    175 			ClientID:     "01F8MGW47HN8ZXNHNZ7E47CDMQ",           // client_2
    176 			ClientSecret: "8f5603a5-c721-46cd-8f1b-2e368f51379f", // client_2
    177 			Scopes:       "read write follow push",
    178 		},
    179 	}
    180 	return apps
    181 }
    182 
    183 // NewTestUsers returns a map of Users keyed by which account belongs to them.
    184 func NewTestUsers() map[string]*gtsmodel.User {
    185 	users := map[string]*gtsmodel.User{
    186 		"unconfirmed_account": {
    187 			ID:                     "01F8MGYG9E893WRHW0TAEXR8GJ",
    188 			Email:                  "",
    189 			AccountID:              "01F8MH0BBE4FHXPH513MBVFHB0",
    190 			EncryptedPassword:      "$2y$10$ggWz5QWwnx6kzb9g0tnIJurFtE0dhr5Zfeaqs9iFuUIXzafQlJVZS", // 'password'
    191 			CreatedAt:              TimeMustParse("2022-06-04T13:12:00Z"),
    192 			SignUpIP:               net.ParseIP("199.222.111.89"),
    193 			UpdatedAt:              time.Time{},
    194 			CurrentSignInAt:        time.Time{},
    195 			CurrentSignInIP:        nil,
    196 			LastSignInAt:           time.Time{},
    197 			LastSignInIP:           nil,
    198 			SignInCount:            0,
    199 			InviteID:               "",
    200 			ChosenLanguages:        []string{},
    201 			FilteredLanguages:      []string{},
    202 			Locale:                 "en",
    203 			CreatedByApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
    204 			LastEmailedAt:          time.Time{},
    205 			ConfirmationToken:      "a5a280bd-34be-44a3-8330-a57eaf61b8dd",
    206 			ConfirmedAt:            time.Time{},
    207 			ConfirmationSentAt:     TimeMustParse("2022-06-04T13:12:00Z"),
    208 			UnconfirmedEmail:       "weed_lord420@example.org",
    209 			Moderator:              FalseBool(),
    210 			Admin:                  FalseBool(),
    211 			Disabled:               FalseBool(),
    212 			Approved:               FalseBool(),
    213 			ResetPasswordToken:     "",
    214 			ResetPasswordSentAt:    time.Time{},
    215 		},
    216 		"admin_account": {
    217 			ID:                     "01F8MGWYWKVKS3VS8DV1AMYPGE",
    218 			Email:                  "admin@example.org",
    219 			AccountID:              "01F8MH17FWEB39HZJ76B6VXSKF",
    220 			EncryptedPassword:      "$2y$10$ggWz5QWwnx6kzb9g0tnIJurFtE0dhr5Zfeaqs9iFuUIXzafQlJVZS", // 'password'
    221 			CreatedAt:              TimeMustParse("2022-06-01T13:12:00Z"),
    222 			SignUpIP:               net.ParseIP("89.22.189.19"),
    223 			UpdatedAt:              TimeMustParse("2022-06-01T13:12:00Z"),
    224 			CurrentSignInAt:        TimeMustParse("2022-06-04T13:12:00Z"),
    225 			CurrentSignInIP:        net.ParseIP("89.122.255.1"),
    226 			LastSignInAt:           TimeMustParse("2022-06-03T13:12:00Z"),
    227 			LastSignInIP:           net.ParseIP("89.122.255.1"),
    228 			SignInCount:            78,
    229 			InviteID:               "",
    230 			ChosenLanguages:        []string{"en"},
    231 			FilteredLanguages:      []string{},
    232 			Locale:                 "en",
    233 			CreatedByApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
    234 			LastEmailedAt:          TimeMustParse("2022-06-03T13:12:00Z"),
    235 			ConfirmationToken:      "",
    236 			ConfirmedAt:            TimeMustParse("2022-06-02T13:12:00Z"),
    237 			ConfirmationSentAt:     time.Time{},
    238 			UnconfirmedEmail:       "",
    239 			Moderator:              TrueBool(),
    240 			Admin:                  TrueBool(),
    241 			Disabled:               FalseBool(),
    242 			Approved:               TrueBool(),
    243 			ResetPasswordToken:     "",
    244 			ResetPasswordSentAt:    time.Time{},
    245 		},
    246 		"local_account_1": {
    247 			ID:                     "01F8MGVGPHQ2D3P3X0454H54Z5",
    248 			Email:                  "zork@example.org",
    249 			AccountID:              "01F8MH1H7YV1Z7D2C8K2730QBF",
    250 			EncryptedPassword:      "$2y$10$ggWz5QWwnx6kzb9g0tnIJurFtE0dhr5Zfeaqs9iFuUIXzafQlJVZS", // 'password'
    251 			CreatedAt:              TimeMustParse("2022-06-01T13:12:00Z"),
    252 			SignUpIP:               net.ParseIP("59.99.19.172"),
    253 			UpdatedAt:              TimeMustParse("2022-06-01T13:12:00Z"),
    254 			CurrentSignInAt:        TimeMustParse("2022-06-04T13:12:00Z"),
    255 			CurrentSignInIP:        net.ParseIP("88.234.118.16"),
    256 			LastSignInAt:           TimeMustParse("2022-06-03T13:12:00Z"),
    257 			LastSignInIP:           net.ParseIP("147.111.231.154"),
    258 			SignInCount:            9,
    259 			InviteID:               "",
    260 			ChosenLanguages:        []string{"en"},
    261 			FilteredLanguages:      []string{},
    262 			Locale:                 "en",
    263 			CreatedByApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
    264 			LastEmailedAt:          TimeMustParse("2022-06-02T13:12:00Z"),
    265 			ConfirmationToken:      "",
    266 			ConfirmedAt:            TimeMustParse("2022-06-02T13:12:00Z"),
    267 			ConfirmationSentAt:     TimeMustParse("2022-06-02T13:12:00Z"),
    268 			UnconfirmedEmail:       "",
    269 			Moderator:              FalseBool(),
    270 			Admin:                  FalseBool(),
    271 			Disabled:               FalseBool(),
    272 			Approved:               TrueBool(),
    273 			ResetPasswordToken:     "",
    274 			ResetPasswordSentAt:    time.Time{},
    275 		},
    276 		"local_account_2": {
    277 			ID:                     "01F8MH1VYJAE00TVVGMM5JNJ8X",
    278 			Email:                  "tortle.dude@example.org",
    279 			AccountID:              "01F8MH5NBDF2MV7CTC4Q5128HF",
    280 			EncryptedPassword:      "$2y$10$ggWz5QWwnx6kzb9g0tnIJurFtE0dhr5Zfeaqs9iFuUIXzafQlJVZS", // 'password'
    281 			CreatedAt:              TimeMustParse("2022-05-23T13:12:00Z"),
    282 			SignUpIP:               net.ParseIP("59.99.19.172"),
    283 			UpdatedAt:              TimeMustParse("2022-05-23T13:12:00Z"),
    284 			CurrentSignInAt:        TimeMustParse("2022-06-05T13:12:00Z"),
    285 			CurrentSignInIP:        net.ParseIP("118.44.18.196"),
    286 			LastSignInAt:           TimeMustParse("2022-06-06T13:12:00Z"),
    287 			LastSignInIP:           net.ParseIP("198.98.21.15"),
    288 			SignInCount:            9,
    289 			InviteID:               "",
    290 			ChosenLanguages:        []string{"en"},
    291 			FilteredLanguages:      []string{},
    292 			Locale:                 "en",
    293 			CreatedByApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
    294 			LastEmailedAt:          TimeMustParse("2022-06-06T13:12:00Z"),
    295 			ConfirmationToken:      "",
    296 			ConfirmedAt:            TimeMustParse("2022-05-24T13:12:00Z"),
    297 			ConfirmationSentAt:     TimeMustParse("2022-05-23T13:12:00Z"),
    298 			UnconfirmedEmail:       "",
    299 			Moderator:              FalseBool(),
    300 			Admin:                  FalseBool(),
    301 			Disabled:               FalseBool(),
    302 			Approved:               TrueBool(),
    303 			ResetPasswordToken:     "",
    304 			ResetPasswordSentAt:    time.Time{},
    305 		},
    306 	}
    307 
    308 	return users
    309 }
    310 
    311 // NewTestAccounts returns a map of accounts keyed by what type of account they are.
    312 func NewTestAccounts() map[string]*gtsmodel.Account {
    313 	accounts := map[string]*gtsmodel.Account{
    314 		"instance_account": {
    315 			ID:                      "01AY6P665V14JJR0AFVRT7311Y",
    316 			Username:                "localhost:8080",
    317 			AvatarMediaAttachmentID: "",
    318 			HeaderMediaAttachmentID: "",
    319 			DisplayName:             "",
    320 			Fields:                  []*gtsmodel.Field{},
    321 			Note:                    "",
    322 			NoteRaw:                 "",
    323 			Memorial:                FalseBool(),
    324 			MovedToAccountID:        "",
    325 			CreatedAt:               TimeMustParse("2020-05-17T13:10:59Z"),
    326 			UpdatedAt:               TimeMustParse("2020-05-17T13:10:59Z"),
    327 			Bot:                     FalseBool(),
    328 			Reason:                  "",
    329 			Locked:                  FalseBool(),
    330 			Discoverable:            TrueBool(),
    331 			Privacy:                 gtsmodel.VisibilityPublic,
    332 			Sensitive:               FalseBool(),
    333 			Language:                "en",
    334 			URI:                     "http://localhost:8080/users/localhost:8080",
    335 			URL:                     "http://localhost:8080/@localhost:8080",
    336 			PublicKeyURI:            "http://localhost:8080/users/localhost:8080#main-key",
    337 			FetchedAt:               time.Time{},
    338 			InboxURI:                "http://localhost:8080/users/localhost:8080/inbox",
    339 			OutboxURI:               "http://localhost:8080/users/localhost:8080/outbox",
    340 			FollowersURI:            "http://localhost:8080/users/localhost:8080/followers",
    341 			FollowingURI:            "http://localhost:8080/users/localhost:8080/following",
    342 			FeaturedCollectionURI:   "http://localhost:8080/users/localhost:8080/collections/featured",
    343 			ActorType:               ap.ActorPerson,
    344 			AlsoKnownAs:             "",
    345 			PrivateKey:              &rsa.PrivateKey{},
    346 			PublicKey:               &rsa.PublicKey{},
    347 			SensitizedAt:            time.Time{},
    348 			SilencedAt:              time.Time{},
    349 			SuspendedAt:             time.Time{},
    350 			HideCollections:         FalseBool(),
    351 			SuspensionOrigin:        "",
    352 			EnableRSS:               FalseBool(),
    353 		},
    354 		"unconfirmed_account": {
    355 			ID:                      "01F8MH0BBE4FHXPH513MBVFHB0",
    356 			Username:                "weed_lord420",
    357 			AvatarMediaAttachmentID: "",
    358 			HeaderMediaAttachmentID: "",
    359 			DisplayName:             "",
    360 			Fields:                  []*gtsmodel.Field{},
    361 			Note:                    "",
    362 			Memorial:                FalseBool(),
    363 			MovedToAccountID:        "",
    364 			CreatedAt:               TimeMustParse("2022-06-04T13:12:00Z"),
    365 			UpdatedAt:               TimeMustParse("2022-06-04T13:12:00Z"),
    366 			Bot:                     FalseBool(),
    367 			Reason:                  "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
    368 			Locked:                  FalseBool(),
    369 			Discoverable:            FalseBool(),
    370 			Privacy:                 gtsmodel.VisibilityPublic,
    371 			Sensitive:               FalseBool(),
    372 			Language:                "en",
    373 			URI:                     "http://localhost:8080/users/weed_lord420",
    374 			URL:                     "http://localhost:8080/@weed_lord420",
    375 			FetchedAt:               time.Time{},
    376 			InboxURI:                "http://localhost:8080/users/weed_lord420/inbox",
    377 			OutboxURI:               "http://localhost:8080/users/weed_lord420/outbox",
    378 			FollowersURI:            "http://localhost:8080/users/weed_lord420/followers",
    379 			FollowingURI:            "http://localhost:8080/users/weed_lord420/following",
    380 			FeaturedCollectionURI:   "http://localhost:8080/users/weed_lord420/collections/featured",
    381 			ActorType:               ap.ActorPerson,
    382 			AlsoKnownAs:             "",
    383 			PrivateKey:              &rsa.PrivateKey{},
    384 			PublicKey:               &rsa.PublicKey{},
    385 			PublicKeyURI:            "http://localhost:8080/users/weed_lord420#main-key",
    386 			SensitizedAt:            time.Time{},
    387 			SilencedAt:              time.Time{},
    388 			SuspendedAt:             time.Time{},
    389 			HideCollections:         FalseBool(),
    390 			SuspensionOrigin:        "",
    391 		},
    392 		"admin_account": {
    393 			ID:                      "01F8MH17FWEB39HZJ76B6VXSKF",
    394 			Username:                "admin",
    395 			AvatarMediaAttachmentID: "",
    396 			HeaderMediaAttachmentID: "",
    397 			DisplayName:             "",
    398 			Fields:                  []*gtsmodel.Field{},
    399 			Note:                    "",
    400 			NoteRaw:                 "",
    401 			Memorial:                FalseBool(),
    402 			MovedToAccountID:        "",
    403 			CreatedAt:               TimeMustParse("2022-05-17T13:10:59Z"),
    404 			UpdatedAt:               TimeMustParse("2022-05-17T13:10:59Z"),
    405 			Bot:                     FalseBool(),
    406 			Reason:                  "",
    407 			Locked:                  FalseBool(),
    408 			Discoverable:            TrueBool(),
    409 			Privacy:                 gtsmodel.VisibilityPublic,
    410 			Sensitive:               FalseBool(),
    411 			Language:                "en",
    412 			URI:                     "http://localhost:8080/users/admin",
    413 			URL:                     "http://localhost:8080/@admin",
    414 			PublicKeyURI:            "http://localhost:8080/users/admin#main-key",
    415 			FetchedAt:               time.Time{},
    416 			InboxURI:                "http://localhost:8080/users/admin/inbox",
    417 			OutboxURI:               "http://localhost:8080/users/admin/outbox",
    418 			FollowersURI:            "http://localhost:8080/users/admin/followers",
    419 			FollowingURI:            "http://localhost:8080/users/admin/following",
    420 			FeaturedCollectionURI:   "http://localhost:8080/users/admin/collections/featured",
    421 			ActorType:               ap.ActorPerson,
    422 			AlsoKnownAs:             "",
    423 			PrivateKey:              &rsa.PrivateKey{},
    424 			PublicKey:               &rsa.PublicKey{},
    425 			SensitizedAt:            time.Time{},
    426 			SilencedAt:              time.Time{},
    427 			SuspendedAt:             time.Time{},
    428 			HideCollections:         FalseBool(),
    429 			SuspensionOrigin:        "",
    430 			EnableRSS:               TrueBool(),
    431 		},
    432 		"local_account_1": {
    433 			ID:                      "01F8MH1H7YV1Z7D2C8K2730QBF",
    434 			Username:                "the_mighty_zork",
    435 			AvatarMediaAttachmentID: "01F8MH58A357CV5K7R7TJMSH6S",
    436 			HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3Q",
    437 			DisplayName:             "original zork (he/they)",
    438 			Fields:                  []*gtsmodel.Field{},
    439 			Note:                    "<p>hey yo this is my profile!</p>",
    440 			NoteRaw:                 "hey yo this is my profile!",
    441 			Memorial:                FalseBool(),
    442 			MovedToAccountID:        "",
    443 			CreatedAt:               TimeMustParse("2022-05-20T11:09:18Z"),
    444 			UpdatedAt:               TimeMustParse("2022-05-20T11:09:18Z"),
    445 			Bot:                     FalseBool(),
    446 			Reason:                  "I wanna be on this damned webbed site so bad! Please! Wow",
    447 			Locked:                  FalseBool(),
    448 			Discoverable:            TrueBool(),
    449 			Privacy:                 gtsmodel.VisibilityPublic,
    450 			Sensitive:               FalseBool(),
    451 			Language:                "en",
    452 			URI:                     "http://localhost:8080/users/the_mighty_zork",
    453 			URL:                     "http://localhost:8080/@the_mighty_zork",
    454 			FetchedAt:               time.Time{},
    455 			InboxURI:                "http://localhost:8080/users/the_mighty_zork/inbox",
    456 			OutboxURI:               "http://localhost:8080/users/the_mighty_zork/outbox",
    457 			FollowersURI:            "http://localhost:8080/users/the_mighty_zork/followers",
    458 			FollowingURI:            "http://localhost:8080/users/the_mighty_zork/following",
    459 			FeaturedCollectionURI:   "http://localhost:8080/users/the_mighty_zork/collections/featured",
    460 			ActorType:               ap.ActorPerson,
    461 			AlsoKnownAs:             "",
    462 			PrivateKey:              &rsa.PrivateKey{},
    463 			PublicKey:               &rsa.PublicKey{},
    464 			PublicKeyURI:            "http://localhost:8080/users/the_mighty_zork/main-key",
    465 			SensitizedAt:            time.Time{},
    466 			SilencedAt:              time.Time{},
    467 			SuspendedAt:             time.Time{},
    468 			HideCollections:         FalseBool(),
    469 			SuspensionOrigin:        "",
    470 			EnableRSS:               TrueBool(),
    471 		},
    472 		"local_account_2": {
    473 			ID:                      "01F8MH5NBDF2MV7CTC4Q5128HF",
    474 			Username:                "1happyturtle",
    475 			AvatarMediaAttachmentID: "",
    476 			HeaderMediaAttachmentID: "",
    477 			DisplayName:             "happy little turtle :3",
    478 			Fields: []*gtsmodel.Field{
    479 				{
    480 					Name:  "should you follow me?",
    481 					Value: "maybe!",
    482 				},
    483 				{
    484 					Name:  "age",
    485 					Value: "120",
    486 				},
    487 			},
    488 			FieldsRaw: []*gtsmodel.Field{
    489 				{
    490 					Name:  "should you follow me?",
    491 					Value: "maybe!",
    492 				},
    493 				{
    494 					Name:  "age",
    495 					Value: "120",
    496 				},
    497 			},
    498 			Note:                  "<p>i post about things that concern me</p>",
    499 			NoteRaw:               "i post about things that concern me",
    500 			Memorial:              FalseBool(),
    501 			MovedToAccountID:      "",
    502 			CreatedAt:             TimeMustParse("2022-06-04T13:12:00Z"),
    503 			UpdatedAt:             TimeMustParse("2022-06-04T13:12:00Z"),
    504 			Bot:                   FalseBool(),
    505 			Reason:                "",
    506 			Locked:                TrueBool(),
    507 			Discoverable:          FalseBool(),
    508 			Privacy:               gtsmodel.VisibilityFollowersOnly,
    509 			Sensitive:             TrueBool(),
    510 			Language:              "fr",
    511 			URI:                   "http://localhost:8080/users/1happyturtle",
    512 			URL:                   "http://localhost:8080/@1happyturtle",
    513 			FetchedAt:             time.Time{},
    514 			InboxURI:              "http://localhost:8080/users/1happyturtle/inbox",
    515 			OutboxURI:             "http://localhost:8080/users/1happyturtle/outbox",
    516 			FollowersURI:          "http://localhost:8080/users/1happyturtle/followers",
    517 			FollowingURI:          "http://localhost:8080/users/1happyturtle/following",
    518 			FeaturedCollectionURI: "http://localhost:8080/users/1happyturtle/collections/featured",
    519 			ActorType:             ap.ActorPerson,
    520 			AlsoKnownAs:           "",
    521 			PrivateKey:            &rsa.PrivateKey{},
    522 			PublicKey:             &rsa.PublicKey{},
    523 			PublicKeyURI:          "http://localhost:8080/users/1happyturtle#main-key",
    524 			SensitizedAt:          time.Time{},
    525 			SilencedAt:            time.Time{},
    526 			SuspendedAt:           time.Time{},
    527 			HideCollections:       FalseBool(),
    528 			SuspensionOrigin:      "",
    529 			EnableRSS:             FalseBool(),
    530 		},
    531 		"remote_account_1": {
    532 			ID:                    "01F8MH5ZK5VRH73AKHQM6Y9VNX",
    533 			Username:              "foss_satan",
    534 			Domain:                "fossbros-anonymous.io",
    535 			DisplayName:           "big gerald",
    536 			Fields:                []*gtsmodel.Field{},
    537 			Note:                  "i post about like, i dunno, stuff, or whatever!!!!",
    538 			Memorial:              FalseBool(),
    539 			MovedToAccountID:      "",
    540 			CreatedAt:             TimeMustParse("2021-09-26T12:52:36+02:00"),
    541 			UpdatedAt:             TimeMustParse("2022-06-04T13:12:00Z"),
    542 			Bot:                   FalseBool(),
    543 			Locked:                FalseBool(),
    544 			Discoverable:          TrueBool(),
    545 			Sensitive:             FalseBool(),
    546 			Language:              "en",
    547 			URI:                   "http://fossbros-anonymous.io/users/foss_satan",
    548 			URL:                   "http://fossbros-anonymous.io/@foss_satan",
    549 			FetchedAt:             time.Time{},
    550 			InboxURI:              "http://fossbros-anonymous.io/users/foss_satan/inbox",
    551 			SharedInboxURI:        StringPtr("http://fossbros-anonymous.io/inbox"),
    552 			OutboxURI:             "http://fossbros-anonymous.io/users/foss_satan/outbox",
    553 			FollowersURI:          "http://fossbros-anonymous.io/users/foss_satan/followers",
    554 			FollowingURI:          "http://fossbros-anonymous.io/users/foss_satan/following",
    555 			FeaturedCollectionURI: "http://fossbros-anonymous.io/users/foss_satan/collections/featured",
    556 			ActorType:             ap.ActorPerson,
    557 			AlsoKnownAs:           "",
    558 			PrivateKey:            &rsa.PrivateKey{},
    559 			PublicKey:             &rsa.PublicKey{},
    560 			PublicKeyURI:          "http://fossbros-anonymous.io/users/foss_satan/main-key",
    561 			SensitizedAt:          time.Time{},
    562 			SilencedAt:            time.Time{},
    563 			SuspendedAt:           time.Time{},
    564 			HideCollections:       FalseBool(),
    565 			SuspensionOrigin:      "",
    566 		},
    567 		"remote_account_2": {
    568 			ID:                    "01FHMQX3GAABWSM0S2VZEC2SWC",
    569 			Username:              "Some_User",
    570 			Domain:                "example.org",
    571 			DisplayName:           "some user",
    572 			Fields:                []*gtsmodel.Field{},
    573 			Note:                  "i'm a real son of a gun",
    574 			Memorial:              FalseBool(),
    575 			MovedToAccountID:      "",
    576 			CreatedAt:             TimeMustParse("2020-08-10T14:13:28+02:00"),
    577 			UpdatedAt:             TimeMustParse("2022-06-04T13:12:00Z"),
    578 			Bot:                   FalseBool(),
    579 			Locked:                TrueBool(),
    580 			Discoverable:          TrueBool(),
    581 			Sensitive:             FalseBool(),
    582 			Language:              "en",
    583 			URI:                   "http://example.org/users/Some_User",
    584 			URL:                   "http://example.org/@Some_User",
    585 			FetchedAt:             time.Time{},
    586 			InboxURI:              "http://example.org/users/Some_User/inbox",
    587 			SharedInboxURI:        StringPtr(""),
    588 			OutboxURI:             "http://example.org/users/Some_User/outbox",
    589 			FollowersURI:          "http://example.org/users/Some_User/followers",
    590 			FollowingURI:          "http://example.org/users/Some_User/following",
    591 			FeaturedCollectionURI: "http://example.org/users/Some_User/collections/featured",
    592 			ActorType:             ap.ActorPerson,
    593 			AlsoKnownAs:           "",
    594 			PrivateKey:            &rsa.PrivateKey{},
    595 			PublicKey:             &rsa.PublicKey{},
    596 			PublicKeyURI:          "http://example.org/users/Some_User#main-key",
    597 			SensitizedAt:          time.Time{},
    598 			SilencedAt:            time.Time{},
    599 			SuspendedAt:           time.Time{},
    600 			HideCollections:       FalseBool(),
    601 			SuspensionOrigin:      "",
    602 		},
    603 		"remote_account_3": {
    604 			ID:                      "062G5WYKY35KKD12EMSM3F8PJ8",
    605 			Username:                "her_fuckin_maj",
    606 			Domain:                  "thequeenisstillalive.technology",
    607 			DisplayName:             "lizzzieeeeeeeeeeee",
    608 			Fields:                  []*gtsmodel.Field{},
    609 			Note:                    "if i die blame charles don't let that fuck become king",
    610 			Memorial:                FalseBool(),
    611 			MovedToAccountID:        "",
    612 			CreatedAt:               TimeMustParse("2020-08-10T14:13:28+02:00"),
    613 			UpdatedAt:               TimeMustParse("2022-06-04T13:12:00Z"),
    614 			Bot:                     FalseBool(),
    615 			Locked:                  TrueBool(),
    616 			Discoverable:            TrueBool(),
    617 			Sensitive:               FalseBool(),
    618 			Language:                "en",
    619 			URI:                     "http://thequeenisstillalive.technology/users/her_fuckin_maj",
    620 			URL:                     "http://thequeenisstillalive.technology/@her_fuckin_maj",
    621 			FetchedAt:               time.Time{},
    622 			InboxURI:                "http://thequeenisstillalive.technology/users/her_fuckin_maj/inbox",
    623 			SharedInboxURI:          StringPtr(""),
    624 			OutboxURI:               "http://thequeenisstillalive.technology/users/her_fuckin_maj/outbox",
    625 			FollowersURI:            "http://thequeenisstillalive.technology/users/her_fuckin_maj/followers",
    626 			FollowingURI:            "http://thequeenisstillalive.technology/users/her_fuckin_maj/following",
    627 			FeaturedCollectionURI:   "http://thequeenisstillalive.technology/users/her_fuckin_maj/collections/featured",
    628 			ActorType:               ap.ActorPerson,
    629 			AlsoKnownAs:             "",
    630 			PrivateKey:              &rsa.PrivateKey{},
    631 			PublicKey:               &rsa.PublicKey{},
    632 			PublicKeyURI:            "http://thequeenisstillalive.technology/users/her_fuckin_maj#main-key",
    633 			SensitizedAt:            time.Time{},
    634 			SilencedAt:              time.Time{},
    635 			SuspendedAt:             time.Time{},
    636 			HideCollections:         FalseBool(),
    637 			SuspensionOrigin:        "",
    638 			HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3R",
    639 		},
    640 		"remote_account_4": {
    641 			ID:                      "07GZRBAEMBNKGZ8Z9VSKSXKR98",
    642 			Username:                "üser",
    643 			Domain:                  "xn--xample-ova.org",
    644 			DisplayName:             "",
    645 			Note:                    "",
    646 			Memorial:                FalseBool(),
    647 			MovedToAccountID:        "",
    648 			CreatedAt:               TimeMustParse("2020-08-10T14:13:28+02:00"),
    649 			UpdatedAt:               TimeMustParse("2022-06-04T13:12:00Z"),
    650 			Bot:                     FalseBool(),
    651 			Locked:                  FalseBool(),
    652 			Discoverable:            FalseBool(),
    653 			Sensitive:               FalseBool(),
    654 			Language:                "de",
    655 			URI:                     "https://xn--xample-ova.org/users/%C3%BCser",
    656 			URL:                     "https://xn--xample-ova.org/users/@%C3%BCser",
    657 			FetchedAt:               time.Time{},
    658 			InboxURI:                "https://xn--xample-ova.org/users/%C3%BCser/inbox",
    659 			SharedInboxURI:          StringPtr(""),
    660 			OutboxURI:               "https://xn--xample-ova.org/users/%C3%BCser/outbox",
    661 			FollowersURI:            "https://xn--xample-ova.org/users/%C3%BCser/followers",
    662 			FollowingURI:            "https://xn--xample-ova.org/users/%C3%BCser/following",
    663 			FeaturedCollectionURI:   "https://xn--xample-ova.org/users/%C3%BCser/collections/featured",
    664 			ActorType:               ap.ActorPerson,
    665 			AlsoKnownAs:             "",
    666 			PrivateKey:              &rsa.PrivateKey{},
    667 			PublicKey:               &rsa.PublicKey{},
    668 			PublicKeyURI:            "https://xn--xample-ova.org/users/%C3%BCser#main-key",
    669 			SensitizedAt:            time.Time{},
    670 			SilencedAt:              time.Time{},
    671 			SuspendedAt:             time.Time{},
    672 			HideCollections:         FalseBool(),
    673 			SuspensionOrigin:        "",
    674 			HeaderMediaAttachmentID: "",
    675 			EnableRSS:               FalseBool(),
    676 		},
    677 	}
    678 
    679 	var accountsSorted []*gtsmodel.Account
    680 	for _, a := range accounts {
    681 		accountsSorted = append(accountsSorted, a)
    682 	}
    683 
    684 	sort.Slice(accountsSorted, func(i, j int) bool {
    685 		return accountsSorted[i].ID > accountsSorted[j].ID
    686 	})
    687 
    688 	preserializedKeys := []string{
    689 		"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/4BHKpxI7X+d6MKKnZtfi8F46sujkBS4HVXP/T/HfMqnwyeTOJMkSfJEbjSeJSyqxjrWKtaeO1vnduddPAgSj9kwaZ9Drf1KZA1zBCJp4ZPqQBQCUdQrWJHw87cCEGuFXObhgCvi8mM8gfBzmF5wz/K8USy/t3GCuAWgUwupAhN40Br1SgSwMv/LI2z04yZJN98SAxKDI8aRHEWd9LnyKSR08r581JEFTcqnqR14RvhC+3nXEYzU3HMND8QLsRXQFDmjeEpwiFPSo55iOToA/fLw0OC2v1v5OwUtuwjMr1mxMGG/QPPhCT5xKxTeIEvNtCcSBO2as3yAfYrJYL/T7AgMBAAECggEBALXCitgQAANizCJB5DL0B1ohHQI57Mfj6EBmQKYAkz09/yHr/uUQj7EFc2hIBMXYAK+GYo7tmbaECtpxa3aakM7JSDpTUeNkD1iHiNwLTFj0Py8irfP0E7nbgh0tk4sQ85nvQaspeYserkc1iyKkBwJwQWHV/6cxdhwflPrl0YYfM2TiSVauB+e/H+M/TzJMCKXMiN6bavJcsJT8m6b3sI1gGFdM+vylacGmrJ0PDroiE5LkjefYe8aGr1Gi+u8yl9n4c2qAR9TltUNV2SgC02J70B+IeS12xeLXKht8ayaAOpZcmggNAOATpEAUZ3qXnWYdu8rMChoNMnwUVJx0XiECgYEA2KgoA721ORR3AyWgVyc/ByyMFS/DGMOLXKBTsiH4Tt65bA7c2UKzcHtrmGbOcEHTD8h/FKoQ8TKhPFqAERyUZ1gwy6E6yuNDZOff5+4aPOszhNwW8ty0O0SrWTOVHyXnBYFAWCbzoKrGNsfxG6T6ZXzf1IYZZuyCc+lwz+Nb++MCgYEA4rfgz3+JwUga2jwWEKiQ+Oz2vuHh8lHRtjKTLvZePKBI5lFjS5PHNhs3JfN8kzhyh87CzcHpBFyeNPmc1WYr0hOuhoVk/8NC97BKvtxokafEXDhRbFlkNsgWb+gqkYZOAih6OL8FkC3yO6hqmLyX+zbN5ke3c0b3fHI4T/3qngkCgYBTS3L23TyLEV8gCps2ZpRIwcupaY9sOeGeXtVOqti4GdDXxm8J6Cbsm8al9QBxEB2A9+hDnY6d7IUomvKZoY88nB9GalocHnuOk8b1eAkGWraX4bXA8TEpiCEITliKfRvwddyzB2aq4n0KGpyLsEXENtom7tddRphwz9LbWeHHWQKBgFuJ/LYq+5bToyvsSMhvFyG6o6HMmCr7yB21a+HxTXlTCjwcLmhMgYmiEXE8T1ct2mhlHhhvq8K8FpCzHBS5jQXkNnpQD8iIsVhKkNNhMMNmpozJnG6P5TuNLCoA5ncdcA/FAhw5XGirdHuL84Y5129x4E6TNEnSJIjVoVEC56DpAoGBAMqetUxfzx57TlZeBegIlaWYhDczB22s6YAiCurWBKOdwhGfZfUuYt5wkrfy3zi6oH2f9kxh4mq+yk7Pc8oXktk6Z1GahTjNuhHI5ESh9cX12L2RbypJwUWWfe4EfRDOdVlaOLI3ECAi8rFpoAUaZIIKzcJF46Ve9Frm+L82eH91",
    690 		"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDA3bAoQMUofndXMXikEU2MOJbfI1uaZbIDrxW0bEO6IOhwe/J0jWJHL2fWc2mbp2NxAH4Db1kZIcl9D0owoRf2cT5k0Y2Dah86dGz4fIedkqGryoWAnEJ2hHKGXGQf2K9OS2L8eaDLGU4CBds0m80vrn153Uiyj7zxWDYqcySM0qQjSg+mvgqpBcxKpd+xACaWNDL8qWvDsBF1D0RuO8hUiXMIKOUoFAGbqe6qWGK0COrEYQTAMydoFuSaAccP70zKQslnSOCKvsOi/iPRKGDNqWINIC/lwqXEIpMj3K+b/A+x41zR7frTgHNLbe4yHWAVNPEwTFningbB/lIyyVmDAgMBAAECggEBALxwnipmRnyvPClMY+RiJ5PGwtqYcGsly82/pwRW98GHX7Rv1lA8x/ZnghxNPbVg0k9ZvMXcaICeu4BejQ2AiKo4sU7OVGc/K+3wTXxoKBU0bJQuV0x24JVuCXvwD7/x9i8Yh0nKCOoH+mkNkcUQKWXaJi0IoXwd5u0kVCAbym1vux/9DcwtydqT4P1EoxEHCXDuRorBP8vYWCZBwRY2etmdAEbHsVpVlNlXWfbGCNMf5e8AecOZre4No8UfTOZkM7YKgjryde3YCmY2zDQI9jExGD2L5nptLizODD5imdpp/IQ7qg6rR3XbIK6CDiKiePEFQibD8XWiz7XVD6JBRokCgYEA0jEAxZseHUyobh1ERHezs2vC2zbiTOfnOpFxhwtNt67dUQZDssTxXF+BymUL8yKi1bnheOTuyASxrgZ7BPdiFvJfhlelSxtxtt1RamY58E179uiel2NPRsR3SL2AsGg+jP+QjJpsJHvYIliXP38G7NVaqaSMFgXfXir7Ty7W0r0CgYEA6uYQWfjmaB66xPrL/oCBaJ+UWM/Zdfw4IETVnRVOxVqGE7AKqC+31fZQ5kIXnNcJNLJ0OJlhGH5vZYp/r4z6qly9BUVolCJcW2YLEOOnChOvKGwlDSXrdGty2f34RXdABwsf/pBHsdpJq70+SE01tTB/8P2NTnRafy9GL/FnwT8CgYEAjJ4D6i8wImHafHBP7441Rl9daNJ66wBqDSCoVrQVNkFiBoauW7at0iKC7ihTqkENtvY4BW0C4gVh6Q6k1lm54agch/+ysWCW3sOJaCkjscPknvZYwubJboqZUqyUn2/eCO4ggi/9ERtZKQEjjnMo6uCBWuSeY01iddlDb2HijfECgYBYQCM4ikiWKaVlyAvIDCOSWRH04/IBX8b+aJ4QrCayAraIwwTd9z+MBUSTnZUdebSdtcXwVb+i4i2b6pLaM48hXkItrswBi39DX20c5UqmgIq4Fxk8fVienpfByqbyAkFt5AIbM72b1jUDbs/tfgSFlDkdI0VpilFNo0ctT/b5JQKBgAxPGtVGzhSQUZWPXjhiBT7MM/1EiLBYhGVrymzd9dmBxj+UyifnRXfIQbOQm3EfI5Z8ZpyS6eqWdi9NTeZi8rg0WleMb/VbOMT3xvTO34vDXvwrQKhFMimX1tY7aKy1udnE2ON2/alq2zWo3zPZfYH1KFdDtGD08GW2M4OO1caa",
    691 		"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGj2wLnDIHnP6wjJ+WmIhp7NGAaKWwfxBWfdMFR+Y0ilkK5ld5igT45UHAmzN3v4HcwHGGpPITD9caDYj5YaGOX+dSdGLgXWwItR0j+ivrHEJmvz8hG6z9wKEZKUUrRw7Ob72S0LOsreq98bjdiWJKHNka27slqQjGyhLQtcg6pe1CLJtnuJH4GEMLj7jJB3/Mqv3vl5CQZ+Js0bXfgw5TF/x/Bzq/8qsxQ1vnmYHJsR0eLPEuDJOvoFPiJZytI09S7qBEJL5PDeVSfjQi3o71sqOzZlEL0b0Ny48rfo/mwJAdkmfcnydRDxeGUEqpAWICCOdUL0+W3/fCffaRZsk1AgMBAAECggEAUuyO6QJgeoF8dGsmMxSc0/ANRp1tpRpLznNZ77ipUYP9z+mG2sFjdjb4kOHASuB18aWFRAAbAQ76fGzuqYe2muk+iFcG/EDH35MUCnRuZxA0QwjX6pHOW2NZZFKyCnLwohJUj74Na65ufMk4tXysydrmaKsfq4i+m5bE6NkiOCtbXsjUGVdJKzkT6X1gEyEPEHgrgVZz9OpRY5nwjZBMcFI6EibFnWdehcuCQLESIX9ll/QzGvTJ1p8xeVJs2ktLWKQ38RewwucNYVLVJmxS1LCPP8x+yHVkOxD66eIncY26sjX+VbyICkaG/ZjKBuoOekOq/T+b6q5ESxWUNfcu+QKBgQDmt3WVBrW6EXKtN1MrVyBoSfn9WHyf8Rfb84t5iNtaWGSyPZK/arUw1DRbI0TdPjct//wMWoUU2/uqcPSzudTaPena3oxjKReXso1hcynHqboCaXJMxWSqDQLumbrVY05C1WFSyhRY0iQS5fIrNzD4+6rmeC2Aj5DKNW5Atda8dwKBgQDcUdhQfjL9SmzzIeAqJUBIfSSI2pSTsZrnrvMtSMkYJbzwYrUdhIVxaS4hXuQYmGgwonLctyvJxVxEMnf+U0nqPgJHE9nGQb5BbK6/LqxBWRJQlc+W6EYodIwvtE5B4JNkPE5757u+xlDdHe2zGUGXSIf4IjBNbSpCu6RcFsGOswKBgEnr4gqbmcJCMOH65fTu930yppxbq6J7Vs+sWrXX+aAazjilrc0S3XcFprjEth3E/10HtbQnlJg4W4wioOSs19wNFk6AG67xzZNXLCFbCrnkUarQKkUawcQSYywbqVcReFPFlmc2RAqpWdGMR2k9R72etQUe4EVeul9veyHUoTbFAoGBAKj3J9NLhaVVb8ri3vzThsJRHzTJlYrTeb5XIO5I1NhtEMK2oLobiQ+aH6O+F2Z5c+Zgn4CABdf/QSyYHAhzLcu0dKC4K5rtjpC0XiwHClovimk9C3BrgGrEP0LSn/XL2p3T1kkWRpkflKKPsl1ZcEEqggSdi7fFkdSN/ZYWaakbAoGBALWVGpA/vXmaZEV/hTDdtDnIHj6RXfKHCsfnyI7AdjUX4gokzdcEvFsEIoI+nnXR/PIAvwqvQw4wiUqQnp2VB8r73YZvW/0npnsidQw3ZjqnyvZ9X8y80nYs7DjSlaG0A8huy2TUdFnJyCMWby30g82kf0b/lhotJg4d3fIDou51",
    692 		"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6q61hiC7OhlMz7JNnLiL/RwOaFC8955GDvwSMH9Zw3oguWH9nLqkmlJ98cnqRG9ZC0qVo6Gagl7gv6yOHDwD4xZI8JoV2ZfNdDzq4QzoBIzMtRsbSS4IvrF3JP+kDH1tim+CbRMBxiFJgLgS6yeeQlLNvBW+CIYzmeCimZ6CWCr91rZPIprUIdjvhxrM9EQU072Pmzn2gpGM6K5gAReN+LtP+VSBC61x7GQJxBaJNtk11PXkgG99EdFi9vvgEBbM9bdcawvf8jxvjgsgdaDx/1cypDdnaL8eistmyv1YI67bKvrSPCEh55b90hl3o3vW4W5G4gcABoyORON96Y+i9AgMBAAECggEBAKp+tyNH0QiMo13fjFpHR2vFnsKSAPwXj063nx2kzqXUeqlp5yOE+LXmNSzjGpOCy1XJM474BRRUvsP1jkODLq4JNiF+RZP4Vij/CfDWZho33jxSUrIsiUGluxtfJiHV+A++s4zdZK/NhP+XyHYah0gEqUaTvl8q6Zhu0yH5sDCZHDLxDBpgiT5qD3lli8/o2xzzBdaibZdjQyHi9v5Yi3+ysly1tmfmqnkXSsevAubwJu504WxvDUSo7hPpG4a8Xb8ODqL738GIF2UY/olCcGkWqTQEr2pOqG9XbMmlUWnxG62GCfK6KtGfIzCyBBkGO2PZa9aPhVnv2bkYxI4PkLkCgYEAzAp7xH88UbSX31suDRa4jZwgtzhJLeyc3YxO5C4XyWZ89oWrA30V1KvfVwFRavYRJW07a+r0moba+0E1Nj5yZVXPOVu0bWd9ZyMbdH2L6MRZoJWU5bUOwyruulRCkqASZbWo4G05NOVesOyY1bhZGE7RyUW0vOo8tSyyRQ8nUGMCgYEA6jTQbDry4QkUP9tDhvc8+LsobIF1mPLEJui+mT98+9IGar6oeVDKekmNDO0Dx2+miLfjMNhCb5qUc8g036ZsekHt2WuQKunADua0coB00CebMdr6AQFf7QOQ/RuA+/gPJ5G0GzWB3YOQ5gE88tTCO/jBfmikVOZvLtgXUGjo3F8CgYEAl2poMoehQZjc41mMsRXdWukztgPE+pmORzKqENbLvB+cOG01XV9j5fCtyqklvFRioP2QjSNM5aeRtcbMMDbjOaQWJaCSImYcP39kDmxkeRXM1UhruJNGIzsm8Ys55Al53ZSTgAhN3Z0hSfYp7N/i7hD/yXc7Cr5g0qoamPkH2bUCgYApf0oeoyM9tDoeRl9knpHzEFZNQ3LusrUGn96FkLY4eDIi371CIYp+uGGBlM1CnQnI16wtj2PWGnGLQkH8DqTR1LSr/V8B+4DIIyB92TzZVOsunjoFy5SPjj42WpU0D/O/cxWSbJyh/xnBZx7Bd+kibyT5nNjhIiM5DZiz6qK3yQKBgAOO/MFKHKpKOXrtafbqCyculG/ope2u4eBveHKO6ByWcUSbuD9ebtr7Lu5AC5tKUJLkSyRx4EHk71bqP1yOITj8z9wQWdVyLxtVtyj9SUkUNvGwIj+F7NJ5VgHzWVZtvYWDCzrfxkEhKk3DRIIVjqmEohJcaOZoZ2Q/f8sjlId6",
    693 		"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1NzommDoutE+FVAbgovPb5ioRRS1k93hhH5Mpe4KfAQb1k0aGA/TjrFr2HcRbOtldo6Fe+RRCAm5Go+sBx829zyMEdXbGtR4Pym78xYoRpsCwD+2AK/edQLjsdDf9zXZod9ig/Pe59awYaeuSFyK/w9q94ncuiE7m+MKfXJnTS/qiwwkxWIRm9lBprPIT0DwXCtoh7FdpsOmjLu2QdGADV+9KSDgV5IbVcxwjPY03vHJS4UAIP5eS46TtSrNF3hyM9Q8vGIPAixOVyAY53cRQUxZWU/FIaNjaEgpreUQfK1pgr0gxh1K7IKwmyF3f/JgL0urFljYp2UonzRU5XKHJAgMBAAECggEBAKVT2HLDqTlY+b/LNGcXY+H4b+LHuS2HdUUuuGU9MKN+HWpIziuQSoi4g1hNOgp9ezgqBByQpAHBE/jQraQ3NKZ55xm3TQDm1qFTb8SfOGL4Po2iSm0IL+VA2jWnpjmgjOmshXACusPmtfakE+55uxM3TUa16UQDyfCBfZZEtnaFLTYzJ7KmD2GPot8SCxJBqNmW7AL8pMSIxMC3cRxUbK4R3+KIisXUuB50jZH3zGHxi34e2jA6gDeFmzgHCDJRidHMsCTHTaATzlvVz9YwwNqPQaYY7OFouZXwFxVAxIg/1zVvLc3zx1gWt+UDFeI7h6Eq0h5DZPdUiR4mrhAKd70CgYEAw6WKbPgjzhJI9XVmnu0aMHHH4MK8pbIq4kddChw24yZv1e9qnNTHw3YK17X9Fqog9CU1OX3M/vddfQbc34SorBmtmGYgOfDSuXTct52Ppyl4CRwndYQc0A88Hw+klluTEPY3+NRV6YSzv8vkNMasVuOh0YI1xzbpc+Bb5LL3kwMCgYEA7R4PLYYmtzKAY2YTQOXGBh3xd6UEHgks30W+QzDxvOv75svZt6yDgiwJzXtyrQzbNaH6yca5nfjkqyhnHwpguJ6DK7+S/RnZfVib5MqRwiU7g8l3neKhIXs6xZxfORunDU9T5ntbyNaGv/TJ2cXNw+9VskhBaHfEN/kmaBNNuEMCgYARLuzlfTXH15tI07Lbqn9uWc/wUao381oI3bOyO6Amey2/YHPAqn+RD0EMiRNddjvGta3jCsWCbz9qx7uGdiRKWUcB55ZVAG3BlB3+knwXdnDwe+SLUbsmGvBw2fLesdRM3RM1a5DQHbOb2NCGQhzI1N1VhVYr1QrT/pSTlZRg+QKBgCE05nc/pEhfoC9LakLaauMManaQ+4ShUFFsWPrb7d7BRaPKxJC+biRauny2XxbxB/n410BOvkvrQUre+6ITN/xi5ofH6nPbnOO69woRfFwuDqmkG0ZXKK2hrldiUMuUnc51X5CVkgMMWA6l32bKFsjryZqQF+jjbO1RzRkiKu41AoGAHQer1NyajHEpEfempx8YTsAnOn+Hi33cXAaQoTkS41lX2YK0cBkD18yhubczZcKnMW+GRKZRYXMm0NfwiuIo5oIYWeO6K+rXF+SKptC5mnw/3FhDVnghDAmEqOcRSWnFXARk1WEbFtwG5phDeFrWXsqPzGAjoZ8bhLvKRsrG4OM=",
    694 		"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBdNw4C8zUmLDkV+mTSqevRzBs28V7/nND5Onu26Yr9mdPfujtfQVQRevE2L52SGZ4nSCxqI34lAY1R7C+lKQ8gBcq+L3TxpJ8IaOztsaUkIkK4O4vl3qbuFmc/2u318lzvQYSU+kbSNz19fCXPtOWw9vZ5xq2YbTljiM/B0L6g3gw0K/3JDMS8JUzOXvoQlozrTaQgcLUIhKfSsMWZh32tI3tc+U0nDUXo9ukn8FZD6lccrDc4TA1MRMBQ1iJUadlT4HtrttkL1/r9o9sm5W3xCaD5ScO9bVjyCZ8efFpYbZ/lMMG8IeZxi25whk8tAPi2sCjMLivKqWYJZA0pu3TAgMBAAECggEAZMYWLU/gTGKZyukMsIB0JzcjP6GgFv4uVxC414ct4brCiEOo3IWCrhUuQuVRGdaPIodfT4xpIDMjpL+Kj0xo3WcwKl9WqynGhskTOHueqCc+bB9NlBcJdHKso77eAu9ybkrqDcQOKvtitvF9eZvtppyyOqlLXfQ5wlavf5atykamHP6JTUdXDkF7EOvoBxN0a2JsUObxr83hWo6KVuvltV/BNvjFv0wQc2jJ3V/y9wvfLwhfjTWo2PMFoGS1M3cn4JkTn2MDDRSd/A1BTOdE6FAZDeOVKV7AmLF5BsIy4QOH86Aj7qenPGKT6bJnR7SHRhn0WLxNXrdCqtZM9WVZsQKBgQD9M8EMgAumo/ydVTj87UxvMCv7jMGaD+sCT3DCqVW4gv1KMi5O7MZnOFG7chdh/X0pgb+rh7zYGUCvL2lOMN4/wb9yGZm2JvFEFh2P9ZahqiyWjYcIo1mOPcQVu5XOCusWDISA084sHOLGFvhkuDi1giQljz5eTccCcFgHlP02KQKBgQDDmBm43jixdx14r29T97PZq5cwap3ZGBWcT3ZhqK9T400nHF+QmJVLVoTrl6eh21CVafdh8gHAgn4zuiNdxJKaxlehzaEAX+luq0htQMTiqLvWrPzQieP9wnB8Cz9ECC/oAFyjALF0+c+7vWf3b4JTPWChEl35caJgZLFoSpRrmwKBgQDGE+ew5La4nU7wsgvL6cPCs9ekiR+na0541zaqQhhaKLcHhSwu+BHaC/f8gKuEL+7rOqJ8CMsV7uNoaNmjnp0vGV2wYBCcq+hQUFC+HuzA+cS53mvFuSxFF1K/gakWr/nqnM5HjeqbHdnWB4A4ItnSPMYUT/QFiCjoYoSrIcXYyQKBgFveTwaT6dEA/6i1zfaEe8cbX1HwYd+b/lqCwDmyf1dJhe1+2CwUXtsZ8iit/KB7YGgtc3Jftw7yu9AT95SNRcbIrlRjPuHsKro+XTBjoZZMZp24dq6Edb+02hyJM9gCeG3h7aDqLG+i/j1SA0km6PGr/HzrIZSOGRRpdyJjFT9NAoGBAKfW5NSxvd5np2UrzjqU+J/BsrQ2bzbSyMrRnQTjJSkifEohPRvP4Qy0o9Pkvw2DOCVdoY67+BhDCEC6uvr4RbWi9MJr832tJn3CdT/j9+CZzUFezT8ldnAwCJMBoRTX46tg5rw5u67af0O/x0L00Daqhsu7nQE8Kvx7pFAn6fFO",
    695 		"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCq1BCPAUsc97P7u4X0Bfu68sUebdLI0ijOGFWYaHEcizTF2BGdkqbOZmQV2sW5d10FMCCVTgLa7d3DXSMk7VpYgVAXxsaREdkbs93bn9eZZYFE+Y4nE0t5YGqmPQb7bNMyCcBXvaEAtIMVjb9AOzFS2F6crDRKumPUtTC9FvJVBDx8a7i/QcAIWeU5faEJDCF8CcatvRXvRjYgm774w/vqLj2Z3S9HQy/dZuwQlQ2nV9MhTOSBYHfWJy9+s2ZpoDHDkWQAT4p+STKWFHGLmLlFHVdBQg1ZzYqPYquj4Ilqsob73NqwzI3v4PbfSCkRKLyte/VLBG7zrkVHeAA10NIzAgMBAAECggEAJQLTH5ihJIKKTTUAvbD6LDPi/0e+DmJyEsz05pNiRlPmuCKrFl+qojdO4elHQ3qX/cLCnHaNac91Z5lrPtnp5BkIOE6JwO6EAluC6s2D0alLS51h7hdhF8gK8z9vntOiIko4kQn1swhpCidu00S/1/om7Xzly3b8oB4tlBo/oKlyrhoZr9r3VDPwJVY1Z9r1feyjNtUVblDRRLBXBGyeCqUhPgESM+huNIVl8QM7zXMs0ie2QrjWSevF6Hzcdxqf05/UwVj0tfMrWf9kTz6aUR1ZUYuzuVxEn96xmrsnvAXI9BTYpRKdZzTfL5gItxdvfF6uPrK0W9QNS9ZIk7EUgQKBgQDOzP82IsZhywEr0D4bOm6GIspk05LGEi6AVVp1YaP9ZxGGTXwIXpXPbWhoZh8o3smnVgW89kD4xIA+2AXJRS/ZSA+XCqlIzGSfekd8UfLM6o6zDiC0YGgce4xMhcHXabKrGquEp64a4hrs3JcrQCM0EqhFlpOWrX3On4JJI/QlwQKBgQDTeDQizbn/wygAn1kccSBeOx45Pc8Bkpcq8KxVYsYpwpKcz4m7hqPIcz8kOofWGFqjV2AHEIoDm5OB5DwejutKJQIJhGln/boS5fOJDhvOwSaV8Lo7ehcqGqD1tbvZfDQJWjEf6acj2owIBNU5ni0GlHo/zqyu+ibaABPH36f88wKBgA8e/io/MLJF3bgOafwjsaEtOg9VSQ4iljPcCdk7YnpM5wMi90bFY77fCRtZHD4ozCXoLFM8zlNiSt5NfV7SKEWC92Db7rTb/R+MGV4Fv/Mr03NUPR/zTKmIfyG5RgsyN1Y7hP8WI6zji4R2PLd04R4Vnyg3cmM6HFDXaPdgIaIBAoGAKOYPl0eYmImi+/PVpTWP4Amo/8MffRtf1zMy8VSoJL1345IT/ku883CunpAfY13UcdDdRqCBQM9fCPkeU36qrO1ZZoPQawdcbHlCz5gF8sfScZ9cNVKYllEOHldmnFp0Kfbil1x2Me37tTVSE9GuvZ4LwrlzFmhVCUaIjNiJwdcCgYBnR7lp+rnJpXPkvllArmrKEvhcyCbcDIEGaV8aPUsXfXoVMUaiVEybdUrL3IuLtNgiab3qNZ/knYSsuAW+0tnoaOhRCUFzK47x+uLFFKCMw4FOOOJJzVu8E/5Lu0d6FpU7MuVXMa0UUGIqfOYNGywuo3XOIfWHh3iSHUg1X6/+1A==",
    696 		"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSIsx0TsUCeSHXDYPzViqRwB/wZhBkj5f0Mrc+Q0yogUmiTcubYQcf/xj9LOvtArJ+8/rori0j8aFX17jZqtFyDDINyhICT+i5bk1ZKPt/uH/H5oFpjtsL+bCoOF8F4AUeELExH0dO3uwl8v9fPZZ3AZEGj6UB6Ru13LON7fKHt+JT6s9jNtUIUpHUDg2GZYv9gLFGDDm9H91Yervl8yF6VWbK+7pcVyhlz5wqHR/qNUiyUXhiie+veiJc9ipCU7RriNEuehvF12d3rRIOK/wRsFAG4LxufJS8Shu8VJrOBlKzsufqjDZtnZb8SrTY0EjLJpslMf67zRDD1kEDpq4jAgMBAAECggEBAMeKxe2YMxpjHpBRRECZTTk0YN/ue5iShrAcTMeyLqRAiUS3bSXyIErw+bDIrIxXKFrHoja71x+vvw9kSSNhQxxymkFf5nQNn6geJxMIiLJC6AxSRgeP4U/g3jEPvqQck592KFzGH/e0Vji/JGMzX6NIeIfrdbx3uJmcp2CaWNkoOs7UYV5VbNDaIWYcgptQS9hJpCQ+cuMov7scXE88uKtwAl+0VVopNr/XA7vV+npsESBCt3dfnp6poA13ldfqReLdPTmDWH7Z8QrTIagrfPi5mKpxksTYyC0/quKyk4yTj8Ge5GWmsXCHtyf19NX7reeJa8MjEWonYDCdnqReDoECgYEA8R5OHNIGC6yw6ZyTuyEt2epXwUj0h2Z9d+JAT9ndRGK9xdMqJt4acjxfcEck2wjv9BuNLr5YvLc4CYiOgyqJHNt5c5Ys5rJEOgBZ2IFoaoXZNom2LEtr583T4RFXp/Id8ix85D6EZj8Hp6OvZygQFwEYQexY383hZZh5enkorUECgYEA3xr3u/SbttM86ib1RP1uuON9ZURfzpmrr2ubSWiRDqwift0T2HesdhWi6xDGjzGyeT5e7irf1BsBKUq2dp/wFX6+15A6eV12C7PvC4N8u3NJwGBdvCmufh5wZ19rerelaB7+vG9c+Nbw9h1BbDi8MlGs06oVSawvwUzp2oVKLmMCgYEAq1RFXOU/tnv3GYhQ0N86nWWPBaC5YJzK+qyh1huQxk8DWdY6VXPshs+vYTCsV5d6KZKKN3S5yR7Hir6lxT4sP30UR7WmIib5o90r+lO5xjdlqQMhl0fgXM48h+iyyHuaG8LQ274whhazccM1l683/6Cfg/hVDnJUfsRhTU1aQgECgYBrZPTZcf6+u+I3qHcqNYBl2YPUCly/+7LsJzVB2ebxlCSqwsq5yamn0fRxiMq7xSVvPXm+1b6WwEUH1mIMqiKMhk1hQJkVMMsRCRVJioqxROa8hua4G6xWI1riN8lp8hraCwl+NXEgi37ESgLjEFBvPGegH+BNbWgzeU2clcrGlwKBgHBxlFLf6AjDxjR8Z5dnZVPyvLOUjejs5nsLdOfONJ8F/MU0PoKFWdBavhbnwXwium6NvcearnhbWL758sKooZviQL6m/sKDGWMq3O8SCnX+TKTEOw+kLLFn4L3sT02WaHYg+C5iVEDdGlsXSehhI2e7hBoTulE/zbUkbA3+wlmv",
    697 		"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6LR5HNVS8rwA6P8U9TGOwEQ1Z8bVTCfWXJ+SjzPNYaTh/YWHA9bg+0TIKbXB9yxPVETKbEBYaP953OcIXJjGFtHNi4snhOP2/F61XoGkLltSDE2tOaGQJ0gQ5uhkGjmK2jfptBcESAZ2W4UzQkV6mGej194leGLjtxdk0A9b/Rk0MPMDrurnHH818pU2XsWfEabUGFAQlU4SuZmLHPqnxMDkOXjnOQdyXweSeMtQVYgiUOy8xkY+ecAbm7f+HGuZM5uSaAg/6z7xOpvVJeACI2PVme6pGV46o5yJUO56tt/ioCmrvgun7LqDDU0VxPuiX5WuwGeNUFrHi0boz3XivAgMBAAECggEAdWgYjQ1rx6WQvisTBooS36iRQ+Ry1dAVCWLGBCouV9XbJDFURSxwKWUhaoQDicC0XAyBXloxphIbCBLrfE/AsTHQBk9AwoB/PLAAx57IP9+5WoO3ivW4CJ1hvsnGGGVYiQlWIMSdMe7E465nE6xpBNSYHe0huq5aiM/ZHr1BKy+l5T2z2k0437+3d8RhSfwlW8T7WYWK2rQZ3hPq9Cl+gDvyvcMNt2Wo9AGonwB+XtrF13tF3nqnPx8jomj4pbmFXMzKR5RsgWNX2Fec064e53OQzkYhqQ6mByUPA//UxfOO1BtNwhFQUjNEZCYMKWcD3EoR17dcosX/GlHt+MZGuQKBgQDWBdDKqV3zZSjeUJwnkd3ykdNdVggqJiNfLww3owUG1E/VUHZuvYzsJbyWp0g+rLESqa+sPp8cKP93q1ve4Dw9Dqp4ejR8hqYUEzq2Adrcgb30WDj5IZRnku34CGsq/wUP9IOyA7chZYONzllY07m/W9ZZcSwG6ziXFeyPj4XzbQKBgQDesR4jMSEys2b5PA4MO+rQYgbKj+lVzHn4uYX0ghhuoYwZYEZ0yJKyDztbgD2x7/DP8bYAZTuksqRk4Ss/bS6iRDZlGQQaXVNeEJMiIMbLCDxx69I312nYHgZ0/ETyk/5eOdJkObshkTrFA0UO13c9t4jRQfNdjTepQj56mTcvCwKBgQCQXaXkPnCoULFLnNZofqVXDXSkvfaN7+HmP8ce9HDclXQwcLEiq+uWEzJt8PLzi+t5qkpchnUvOpxwbX9wDJO1n+HvmIc1BGKcogf1Y7TtDvtCCgyMSFFhuCObLpqTiygwBgCboJP0DBS8H9f26gKeiOVCues304z9pQVIJUj21QKBgBsUDGcZFUFWAUJzI/4m1wGpucutviC5sWcmH/zASPpC2IdJZqfSr8vJAF269UWKuIyAhrH7nUoEkurVWm3m99GxW6/lX9NY38dDWrC+rY2Indj4ZOJ3Zh5qYDyfZD7e8gJBI60eO/vz7eKA6EfKuWwewhs32sDYaBlDvdcohEZLAoGBAIoWjKNJg02dKQUU4df1BjhvEw5pSEh4hGDBR12cD52ibqGPLF36TBwVnNL284BXipjBWejzvVnCUAzflym4UgMUidhJxpVrVJSx0Tdclr0+70Lz6emtNA4e+A9ttJLwuiZrmct7G9FWJ6GgBa/1z7a+/qRLM4SMxgbMufQcIl+r",
    698 	}
    699 
    700 	if diff := len(accountsSorted) - len(preserializedKeys); diff > 0 {
    701 		keyStrings := make([]string, diff)
    702 		for i := 0; i < diff; i++ {
    703 			priv, _ := rsa.GenerateKey(rand.Reader, 2048)
    704 			key, _ := x509.MarshalPKCS8PrivateKey(priv)
    705 			keyStrings = append(keyStrings, base64.StdEncoding.EncodeToString(key))
    706 		}
    707 		panic(fmt.Sprintf("mismatch between number of hardcoded test RSA keys and accounts used for test data. Insert the following generated key[s]: \n%+v", keyStrings))
    708 	}
    709 
    710 	for i, v := range accountsSorted {
    711 		premadeBytes, err := base64.StdEncoding.DecodeString(preserializedKeys[i])
    712 		if err != nil {
    713 			panic(err)
    714 		}
    715 		key, err := x509.ParsePKCS8PrivateKey(premadeBytes)
    716 		if err != nil {
    717 			panic(err)
    718 		}
    719 		priv, ok := key.(*rsa.PrivateKey)
    720 		if !ok {
    721 			panic(fmt.Sprintf("generated key at index %d is of incorrect type", i))
    722 		}
    723 		v.PrivateKey = priv
    724 		v.PublicKey = &priv.PublicKey
    725 	}
    726 
    727 	return accounts
    728 }
    729 
    730 func NewTestTombstones() map[string]*gtsmodel.Tombstone {
    731 	return map[string]*gtsmodel.Tombstone{
    732 		"https://somewhere.mysterious/users/rest_in_piss#main-key": {
    733 			ID:        "01GHBTVE9HQPPBDH2W5VH2DGN4",
    734 			CreatedAt: TimeMustParse("2021-11-09T19:33:45Z"),
    735 			UpdatedAt: TimeMustParse("2021-11-09T19:33:45Z"),
    736 			Domain:    "somewhere.mysterious",
    737 			URI:       "https://somewhere.mysterious/users/rest_in_piss#main-key",
    738 		},
    739 	}
    740 }
    741 
    742 // NewTestAttachments returns a map of attachments keyed according to which account
    743 // and status they belong to, and which attachment number of that status they are.
    744 func NewTestAttachments() map[string]*gtsmodel.MediaAttachment {
    745 	return map[string]*gtsmodel.MediaAttachment{
    746 		"admin_account_status_1_attachment_1": {
    747 			ID:        "01F8MH6NEM8D7527KZAECTCR76",
    748 			StatusID:  "01F8MH75CBF9JFX4ZAD54N0W0R",
    749 			URL:       "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
    750 			RemoteURL: "",
    751 			CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
    752 			UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
    753 			Type:      gtsmodel.FileTypeImage,
    754 			FileMeta: gtsmodel.FileMeta{
    755 				Original: gtsmodel.Original{
    756 					Width:  1200,
    757 					Height: 630,
    758 					Size:   756000,
    759 					Aspect: 1.9047619047619047,
    760 				},
    761 				Small: gtsmodel.Small{
    762 					Width:  256,
    763 					Height: 134,
    764 					Size:   34304,
    765 					Aspect: 1.9104477611940298,
    766 				},
    767 			},
    768 			AccountID:         "01F8MH17FWEB39HZJ76B6VXSKF",
    769 			Description:       "Black and white image of some 50's style text saying: Welcome On Board",
    770 			ScheduledStatusID: "",
    771 			Blurhash:          "LNJRdVM{00Rj%Mayt7j[4nWBofRj",
    772 			Processing:        2,
    773 			File: gtsmodel.File{
    774 				Path:        "01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
    775 				ContentType: "image/jpeg",
    776 				FileSize:    62529,
    777 				UpdatedAt:   TimeMustParse("2022-06-04T13:12:00Z"),
    778 			},
    779 			Thumbnail: gtsmodel.Thumbnail{
    780 				Path:        "01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpg",
    781 				ContentType: "image/jpeg",
    782 				FileSize:    6872,
    783 				UpdatedAt:   TimeMustParse("2022-06-04T13:12:00Z"),
    784 				URL:         "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpg",
    785 				RemoteURL:   "",
    786 			},
    787 			Avatar: FalseBool(),
    788 			Header: FalseBool(),
    789 			Cached: TrueBool(),
    790 		},
    791 		"local_account_1_status_4_attachment_1": {
    792 			ID:        "01F8MH7TDVANYKWVE8VVKFPJTJ",
    793 			StatusID:  "01F8MH82FYRXD2RC6108DAJ5HB",
    794 			URL:       "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH7TDVANYKWVE8VVKFPJTJ.gif",
    795 			RemoteURL: "",
    796 			CreatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    797 			UpdatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    798 			Type:      gtsmodel.FileTypeImage,
    799 			FileMeta: gtsmodel.FileMeta{
    800 				Original: gtsmodel.Original{
    801 					Width:  400,
    802 					Height: 280,
    803 					Size:   756000,
    804 					Aspect: 1.4285714285714286,
    805 				},
    806 				Small: gtsmodel.Small{
    807 					Width:  256,
    808 					Height: 179,
    809 					Size:   45824,
    810 					Aspect: 1.4301675977653632,
    811 				},
    812 				Focus: gtsmodel.Focus{
    813 					X: 0,
    814 					Y: 0,
    815 				},
    816 			},
    817 			AccountID:         "01F8MH1H7YV1Z7D2C8K2730QBF",
    818 			Description:       "90's Trent Reznor turning to the camera",
    819 			ScheduledStatusID: "",
    820 			Blurhash:          "LEDara58O=t5EMSOENEN9]}?aK%0",
    821 			Processing:        2,
    822 			File: gtsmodel.File{
    823 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH7TDVANYKWVE8VVKFPJTJ.gif",
    824 				ContentType: "image/gif",
    825 				FileSize:    1109138,
    826 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    827 			},
    828 			Thumbnail: gtsmodel.Thumbnail{
    829 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01F8MH7TDVANYKWVE8VVKFPJTJ.jpg",
    830 				ContentType: "image/jpeg",
    831 				FileSize:    8803,
    832 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    833 				URL:         "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01F8MH7TDVANYKWVE8VVKFPJTJ.jpg",
    834 				RemoteURL:   "",
    835 			},
    836 			Avatar: FalseBool(),
    837 			Header: FalseBool(),
    838 			Cached: TrueBool(),
    839 		},
    840 		"local_account_1_status_4_attachment_2": {
    841 			ID:        "01CDR64G398ADCHXK08WWTHEZ5",
    842 			StatusID:  "01F8MH82FYRXD2RC6108DAJ5HB",
    843 			URL:       "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01CDR64G398ADCHXK08WWTHEZ5.mp4",
    844 			RemoteURL: "",
    845 			CreatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    846 			UpdatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    847 			Type:      gtsmodel.FileTypeVideo,
    848 			FileMeta: gtsmodel.FileMeta{
    849 				Original: gtsmodel.Original{
    850 					Width:     720,
    851 					Height:    404,
    852 					Size:      290880,
    853 					Aspect:    1.78217821782178,
    854 					Duration:  Float32Ptr(15.033334),
    855 					Framerate: Float32Ptr(30.0),
    856 					Bitrate:   Uint64Ptr(1206522),
    857 				},
    858 				Small: gtsmodel.Small{
    859 					Width:  720,
    860 					Height: 404,
    861 					Size:   290880,
    862 					Aspect: 1.78217821782178,
    863 				},
    864 				Focus: gtsmodel.Focus{
    865 					X: 0,
    866 					Y: 0,
    867 				},
    868 			},
    869 			AccountID:         "01F8MH1H7YV1Z7D2C8K2730QBF",
    870 			Description:       "A cow adorably licking another cow!",
    871 			ScheduledStatusID: "",
    872 			Blurhash:          "",
    873 			Processing:        2,
    874 			File: gtsmodel.File{
    875 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01CDR64G398ADCHXK08WWTHEZ5.gif",
    876 				ContentType: "video/mp4",
    877 				FileSize:    2273532,
    878 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    879 			},
    880 			Thumbnail: gtsmodel.Thumbnail{
    881 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01CDR64G398ADCHXK08WWTHEZ5.jpg",
    882 				ContentType: "image/jpeg",
    883 				FileSize:    5272,
    884 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    885 				URL:         "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01CDR64G398ADCHXK08WWTHEZ5.jpg",
    886 				RemoteURL:   "",
    887 			},
    888 			Avatar: FalseBool(),
    889 			Header: FalseBool(),
    890 			Cached: TrueBool(),
    891 		},
    892 		"local_account_1_unattached_1": {
    893 			ID:        "01F8MH8RMYQ6MSNY3JM2XT1CQ5",
    894 			StatusID:  "", // this attachment isn't connected to a status YET
    895 			URL:       "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg",
    896 			RemoteURL: "",
    897 			CreatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    898 			UpdatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    899 			Type:      gtsmodel.FileTypeImage,
    900 			FileMeta: gtsmodel.FileMeta{
    901 				Original: gtsmodel.Original{
    902 					Width:  800,
    903 					Height: 450,
    904 					Size:   360000,
    905 					Aspect: 1.7777777777777777,
    906 				},
    907 				Small: gtsmodel.Small{
    908 					Width:  256,
    909 					Height: 144,
    910 					Size:   36864,
    911 					Aspect: 1.7777777777777777,
    912 				},
    913 				Focus: gtsmodel.Focus{
    914 					X: 0,
    915 					Y: 0,
    916 				},
    917 			},
    918 			AccountID:         "01F8MH1H7YV1Z7D2C8K2730QBF",
    919 			Description:       "the oh you meme",
    920 			ScheduledStatusID: "",
    921 			Blurhash:          "LSAd]9ogDge-R:M|j=xWIto0xXWX",
    922 			Processing:        2,
    923 			File: gtsmodel.File{
    924 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg",
    925 				ContentType: "image/jpeg",
    926 				FileSize:    27759,
    927 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    928 			},
    929 			Thumbnail: gtsmodel.Thumbnail{
    930 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg",
    931 				ContentType: "image/jpeg",
    932 				FileSize:    6177,
    933 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    934 				URL:         "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg",
    935 				RemoteURL:   "",
    936 			},
    937 			Avatar: FalseBool(),
    938 			Header: FalseBool(),
    939 			Cached: TrueBool(),
    940 		},
    941 		"local_account_1_avatar": {
    942 			ID:        "01F8MH58A357CV5K7R7TJMSH6S",
    943 			StatusID:  "", // this attachment isn't connected to a status
    944 			URL:       "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
    945 			RemoteURL: "",
    946 			CreatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    947 			UpdatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    948 			Type:      gtsmodel.FileTypeImage,
    949 			FileMeta: gtsmodel.FileMeta{
    950 				Original: gtsmodel.Original{
    951 					Width:  1092,
    952 					Height: 1800,
    953 					Size:   1965600,
    954 					Aspect: 0.6066666666666667,
    955 				},
    956 				Small: gtsmodel.Small{
    957 					Width:  155,
    958 					Height: 256,
    959 					Size:   39680,
    960 					Aspect: 0.60546875,
    961 				},
    962 				Focus: gtsmodel.Focus{
    963 					X: 0,
    964 					Y: 0,
    965 				},
    966 			},
    967 			AccountID:         "01F8MH1H7YV1Z7D2C8K2730QBF",
    968 			Description:       "a green goblin looking nasty",
    969 			ScheduledStatusID: "",
    970 			Blurhash:          "LKK9MT,p|YSNDkJ-5rsmvnwcOoe:",
    971 			Processing:        2,
    972 			File: gtsmodel.File{
    973 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
    974 				ContentType: "image/jpeg",
    975 				FileSize:    457680,
    976 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    977 			},
    978 			Thumbnail: gtsmodel.Thumbnail{
    979 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
    980 				ContentType: "image/jpeg",
    981 				FileSize:    15374,
    982 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
    983 				URL:         "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
    984 				RemoteURL:   "",
    985 			},
    986 			Avatar: TrueBool(),
    987 			Header: FalseBool(),
    988 			Cached: TrueBool(),
    989 		},
    990 		"local_account_1_header": {
    991 			ID:        "01PFPMWK2FF0D9WMHEJHR07C3Q",
    992 			StatusID:  "",
    993 			URL:       "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
    994 			RemoteURL: "",
    995 			CreatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    996 			UpdatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
    997 			Type:      gtsmodel.FileTypeImage,
    998 			FileMeta: gtsmodel.FileMeta{
    999 				Original: gtsmodel.Original{
   1000 					Width:  1018,
   1001 					Height: 764,
   1002 					Size:   777752,
   1003 					Aspect: 1.3324607329842932,
   1004 				},
   1005 				Small: gtsmodel.Small{
   1006 					Width:  256,
   1007 					Height: 192,
   1008 					Size:   49152,
   1009 					Aspect: 1.3333333333333333,
   1010 				},
   1011 				Focus: gtsmodel.Focus{
   1012 					X: 0,
   1013 					Y: 0,
   1014 				},
   1015 			},
   1016 			AccountID:         "01F8MH1H7YV1Z7D2C8K2730QBF",
   1017 			Description:       "A very old-school screenshot of the original team fortress mod for quake ",
   1018 			ScheduledStatusID: "",
   1019 			Blurhash:          "L26j{^WCs+R-N}jsxWj@4;WWxDoK",
   1020 			Processing:        2,
   1021 			File: gtsmodel.File{
   1022 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
   1023 				ContentType: "image/jpeg",
   1024 				FileSize:    517226,
   1025 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
   1026 			},
   1027 			Thumbnail: gtsmodel.Thumbnail{
   1028 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
   1029 				ContentType: "image/jpeg",
   1030 				FileSize:    42308,
   1031 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
   1032 				URL:         "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
   1033 				RemoteURL:   "",
   1034 			},
   1035 			Avatar: FalseBool(),
   1036 			Header: TrueBool(),
   1037 			Cached: TrueBool(),
   1038 		},
   1039 		"remote_account_1_status_1_attachment_1": {
   1040 			ID:        "01FVW7RXPQ8YJHTEXYPE7Q8ZY0",
   1041 			StatusID:  "01FVW7JHQFSFK166WWKR8CBA6M",
   1042 			URL:       "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg",
   1043 			RemoteURL: "http://fossbros-anonymous.io/attachments/original/13bbc3f8-2b5e-46ea-9531-40b4974d9912.jpg",
   1044 			CreatedAt: TimeMustParse("2021-09-20T12:40:37+02:00"),
   1045 			UpdatedAt: TimeMustParse("2021-09-20T12:40:37+02:00"),
   1046 			Type:      gtsmodel.FileTypeImage,
   1047 			FileMeta: gtsmodel.FileMeta{
   1048 				Original: gtsmodel.Original{
   1049 					Width:  472,
   1050 					Height: 291,
   1051 					Size:   137352,
   1052 					Aspect: 1.6219931271477663,
   1053 				},
   1054 				Small: gtsmodel.Small{
   1055 					Width:  472,
   1056 					Height: 291,
   1057 					Size:   137352,
   1058 					Aspect: 1.6219931271477663,
   1059 				},
   1060 				Focus: gtsmodel.Focus{
   1061 					X: 0,
   1062 					Y: 0,
   1063 				},
   1064 			},
   1065 			AccountID:         "01F8MH5ZK5VRH73AKHQM6Y9VNX",
   1066 			Description:       "tweet from thoughts of dog: i drank. all the water. in my bowl. earlier. but just now. i returned. to the same bowl. and it was. full again.. the bowl. is haunted",
   1067 			ScheduledStatusID: "",
   1068 			Blurhash:          "LARysgM_IU_3~pD%M_Rj_39FIAt6",
   1069 			Processing:        2,
   1070 			File: gtsmodel.File{
   1071 				Path:        "01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg",
   1072 				ContentType: "image/jpeg",
   1073 				FileSize:    19310,
   1074 				UpdatedAt:   TimeMustParse("2021-09-20T12:40:37+02:00"),
   1075 			},
   1076 			Thumbnail: gtsmodel.Thumbnail{
   1077 				Path:        "01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/small/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg",
   1078 				ContentType: "image/jpeg",
   1079 				FileSize:    19312,
   1080 				UpdatedAt:   TimeMustParse("2021-09-20T12:40:37+02:00"),
   1081 				URL:         "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/small/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg",
   1082 				RemoteURL:   "http://fossbros-anonymous.io/attachments/small/a499f55b-2d1e-4acd-98d2-1ac2ba6d79b9.jpg",
   1083 			},
   1084 			Avatar: FalseBool(),
   1085 			Header: FalseBool(),
   1086 			Cached: TrueBool(),
   1087 		},
   1088 		"remote_account_3_header": {
   1089 			ID:        "01PFPMWK2FF0D9WMHEJHR07C3R",
   1090 			StatusID:  "",
   1091 			URL:       "http://localhost:8080/fileserver/062G5WYKY35KKD12EMSM3F8PJ8/header/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
   1092 			RemoteURL: "http://fossbros-anonymous.io/attachments/small/a499f55b-2d1e-4acd-98d2-1ac2ba6d79b9.jpg",
   1093 			CreatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
   1094 			UpdatedAt: TimeMustParse("2022-06-09T13:12:00Z"),
   1095 			Type:      gtsmodel.FileTypeImage,
   1096 			FileMeta: gtsmodel.FileMeta{
   1097 				Original: gtsmodel.Original{
   1098 					Width:  472,
   1099 					Height: 291,
   1100 					Size:   137352,
   1101 					Aspect: 1.6219931271477663,
   1102 				},
   1103 				Small: gtsmodel.Small{
   1104 					Width:  472,
   1105 					Height: 291,
   1106 					Size:   137352,
   1107 					Aspect: 1.6219931271477663,
   1108 				},
   1109 				Focus: gtsmodel.Focus{
   1110 					X: 0,
   1111 					Y: 0,
   1112 				},
   1113 			},
   1114 			AccountID:         "062G5WYKY35KKD12EMSM3F8PJ8",
   1115 			Description:       "tweet from thoughts of dog: i drank. all the water. in my bowl. earlier. but just now. i returned. to the same bowl. and it was. full again.. the bowl. is haunted",
   1116 			ScheduledStatusID: "",
   1117 			Blurhash:          "LARysgM_IU_3~pD%M_Rj_39FIAt6",
   1118 			Processing:        2,
   1119 			File: gtsmodel.File{
   1120 				Path:        "062G5WYKY35KKD12EMSM3F8PJ8/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
   1121 				ContentType: "image/jpeg",
   1122 				FileSize:    19310,
   1123 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
   1124 			},
   1125 			Thumbnail: gtsmodel.Thumbnail{
   1126 				Path:        "062G5WYKY35KKD12EMSM3F8PJ8/attachment/small/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
   1127 				ContentType: "image/jpeg",
   1128 				FileSize:    20395,
   1129 				UpdatedAt:   TimeMustParse("2022-06-09T13:12:00Z"),
   1130 				URL:         "http://localhost:8080/fileserver/062G5WYKY35KKD12EMSM3F8PJ8/header/small/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
   1131 				RemoteURL:   "http://fossbros-anonymous.io/attachments/small/a499f55b-2d1e-4acd-98d2-1ac2ba6d79b9.jpg",
   1132 			},
   1133 			Avatar: FalseBool(),
   1134 			Header: TrueBool(),
   1135 			Cached: TrueBool(),
   1136 		},
   1137 	}
   1138 }
   1139 
   1140 // NewTestEmojis returns a map of gts emojis, keyed by the emoji shortcode
   1141 func NewTestEmojis() map[string]*gtsmodel.Emoji {
   1142 	return map[string]*gtsmodel.Emoji{
   1143 		"rainbow": {
   1144 			ID:                     "01F8MH9H8E4VG3KDYJR9EGPXCQ",
   1145 			Shortcode:              "rainbow",
   1146 			Domain:                 "",
   1147 			CreatedAt:              TimeMustParse("2021-09-20T12:40:37+02:00"),
   1148 			UpdatedAt:              TimeMustParse("2021-09-20T12:40:37+02:00"),
   1149 			ImageRemoteURL:         "",
   1150 			ImageStaticRemoteURL:   "",
   1151 			ImageURL:               "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
   1152 			ImagePath:              "/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
   1153 			ImageStaticURL:         "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
   1154 			ImageStaticPath:        "01AY6P665V14JJR0AFVRT7311Y/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png",
   1155 			ImageContentType:       "image/png",
   1156 			ImageStaticContentType: "image/png",
   1157 			ImageFileSize:          36702,
   1158 			ImageStaticFileSize:    10413,
   1159 			ImageUpdatedAt:         TimeMustParse("2021-09-20T12:40:37+02:00"),
   1160 			Disabled:               FalseBool(),
   1161 			URI:                    "http://localhost:8080/emoji/01F8MH9H8E4VG3KDYJR9EGPXCQ",
   1162 			VisibleInPicker:        TrueBool(),
   1163 			CategoryID:             "01GGQ8V4993XK67B2JB396YFB7",
   1164 		},
   1165 		"yell": {
   1166 			ID:                     "01GD5KP5CQEE1R3X43Y1EHS2CW",
   1167 			Shortcode:              "yell",
   1168 			Domain:                 "fossbros-anonymous.io",
   1169 			CreatedAt:              TimeMustParse("2020-03-18T13:12:00+01:00"),
   1170 			UpdatedAt:              TimeMustParse("2020-03-18T13:12:00+01:00"),
   1171 			ImageRemoteURL:         "http://fossbros-anonymous.io/emoji/yell.gif",
   1172 			ImageStaticRemoteURL:   "",
   1173 			ImageURL:               "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
   1174 			ImagePath:              "01AY6P665V14JJR0AFVRT7311Y/emoji/original/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
   1175 			ImageStaticURL:         "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/static/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
   1176 			ImageStaticPath:        "01AY6P665V14JJR0AFVRT7311Y/emoji/static/01GD5KP5CQEE1R3X43Y1EHS2CW.png",
   1177 			ImageContentType:       "image/png",
   1178 			ImageStaticContentType: "image/png",
   1179 			ImageFileSize:          10889,
   1180 			ImageStaticFileSize:    10808,
   1181 			ImageUpdatedAt:         TimeMustParse("2020-03-18T13:12:00+01:00"),
   1182 			Disabled:               FalseBool(),
   1183 			URI:                    "http://fossbros-anonymous.io/emoji/01GD5KP5CQEE1R3X43Y1EHS2CW",
   1184 			VisibleInPicker:        FalseBool(),
   1185 			CategoryID:             "",
   1186 		},
   1187 	}
   1188 }
   1189 
   1190 func NewTestEmojiCategories() map[string]*gtsmodel.EmojiCategory {
   1191 	return map[string]*gtsmodel.EmojiCategory{
   1192 		"reactions": {
   1193 			ID:        "01GGQ8V4993XK67B2JB396YFB7",
   1194 			Name:      "reactions",
   1195 			CreatedAt: TimeMustParse("2020-03-18T11:40:55+02:00"),
   1196 			UpdatedAt: TimeMustParse("2020-03-19T12:35:12+02:00"),
   1197 		},
   1198 		"cute stuff": {
   1199 			ID:        "01GGQ989PTT9PMRN4FZ1WWK2B9",
   1200 			Name:      "cute stuff",
   1201 			CreatedAt: TimeMustParse("2020-03-20T11:40:55+02:00"),
   1202 			UpdatedAt: TimeMustParse("2020-03-21T12:35:12+02:00"),
   1203 		},
   1204 	}
   1205 }
   1206 
   1207 func NewTestStatusToEmojis() map[string]*gtsmodel.StatusToEmoji {
   1208 	return map[string]*gtsmodel.StatusToEmoji{
   1209 		"admin_account_status_1_rainbow": {
   1210 			StatusID: "01F8MH75CBF9JFX4ZAD54N0W0R",
   1211 			EmojiID:  "01F8MH9H8E4VG3KDYJR9EGPXCQ",
   1212 		},
   1213 	}
   1214 }
   1215 
   1216 func NewTestInstances() map[string]*gtsmodel.Instance {
   1217 	return map[string]*gtsmodel.Instance{
   1218 		"localhost:8080": {
   1219 			ID:                     "01G774F5TSHJ2ZSF7XRC5EMT6K",
   1220 			CreatedAt:              TimeMustParse("2020-01-20T13:12:00+02:00"),
   1221 			UpdatedAt:              TimeMustParse("2020-01-20T13:12:00+02:00"),
   1222 			Domain:                 "localhost:8080",
   1223 			URI:                    "http://localhost:8080",
   1224 			Title:                  "GoToSocial Testrig Instance",
   1225 			ShortDescription:       "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>",
   1226 			Description:            "<p>This is the GoToSocial testrig. It doesn't federate or anything.</p><p>When the testrig is shut down, all data on it will be deleted.</p><p>Don't use this in production!</p>",
   1227 			ContactEmail:           "admin@example.org",
   1228 			ContactAccountUsername: "admin",
   1229 			ContactAccountID:       "01F8MH17FWEB39HZJ76B6VXSKF",
   1230 		},
   1231 		"fossbros-anonymous.io": {
   1232 			ID:        "01G5H6YMJQKR86QZKXXQ2S95FZ",
   1233 			CreatedAt: TimeMustParse("2021-09-20T12:40:37+02:00"),
   1234 			UpdatedAt: TimeMustParse("2021-09-20T12:40:37+02:00"),
   1235 			Domain:    "fossbros-anonymous.io",
   1236 			URI:       "http://fossbros-anonymous.io",
   1237 		},
   1238 		"example.org": {
   1239 			ID:        "01G5H71G52DJKVBYKXPNPNDN1G",
   1240 			CreatedAt: TimeMustParse("2020-05-13T15:29:12+02:00"),
   1241 			UpdatedAt: TimeMustParse("2020-05-13T15:29:12+02:00"),
   1242 			Domain:    "example.org",
   1243 			URI:       "http://example.org",
   1244 		},
   1245 	}
   1246 }
   1247 
   1248 func NewTestDomainBlocks() map[string]*gtsmodel.DomainBlock {
   1249 	return map[string]*gtsmodel.DomainBlock{
   1250 		"replyguys.com": {
   1251 			ID:                 "01FF22EQM7X8E3RX1XGPN7S87D",
   1252 			CreatedAt:          TimeMustParse("2020-05-13T15:29:12+02:00"),
   1253 			UpdatedAt:          TimeMustParse("2020-05-13T15:29:12+02:00"),
   1254 			Domain:             "replyguys.com",
   1255 			CreatedByAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
   1256 			PrivateComment:     "i blocked this domain because they keep replying with pushy + unwarranted linux advice",
   1257 			PublicComment:      "reply-guying to tech posts",
   1258 			Obfuscate:          FalseBool(),
   1259 		},
   1260 	}
   1261 }
   1262 
   1263 type filenames struct {
   1264 	Original string
   1265 	Small    string
   1266 	Static   string
   1267 }
   1268 
   1269 // newTestStoredAttachments returns a map of filenames, keyed according to which attachment they pertain to.
   1270 func newTestStoredAttachments() map[string]filenames {
   1271 	return map[string]filenames{
   1272 		"admin_account_status_1_attachment_1": {
   1273 			Original: "welcome-original.jpg",
   1274 			Small:    "welcome-small.jpg",
   1275 		},
   1276 		"local_account_1_status_4_attachment_1": {
   1277 			Original: "trent-original.gif",
   1278 			Small:    "trent-small.jpg",
   1279 		},
   1280 		"local_account_1_status_4_attachment_2": {
   1281 			Original: "cowlick-original.mp4",
   1282 			Small:    "cowlick-small.jpeg",
   1283 		},
   1284 		"local_account_1_unattached_1": {
   1285 			Original: "ohyou-original.jpg",
   1286 			Small:    "ohyou-small.jpg",
   1287 		},
   1288 		"local_account_1_avatar": {
   1289 			Original: "zork-original.jpg",
   1290 			Small:    "zork-small.jpg",
   1291 		},
   1292 		"local_account_1_header": {
   1293 			Original: "team-fortress-original.jpg",
   1294 			Small:    "team-fortress-small.jpg",
   1295 		},
   1296 		"remote_account_1_status_1_attachment_1": {
   1297 			Original: "thoughtsofdog-original.jpg",
   1298 			Small:    "thoughtsofdog-small.jpg",
   1299 		},
   1300 	}
   1301 }
   1302 
   1303 // newTestStoredEmoji returns a map of filenames, keyed according to which emoji they pertain to
   1304 func newTestStoredEmoji() map[string]filenames {
   1305 	return map[string]filenames{
   1306 		"rainbow": {
   1307 			Original: "rainbow-original.png",
   1308 			Static:   "rainbow-static.png",
   1309 		},
   1310 		"yell": {
   1311 			Original: "yell-original.png",
   1312 			Static:   "yell-static.png",
   1313 		},
   1314 	}
   1315 }
   1316 
   1317 // NewTestStatuses returns a map of statuses keyed according to which account
   1318 // and status they are.
   1319 func NewTestStatuses() map[string]*gtsmodel.Status {
   1320 	return map[string]*gtsmodel.Status{
   1321 		"admin_account_status_1": {
   1322 			ID:                       "01F8MH75CBF9JFX4ZAD54N0W0R",
   1323 			PinnedAt:                 TimeMustParse("2022-05-14T13:21:09+02:00"),
   1324 			URI:                      "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
   1325 			URL:                      "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
   1326 			Content:                  "hello world! #welcome ! first post on the instance :rainbow: !",
   1327 			Text:                     "hello world! #welcome ! first post on the instance :rainbow: !",
   1328 			AttachmentIDs:            []string{"01F8MH6NEM8D7527KZAECTCR76"},
   1329 			TagIDs:                   []string{"01F8MHA1A2NF9MJ3WCCQ3K8BSZ"},
   1330 			MentionIDs:               []string{},
   1331 			EmojiIDs:                 []string{"01F8MH9H8E4VG3KDYJR9EGPXCQ"},
   1332 			CreatedAt:                TimeMustParse("2021-10-20T11:36:45Z"),
   1333 			UpdatedAt:                TimeMustParse("2021-10-20T11:36:45Z"),
   1334 			Local:                    TrueBool(),
   1335 			AccountURI:               "http://localhost:8080/users/admin",
   1336 			AccountID:                "01F8MH17FWEB39HZJ76B6VXSKF",
   1337 			InReplyToID:              "",
   1338 			BoostOfID:                "",
   1339 			ContentWarning:           "",
   1340 			Visibility:               gtsmodel.VisibilityPublic,
   1341 			Sensitive:                FalseBool(),
   1342 			Language:                 "en",
   1343 			CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
   1344 			Federated:                TrueBool(),
   1345 			Boostable:                TrueBool(),
   1346 			Replyable:                TrueBool(),
   1347 			Likeable:                 TrueBool(),
   1348 			ActivityStreamsType:      ap.ObjectNote,
   1349 		},
   1350 		"admin_account_status_2": {
   1351 			ID:                       "01F8MHAAY43M6RJ473VQFCVH37",
   1352 			PinnedAt:                 TimeMustParse("2022-05-14T14:21:09+02:00"),
   1353 			URI:                      "http://localhost:8080/users/admin/statuses/01F8MHAAY43M6RJ473VQFCVH37",
   1354 			URL:                      "http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37",
   1355 			Content:                  "🐕🐕🐕🐕🐕",
   1356 			Text:                     "🐕🐕🐕🐕🐕",
   1357 			CreatedAt:                TimeMustParse("2021-10-20T12:36:45Z"),
   1358 			UpdatedAt:                TimeMustParse("2021-10-20T12:36:45Z"),
   1359 			Local:                    TrueBool(),
   1360 			AccountURI:               "http://localhost:8080/users/admin",
   1361 			AccountID:                "01F8MH17FWEB39HZJ76B6VXSKF",
   1362 			InReplyToID:              "",
   1363 			BoostOfID:                "",
   1364 			ContentWarning:           "open to see some puppies",
   1365 			Visibility:               gtsmodel.VisibilityPublic,
   1366 			Sensitive:                TrueBool(),
   1367 			Language:                 "en",
   1368 			CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
   1369 			Federated:                TrueBool(),
   1370 			Boostable:                TrueBool(),
   1371 			Replyable:                TrueBool(),
   1372 			Likeable:                 TrueBool(),
   1373 			ActivityStreamsType:      ap.ObjectNote,
   1374 		},
   1375 		"admin_account_status_3": {
   1376 			ID:                       "01FF25D5Q0DH7CHD57CTRS6WK0",
   1377 			URI:                      "http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
   1378 			URL:                      "http://localhost:8080/@admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
   1379 			Content:                  "hi @the_mighty_zork welcome to the instance!",
   1380 			Text:                     "hi @the_mighty_zork welcome to the instance!",
   1381 			CreatedAt:                TimeMustParse("2021-11-20T13:32:16Z"),
   1382 			UpdatedAt:                TimeMustParse("2021-11-20T13:32:16Z"),
   1383 			Local:                    TrueBool(),
   1384 			AccountURI:               "http://localhost:8080/users/admin",
   1385 			MentionIDs:               []string{"01FF26A6BGEKCZFWNEHXB2ZZ6M"},
   1386 			AccountID:                "01F8MH17FWEB39HZJ76B6VXSKF",
   1387 			InReplyToID:              "01F8MHAMCHF6Y650WCRSCP4WMY",
   1388 			InReplyToAccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF",
   1389 			InReplyToURI:             "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
   1390 			BoostOfID:                "",
   1391 			Visibility:               gtsmodel.VisibilityPublic,
   1392 			Sensitive:                FalseBool(),
   1393 			Language:                 "en",
   1394 			CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
   1395 			Federated:                TrueBool(),
   1396 			Boostable:                TrueBool(),
   1397 			Replyable:                TrueBool(),
   1398 			Likeable:                 TrueBool(),
   1399 			ActivityStreamsType:      ap.ObjectNote,
   1400 		},
   1401 		"admin_account_status_4": {
   1402 			ID:                       "01G36SF3V6Y6V5BF9P4R7PQG7G",
   1403 			URI:                      "http://localhost:8080/users/admin/statuses/01G36SF3V6Y6V5BF9P4R7PQG7G",
   1404 			URL:                      "http://localhost:8080/@admin/statuses/01G36SF3V6Y6V5BF9P4R7PQG7G",
   1405 			Content:                  "hello everyone!",
   1406 			Text:                     "hello everyone!",
   1407 			CreatedAt:                TimeMustParse("2021-10-20T12:41:37+02:00"),
   1408 			UpdatedAt:                TimeMustParse("2021-10-20T12:41:37+02:00"),
   1409 			Local:                    TrueBool(),
   1410 			AccountURI:               "http://localhost:8080/users/admin",
   1411 			AccountID:                "01F8MH17FWEB39HZJ76B6VXSKF",
   1412 			InReplyToID:              "",
   1413 			InReplyToAccountID:       "",
   1414 			InReplyToURI:             "",
   1415 			BoostOfID:                "01F8MHAMCHF6Y650WCRSCP4WMY",
   1416 			BoostOfAccountID:         "01F8MH1H7YV1Z7D2C8K2730QBF",
   1417 			ContentWarning:           "introduction post",
   1418 			Visibility:               gtsmodel.VisibilityPublic,
   1419 			Sensitive:                TrueBool(),
   1420 			Language:                 "en",
   1421 			CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
   1422 			Federated:                TrueBool(),
   1423 			Boostable:                TrueBool(),
   1424 			Replyable:                TrueBool(),
   1425 			Likeable:                 TrueBool(),
   1426 			ActivityStreamsType:      ap.ObjectNote,
   1427 		},
   1428 		"local_account_1_status_1": {
   1429 			ID:                       "01F8MHAMCHF6Y650WCRSCP4WMY",
   1430 			URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
   1431 			URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
   1432 			Content:                  "hello everyone!",
   1433 			Text:                     "hello everyone!",
   1434 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1435 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1436 			Local:                    TrueBool(),
   1437 			AccountURI:               "http://localhost:8080/users/the_mighty_zork",
   1438 			AccountID:                "01F8MH1H7YV1Z7D2C8K2730QBF",
   1439 			InReplyToID:              "",
   1440 			BoostOfID:                "",
   1441 			ContentWarning:           "introduction post",
   1442 			Visibility:               gtsmodel.VisibilityPublic,
   1443 			Sensitive:                TrueBool(),
   1444 			Language:                 "en",
   1445 			CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
   1446 			Federated:                TrueBool(),
   1447 			Boostable:                TrueBool(),
   1448 			Replyable:                TrueBool(),
   1449 			Likeable:                 TrueBool(),
   1450 			ActivityStreamsType:      ap.ObjectNote,
   1451 		},
   1452 		"local_account_1_status_2": {
   1453 			ID:                       "01F8MHAYFKS4KMXF8K5Y1C0KRN",
   1454 			URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAYFKS4KMXF8K5Y1C0KRN",
   1455 			URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAYFKS4KMXF8K5Y1C0KRN",
   1456 			Content:                  "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
   1457 			Text:                     "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
   1458 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1459 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1460 			Local:                    TrueBool(),
   1461 			AccountURI:               "http://localhost:8080/users/the_mighty_zork",
   1462 			AccountID:                "01F8MH1H7YV1Z7D2C8K2730QBF",
   1463 			InReplyToID:              "",
   1464 			BoostOfID:                "",
   1465 			ContentWarning:           "",
   1466 			Visibility:               gtsmodel.VisibilityUnlocked,
   1467 			Sensitive:                FalseBool(),
   1468 			Language:                 "en",
   1469 			CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
   1470 			Federated:                FalseBool(),
   1471 			Boostable:                TrueBool(),
   1472 			Replyable:                TrueBool(),
   1473 			Likeable:                 TrueBool(),
   1474 			ActivityStreamsType:      ap.ObjectNote,
   1475 		},
   1476 		"local_account_1_status_3": {
   1477 			ID:                       "01F8MHBBN8120SYH7D5S050MGK",
   1478 			URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHBBN8120SYH7D5S050MGK",
   1479 			URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01F8MHBBN8120SYH7D5S050MGK",
   1480 			Content:                  "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
   1481 			Text:                     "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
   1482 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1483 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1484 			Local:                    TrueBool(),
   1485 			AccountURI:               "http://localhost:8080/users/the_mighty_zork",
   1486 			AccountID:                "01F8MH1H7YV1Z7D2C8K2730QBF",
   1487 			InReplyToID:              "",
   1488 			BoostOfID:                "",
   1489 			ContentWarning:           "test: you shouldn't be able to interact with this post in any way",
   1490 			Visibility:               gtsmodel.VisibilityMutualsOnly,
   1491 			Sensitive:                FalseBool(),
   1492 			Language:                 "en",
   1493 			CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
   1494 			Federated:                TrueBool(),
   1495 			Boostable:                FalseBool(),
   1496 			Replyable:                FalseBool(),
   1497 			Likeable:                 FalseBool(),
   1498 			ActivityStreamsType:      ap.ObjectNote,
   1499 		},
   1500 		"local_account_1_status_4": {
   1501 			ID:                       "01F8MH82FYRXD2RC6108DAJ5HB",
   1502 			URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01F8MH82FYRXD2RC6108DAJ5HB",
   1503 			URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01F8MH82FYRXD2RC6108DAJ5HB",
   1504 			Content:                  "here's a little gif of trent.... and also a cow",
   1505 			Text:                     "here's a little gif of trent.... and also a cow",
   1506 			AttachmentIDs:            []string{"01F8MH7TDVANYKWVE8VVKFPJTJ", "01CDR64G398ADCHXK08WWTHEZ5"},
   1507 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1508 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1509 			Local:                    TrueBool(),
   1510 			AccountURI:               "http://localhost:8080/users/the_mighty_zork",
   1511 			AccountID:                "01F8MH1H7YV1Z7D2C8K2730QBF",
   1512 			InReplyToID:              "",
   1513 			BoostOfID:                "",
   1514 			ContentWarning:           "eye contact, trent reznor gif, cow",
   1515 			Visibility:               gtsmodel.VisibilityMutualsOnly,
   1516 			Sensitive:                FalseBool(),
   1517 			Language:                 "en",
   1518 			CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
   1519 			Federated:                TrueBool(),
   1520 			Boostable:                TrueBool(),
   1521 			Replyable:                TrueBool(),
   1522 			Likeable:                 TrueBool(),
   1523 			ActivityStreamsType:      ap.ObjectNote,
   1524 		},
   1525 		"local_account_1_status_5": {
   1526 			ID:                       "01FCTA44PW9H1TB328S9AQXKDS",
   1527 			URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01FCTA44PW9H1TB328S9AQXKDS",
   1528 			URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01FCTA44PW9H1TB328S9AQXKDS",
   1529 			Content:                  "hi!",
   1530 			Text:                     "hi!",
   1531 			AttachmentIDs:            []string{},
   1532 			CreatedAt:                TimeMustParse("2022-05-20T11:37:55Z"),
   1533 			UpdatedAt:                TimeMustParse("2022-05-20T11:37:55Z"),
   1534 			Local:                    TrueBool(),
   1535 			AccountURI:               "http://localhost:8080/users/the_mighty_zork",
   1536 			AccountID:                "01F8MH1H7YV1Z7D2C8K2730QBF",
   1537 			InReplyToID:              "",
   1538 			BoostOfID:                "",
   1539 			ContentWarning:           "",
   1540 			Visibility:               gtsmodel.VisibilityFollowersOnly,
   1541 			Sensitive:                FalseBool(),
   1542 			Language:                 "en",
   1543 			CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
   1544 			Federated:                TrueBool(),
   1545 			Boostable:                TrueBool(),
   1546 			Replyable:                TrueBool(),
   1547 			Likeable:                 TrueBool(),
   1548 			ActivityStreamsType:      ap.ObjectNote,
   1549 		},
   1550 		"local_account_2_status_1": {
   1551 			ID:                       "01F8MHBQCBTDKN6X5VHGMMN4MA",
   1552 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA",
   1553 			URL:                      "http://localhost:8080/@1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA",
   1554 			Content:                  "🐢 hi everyone i post about turtles 🐢",
   1555 			Text:                     "🐢 hi everyone i post about turtles 🐢",
   1556 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1557 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1558 			Local:                    TrueBool(),
   1559 			AccountURI:               "http://localhost:8080/users/1happyturtle",
   1560 			AccountID:                "01F8MH5NBDF2MV7CTC4Q5128HF",
   1561 			InReplyToID:              "",
   1562 			BoostOfID:                "",
   1563 			ContentWarning:           "introduction post",
   1564 			Visibility:               gtsmodel.VisibilityPublic,
   1565 			Sensitive:                TrueBool(),
   1566 			Language:                 "en",
   1567 			CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
   1568 			Federated:                TrueBool(),
   1569 			Boostable:                TrueBool(),
   1570 			Replyable:                TrueBool(),
   1571 			Likeable:                 TrueBool(),
   1572 			ActivityStreamsType:      ap.ObjectNote,
   1573 		},
   1574 		"local_account_2_status_2": {
   1575 			ID:                       "01F8MHC0H0A7XHTVH5F596ZKBM",
   1576 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01F8MHC0H0A7XHTVH5F596ZKBM",
   1577 			URL:                      "http://localhost:8080/@1happyturtle/statuses/01F8MHC0H0A7XHTVH5F596ZKBM",
   1578 			Content:                  "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
   1579 			Text:                     "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
   1580 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1581 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1582 			Local:                    TrueBool(),
   1583 			AccountURI:               "http://localhost:8080/users/1happyturtle",
   1584 			AccountID:                "01F8MH5NBDF2MV7CTC4Q5128HF",
   1585 			InReplyToID:              "",
   1586 			BoostOfID:                "",
   1587 			ContentWarning:           "",
   1588 			Visibility:               gtsmodel.VisibilityPublic,
   1589 			Sensitive:                TrueBool(),
   1590 			Language:                 "en",
   1591 			CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
   1592 			Federated:                TrueBool(),
   1593 			Boostable:                TrueBool(),
   1594 			Replyable:                FalseBool(),
   1595 			Likeable:                 TrueBool(),
   1596 			ActivityStreamsType:      ap.ObjectNote,
   1597 		},
   1598 		"local_account_2_status_3": {
   1599 			ID:                       "01F8MHC8VWDRBQR0N1BATDDEM5",
   1600 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
   1601 			URL:                      "http://localhost:8080/@1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
   1602 			Content:                  "🐢 i don't mind people sharing this one but I don't want likes or replies to it because cba🐢",
   1603 			Text:                     "🐢 i don't mind people sharing this one but I don't want likes or replies to it because cba🐢",
   1604 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1605 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1606 			Local:                    TrueBool(),
   1607 			AccountURI:               "http://localhost:8080/users/1happyturtle",
   1608 			AccountID:                "01F8MH5NBDF2MV7CTC4Q5128HF",
   1609 			InReplyToID:              "",
   1610 			BoostOfID:                "",
   1611 			ContentWarning:           "you won't be able to like or reply to this",
   1612 			Visibility:               gtsmodel.VisibilityUnlocked,
   1613 			Sensitive:                TrueBool(),
   1614 			Language:                 "en",
   1615 			CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
   1616 			Federated:                TrueBool(),
   1617 			Boostable:                TrueBool(),
   1618 			Replyable:                FalseBool(),
   1619 			Likeable:                 FalseBool(),
   1620 			ActivityStreamsType:      ap.ObjectNote,
   1621 		},
   1622 		"local_account_2_status_4": {
   1623 			ID:                       "01F8MHCP5P2NWYQ416SBA0XSEV",
   1624 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01F8MHCP5P2NWYQ416SBA0XSEV",
   1625 			URL:                      "http://localhost:8080/@1happyturtle/statuses/01F8MHCP5P2NWYQ416SBA0XSEV",
   1626 			Content:                  "🐢 this is a public status but I want it local only and not boostable 🐢",
   1627 			Text:                     "🐢 this is a public status but I want it local only and not boostable 🐢",
   1628 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1629 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1630 			Local:                    TrueBool(),
   1631 			AccountURI:               "http://localhost:8080/users/1happyturtle",
   1632 			AccountID:                "01F8MH5NBDF2MV7CTC4Q5128HF",
   1633 			InReplyToID:              "",
   1634 			BoostOfID:                "",
   1635 			ContentWarning:           "",
   1636 			Visibility:               gtsmodel.VisibilityPublic,
   1637 			Sensitive:                TrueBool(),
   1638 			Language:                 "en",
   1639 			CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
   1640 			Federated:                FalseBool(),
   1641 			Boostable:                FalseBool(),
   1642 			Replyable:                TrueBool(),
   1643 			Likeable:                 TrueBool(),
   1644 
   1645 			ActivityStreamsType: ap.ObjectNote,
   1646 		},
   1647 		"local_account_2_status_5": {
   1648 			ID:                       "01FCQSQ667XHJ9AV9T27SJJSX5",
   1649 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01FCQSQ667XHJ9AV9T27SJJSX5",
   1650 			URL:                      "http://localhost:8080/@1happyturtle/statuses/01FCQSQ667XHJ9AV9T27SJJSX5",
   1651 			Content:                  "🐢 @the_mighty_zork hi zork! 🐢",
   1652 			Text:                     "🐢 @the_mighty_zork hi zork! 🐢",
   1653 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1654 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1655 			Local:                    TrueBool(),
   1656 			AccountURI:               "http://localhost:8080/users/1happyturtle",
   1657 			MentionIDs:               []string{"01FDF2HM2NF6FSRZCDEDV451CN"},
   1658 			AccountID:                "01F8MH5NBDF2MV7CTC4Q5128HF",
   1659 			InReplyToID:              "01F8MHAMCHF6Y650WCRSCP4WMY",
   1660 			InReplyToAccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF",
   1661 			InReplyToURI:             "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
   1662 			BoostOfID:                "",
   1663 			ContentWarning:           "",
   1664 			Visibility:               gtsmodel.VisibilityPublic,
   1665 			Sensitive:                FalseBool(),
   1666 			Language:                 "en",
   1667 			CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
   1668 			Federated:                TrueBool(),
   1669 			Boostable:                TrueBool(),
   1670 			Replyable:                TrueBool(),
   1671 			Likeable:                 TrueBool(),
   1672 			ActivityStreamsType:      ap.ObjectNote,
   1673 		},
   1674 		"local_account_2_status_6": {
   1675 			ID:                       "01FN3VJGFH10KR7S2PB0GFJZYG",
   1676 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01FN3VJGFH10KR7S2PB0GFJZYG",
   1677 			URL:                      "http://localhost:8080/@1happyturtle/statuses/01FN3VJGFH10KR7S2PB0GFJZYG",
   1678 			Content:                  "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢",
   1679 			Text:                     "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢",
   1680 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1681 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1682 			Local:                    TrueBool(),
   1683 			AccountURI:               "http://localhost:8080/users/1happyturtle",
   1684 			MentionIDs:               []string{"01FDF2HM2NF6FSRZCDEDV451CN"},
   1685 			AccountID:                "01F8MH5NBDF2MV7CTC4Q5128HF",
   1686 			InReplyToID:              "",
   1687 			InReplyToAccountID:       "",
   1688 			InReplyToURI:             "",
   1689 			BoostOfID:                "",
   1690 			ContentWarning:           "",
   1691 			Visibility:               gtsmodel.VisibilityDirect,
   1692 			Sensitive:                FalseBool(),
   1693 			Language:                 "en",
   1694 			CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
   1695 			Federated:                TrueBool(),
   1696 			Boostable:                TrueBool(),
   1697 			Replyable:                TrueBool(),
   1698 			Likeable:                 TrueBool(),
   1699 			ActivityStreamsType:      ap.ObjectNote,
   1700 		},
   1701 		"local_account_2_status_7": {
   1702 			ID:                       "01G20ZM733MGN8J344T4ZDDFY1",
   1703 			PinnedAt:                 TimeMustParse("2021-03-18T09:13:55+02:00"),
   1704 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01G20ZM733MGN8J344T4ZDDFY1",
   1705 			URL:                      "http://localhost:8080/@1happyturtle/statuses/01G20ZM733MGN8J344T4ZDDFY1",
   1706 			Content:                  "🐢 hi followers! did u know i'm a turtle? 🐢",
   1707 			Text:                     "🐢 hi followers! did u know i'm a turtle? 🐢",
   1708 			AttachmentIDs:            []string{},
   1709 			CreatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1710 			UpdatedAt:                TimeMustParse("2021-10-20T12:40:37+02:00"),
   1711 			Local:                    TrueBool(),
   1712 			AccountURI:               "http://localhost:8080/users/1happyturtle",
   1713 			AccountID:                "01F8MH5NBDF2MV7CTC4Q5128HF",
   1714 			InReplyToID:              "",
   1715 			BoostOfID:                "",
   1716 			ContentWarning:           "",
   1717 			Visibility:               gtsmodel.VisibilityFollowersOnly,
   1718 			Sensitive:                FalseBool(),
   1719 			Language:                 "en",
   1720 			CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
   1721 			Federated:                TrueBool(),
   1722 			Boostable:                TrueBool(),
   1723 			Replyable:                TrueBool(),
   1724 			Likeable:                 TrueBool(),
   1725 			ActivityStreamsType:      ap.ObjectNote,
   1726 		},
   1727 		"remote_account_1_status_1": {
   1728 			ID:                       "01FVW7JHQFSFK166WWKR8CBA6M",
   1729 			URI:                      "http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M",
   1730 			URL:                      "http://fossbros-anonymous.io/@foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M",
   1731 			Content:                  "dark souls status bot: \"thoughts of dog\"",
   1732 			AttachmentIDs:            []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"},
   1733 			CreatedAt:                TimeMustParse("2021-09-20T12:40:37+02:00"),
   1734 			UpdatedAt:                TimeMustParse("2021-09-20T12:40:37+02:00"),
   1735 			Local:                    FalseBool(),
   1736 			AccountURI:               "http://fossbros-anonymous.io/users/foss_satan",
   1737 			MentionIDs:               []string{},
   1738 			AccountID:                "01F8MH5ZK5VRH73AKHQM6Y9VNX",
   1739 			InReplyToID:              "",
   1740 			InReplyToAccountID:       "",
   1741 			InReplyToURI:             "",
   1742 			BoostOfID:                "",
   1743 			ContentWarning:           "",
   1744 			Visibility:               gtsmodel.VisibilityUnlocked,
   1745 			Sensitive:                FalseBool(),
   1746 			Language:                 "en",
   1747 			CreatedWithApplicationID: "",
   1748 			Federated:                TrueBool(),
   1749 			Boostable:                TrueBool(),
   1750 			Replyable:                TrueBool(),
   1751 			Likeable:                 TrueBool(),
   1752 			ActivityStreamsType:      ap.ObjectNote,
   1753 		},
   1754 	}
   1755 }
   1756 
   1757 // NewTestTags returns a map of gts model tags keyed by their name
   1758 func NewTestTags() map[string]*gtsmodel.Tag {
   1759 	return map[string]*gtsmodel.Tag{
   1760 		"welcome": {
   1761 			ID:                     "01F8MHA1A2NF9MJ3WCCQ3K8BSZ",
   1762 			URL:                    "http://localhost:8080/tags/welcome",
   1763 			Name:                   "welcome",
   1764 			FirstSeenFromAccountID: "",
   1765 			CreatedAt:              TimeMustParse("2022-05-14T13:21:09+02:00"),
   1766 			UpdatedAt:              TimeMustParse("2022-05-14T13:21:09+02:00"),
   1767 			Useable:                TrueBool(),
   1768 			Listable:               TrueBool(),
   1769 			LastStatusAt:           TimeMustParse("2022-05-14T13:21:09+02:00"),
   1770 		},
   1771 		"Hashtag": {
   1772 			ID:                     "01FCT9SGYA71487N8D0S1M638G",
   1773 			URL:                    "http://localhost:8080/tags/Hashtag",
   1774 			Name:                   "Hashtag",
   1775 			FirstSeenFromAccountID: "",
   1776 			CreatedAt:              TimeMustParse("2022-05-14T13:21:09+02:00"),
   1777 			UpdatedAt:              TimeMustParse("2022-05-14T13:21:09+02:00"),
   1778 			Useable:                TrueBool(),
   1779 			Listable:               TrueBool(),
   1780 			LastStatusAt:           TimeMustParse("2022-05-14T13:21:09+02:00"),
   1781 		},
   1782 	}
   1783 }
   1784 
   1785 func NewTestStatusToTags() map[string]*gtsmodel.StatusToTag {
   1786 	return map[string]*gtsmodel.StatusToTag{
   1787 		"admin_account_status_1_welcome": {
   1788 			StatusID: "01F8MH75CBF9JFX4ZAD54N0W0R",
   1789 			TagID:    "01F8MHA1A2NF9MJ3WCCQ3K8BSZ",
   1790 		},
   1791 	}
   1792 }
   1793 
   1794 // NewTestMentions returns a map of gts model mentions keyed by their name.
   1795 func NewTestMentions() map[string]*gtsmodel.Mention {
   1796 	return map[string]*gtsmodel.Mention{
   1797 		"zork_mention_foss_satan": {
   1798 			ID:               "01FCTA2Y6FGHXQA4ZE6N5NMNEX",
   1799 			StatusID:         "01FCTA44PW9H1TB328S9AQXKDS",
   1800 			CreatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1801 			UpdatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1802 			OriginAccountID:  "01F8MH1H7YV1Z7D2C8K2730QBF",
   1803 			OriginAccountURI: "http://localhost:8080/users/the_mighty_zork",
   1804 			TargetAccountID:  "01F8MH5ZK5VRH73AKHQM6Y9VNX",
   1805 			NameString:       "@foss_satan@fossbros-anonymous.io",
   1806 			TargetAccountURI: "http://fossbros-anonymous.io/users/foss_satan",
   1807 			TargetAccountURL: "http://fossbros-anonymous.io/@foss_satan",
   1808 		},
   1809 		"local_user_2_mention_zork": {
   1810 			ID:               "01FDF2HM2NF6FSRZCDEDV451CN",
   1811 			StatusID:         "01FCQSQ667XHJ9AV9T27SJJSX5",
   1812 			CreatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1813 			UpdatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1814 			OriginAccountID:  "01F8MH5NBDF2MV7CTC4Q5128HF",
   1815 			OriginAccountURI: "http://localhost:8080/users/1happyturtle",
   1816 			TargetAccountID:  "01F8MH1H7YV1Z7D2C8K2730QBF",
   1817 			NameString:       "@the_mighty_zork",
   1818 			TargetAccountURI: "http://localhost:8080/users/the_mighty_zork",
   1819 			TargetAccountURL: "http://localhost:8080/@the_mighty_zork",
   1820 		},
   1821 		"local_user_2_mention_zork_direct_message": {
   1822 			ID:               "01FN3VKDEF4CN2W9TKX339BEHB",
   1823 			StatusID:         "01FN3VJGFH10KR7S2PB0GFJZYG",
   1824 			CreatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1825 			UpdatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1826 			OriginAccountID:  "01F8MH5NBDF2MV7CTC4Q5128HF",
   1827 			OriginAccountURI: "http://localhost:8080/users/1happyturtle",
   1828 			TargetAccountID:  "01F8MH1H7YV1Z7D2C8K2730QBF",
   1829 			NameString:       "@the_mighty_zork",
   1830 			TargetAccountURI: "http://localhost:8080/users/the_mighty_zork",
   1831 			TargetAccountURL: "http://localhost:8080/@the_mighty_zork",
   1832 		},
   1833 		"admin_account_mention_zork": {
   1834 			ID:               "01FF26A6BGEKCZFWNEHXB2ZZ6M",
   1835 			StatusID:         "01FF25D5Q0DH7CHD57CTRS6WK0",
   1836 			CreatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1837 			UpdatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1838 			OriginAccountID:  "01F8MH17FWEB39HZJ76B6VXSKF",
   1839 			OriginAccountURI: "http://localhost:8080/users/admin",
   1840 			TargetAccountID:  "01F8MH1H7YV1Z7D2C8K2730QBF",
   1841 			NameString:       "@the_mighty_zork",
   1842 			TargetAccountURI: "http://localhost:8080/users/the_mighty_zork",
   1843 			TargetAccountURL: "http://localhost:8080/@the_mighty_zork",
   1844 		},
   1845 	}
   1846 }
   1847 
   1848 // NewTestFaves returns a map of gts model faves, keyed in the format [faving_account]_[target_status]
   1849 func NewTestFaves() map[string]*gtsmodel.StatusFave {
   1850 	return map[string]*gtsmodel.StatusFave{
   1851 		"local_account_1_admin_account_status_1": {
   1852 			ID:              "01F8MHD2QCZSZ6WQS2ATVPEYJ9",
   1853 			CreatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   1854 			AccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF", // local account 1
   1855 			TargetAccountID: "01F8MH17FWEB39HZJ76B6VXSKF", // admin account
   1856 			StatusID:        "01F8MH75CBF9JFX4ZAD54N0W0R", // admin account status 1
   1857 			URI:             "http://localhost:8080/users/the_mighty_zork/liked/01F8MHD2QCZSZ6WQS2ATVPEYJ9",
   1858 		},
   1859 		"local_account_1_admin_account_status_3": {
   1860 			ID:              "01GM435XERVPXXRK6NBAHK5HCZ",
   1861 			CreatedAt:       TimeMustParse("2022-12-12T20:17:56+02:00"),
   1862 			AccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF", // local account 1
   1863 			TargetAccountID: "01F8MH17FWEB39HZJ76B6VXSKF", // admin account
   1864 			StatusID:        "01F8MHAAY43M6RJ473VQFCVH37", // admin account status 1
   1865 			URI:             "http://localhost:8080/users/the_mighty_zork/liked/01GM435XERVPXXRK6NBAHK5HCZ",
   1866 		},
   1867 		"local_account_1_local_account_2_status_1": {
   1868 			ID:              "01GM43AKBMN4YNXQ1HZHVC1SGB",
   1869 			CreatedAt:       TimeMustParse("2022-12-12T20:19:49+02:00"),
   1870 			AccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF", // local account 1
   1871 			TargetAccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", // admin account
   1872 			StatusID:        "01F8MHBQCBTDKN6X5VHGMMN4MA", // admin account status 1
   1873 			URI:             "http://localhost:8080/users/the_mighty_zork/liked/01GM43AKBMN4YNXQ1HZHVC1SGB",
   1874 		},
   1875 		"local_account_1_local_account_2_status_4": {
   1876 			ID:              "01GM43CC47DRPNZZ7BD04BS1YZ",
   1877 			CreatedAt:       TimeMustParse("2022-12-12T20:20:47+02:00"),
   1878 			AccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF", // local account 1
   1879 			TargetAccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", // admin account
   1880 			StatusID:        "01F8MHCP5P2NWYQ416SBA0XSEV", // admin account status 1
   1881 			URI:             "http://localhost:8080/users/the_mighty_zork/liked/01GM43CC47DRPNZZ7BD04BS1YZ",
   1882 		},
   1883 		"admin_account_local_account_1_status_1": {
   1884 			ID:              "01F8Q0486ANTDWKG02A7DS1Q24",
   1885 			CreatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   1886 			AccountID:       "01F8MH17FWEB39HZJ76B6VXSKF", // admin account
   1887 			TargetAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", // local account 1
   1888 			StatusID:        "01F8MHAMCHF6Y650WCRSCP4WMY", // local account status 1
   1889 			URI:             "http://localhost:8080/users/admin/liked/01F8Q0486ANTDWKG02A7DS1Q24",
   1890 		},
   1891 	}
   1892 }
   1893 
   1894 // NewTestNotifications returns some notifications for use in testing.
   1895 func NewTestNotifications() map[string]*gtsmodel.Notification {
   1896 	return map[string]*gtsmodel.Notification{
   1897 		"local_account_1_like": {
   1898 			ID:               "01F8Q0ANPTWW10DAKTX7BRPBJP",
   1899 			NotificationType: gtsmodel.NotificationFave,
   1900 			CreatedAt:        TimeMustParse("2022-05-14T13:21:09+02:00"),
   1901 			TargetAccountID:  "01F8MH1H7YV1Z7D2C8K2730QBF",
   1902 			OriginAccountID:  "01F8MH17FWEB39HZJ76B6VXSKF",
   1903 			StatusID:         "01F8MHAMCHF6Y650WCRSCP4WMY",
   1904 			Read:             FalseBool(),
   1905 		},
   1906 		"local_account_2_like": {
   1907 			ID:               "01GTS6PRPXJYZBPFFQ56PP0XR8",
   1908 			NotificationType: gtsmodel.NotificationFave,
   1909 			CreatedAt:        TimeMustParse("2022-01-13T12:45:01+02:00"),
   1910 			TargetAccountID:  "01F8MH17FWEB39HZJ76B6VXSKF",
   1911 			OriginAccountID:  "01F8MH5NBDF2MV7CTC4Q5128HF",
   1912 			StatusID:         "01F8MH75CBF9JFX4ZAD54N0W0R",
   1913 			Read:             FalseBool(),
   1914 		},
   1915 	}
   1916 }
   1917 
   1918 // NewTestFollows returns some follows for use in testing.
   1919 func NewTestFollows() map[string]*gtsmodel.Follow {
   1920 	return map[string]*gtsmodel.Follow{
   1921 		"local_account_1_admin_account": {
   1922 			ID:              "01F8PY8RHWRQZV038T4E8T9YK8",
   1923 			CreatedAt:       TimeMustParse("2022-05-14T16:21:09+02:00"),
   1924 			UpdatedAt:       TimeMustParse("2022-05-14T16:21:09+02:00"),
   1925 			AccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF",
   1926 			TargetAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
   1927 			ShowReblogs:     TrueBool(),
   1928 			URI:             "http://localhost:8080/users/the_mighty_zork/follow/01F8PY8RHWRQZV038T4E8T9YK8",
   1929 			Notify:          FalseBool(),
   1930 		},
   1931 		"local_account_1_local_account_2": {
   1932 			ID:              "01F8PYDCE8XE23GRE5DPZJDZDP",
   1933 			CreatedAt:       TimeMustParse("2022-05-14T15:21:09+02:00"),
   1934 			UpdatedAt:       TimeMustParse("2022-05-14T15:21:09+02:00"),
   1935 			AccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF",
   1936 			TargetAccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
   1937 			ShowReblogs:     TrueBool(),
   1938 			URI:             "http://localhost:8080/users/the_mighty_zork/follow/01F8PYDCE8XE23GRE5DPZJDZDP",
   1939 			Notify:          FalseBool(),
   1940 		},
   1941 		"local_account_2_local_account_1": {
   1942 			ID:              "01G1TK1RS4K3E0MSFTXBFWAH9Q",
   1943 			CreatedAt:       TimeMustParse("2022-05-14T14:21:09+02:00"),
   1944 			UpdatedAt:       TimeMustParse("2022-05-14T14:21:09+02:00"),
   1945 			AccountID:       "01F8MH5NBDF2MV7CTC4Q5128HF",
   1946 			TargetAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
   1947 			ShowReblogs:     TrueBool(),
   1948 			URI:             "http://localhost:8080/users/1happyturtle/follow/01F8PYDCE8XE23GRE5DPZJDZDP",
   1949 			Notify:          FalseBool(),
   1950 		},
   1951 		"admin_account_local_account_1": {
   1952 			ID:              "01G1TK3PQKFW1BQZ9WVYRTFECK",
   1953 			CreatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   1954 			UpdatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   1955 			AccountID:       "01F8MH17FWEB39HZJ76B6VXSKF",
   1956 			TargetAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
   1957 			ShowReblogs:     TrueBool(),
   1958 			URI:             "http://localhost:8080/users/admin/follow/01G1TK3PQKFW1BQZ9WVYRTFECK",
   1959 			Notify:          FalseBool(),
   1960 		},
   1961 	}
   1962 }
   1963 
   1964 func NewTestLists() map[string]*gtsmodel.List {
   1965 	return map[string]*gtsmodel.List{
   1966 		"local_account_1_list_1": {
   1967 			ID:            "01H0G8E4Q2J3FE3JDWJVWEDCD1",
   1968 			CreatedAt:     TimeMustParse("2022-05-14T13:21:09+02:00"),
   1969 			UpdatedAt:     TimeMustParse("2022-05-14T13:21:09+02:00"),
   1970 			Title:         "Cool Ass Posters From This Instance",
   1971 			AccountID:     "01F8MH1H7YV1Z7D2C8K2730QBF",
   1972 			RepliesPolicy: gtsmodel.RepliesPolicyFollowed,
   1973 		},
   1974 	}
   1975 }
   1976 
   1977 func NewTestListEntries() map[string]*gtsmodel.ListEntry {
   1978 	return map[string]*gtsmodel.ListEntry{
   1979 		"local_account_1_list_1_entry_1": {
   1980 			ID:        "01H0G89MWVQE0M58VD2HQYMQWH",
   1981 			CreatedAt: TimeMustParse("2022-05-14T13:21:09+02:00"),
   1982 			UpdatedAt: TimeMustParse("2022-05-14T13:21:09+02:00"),
   1983 			ListID:    "01H0G8E4Q2J3FE3JDWJVWEDCD1",
   1984 			FollowID:  "01F8PYDCE8XE23GRE5DPZJDZDP",
   1985 		},
   1986 		"local_account_1_list_1_entry_2": {
   1987 			ID:        "01H0G8FFM1AGQDRNGBGGX8CYJQ",
   1988 			CreatedAt: TimeMustParse("2022-05-14T13:21:09+02:00"),
   1989 			UpdatedAt: TimeMustParse("2022-05-14T13:21:09+02:00"),
   1990 			ListID:    "01H0G8E4Q2J3FE3JDWJVWEDCD1",
   1991 			FollowID:  "01F8PY8RHWRQZV038T4E8T9YK8",
   1992 		},
   1993 	}
   1994 }
   1995 
   1996 func NewTestBlocks() map[string]*gtsmodel.Block {
   1997 	return map[string]*gtsmodel.Block{
   1998 		"local_account_2_block_remote_account_1": {
   1999 			ID:              "01FEXXET6XXMF7G2V3ASZP3YQW",
   2000 			CreatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   2001 			UpdatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   2002 			URI:             "http://localhost:8080/users/1happyturtle/blocks/01FEXXET6XXMF7G2V3ASZP3YQW",
   2003 			AccountID:       "01F8MH5NBDF2MV7CTC4Q5128HF",
   2004 			TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
   2005 		},
   2006 	}
   2007 }
   2008 
   2009 func NewTestReports() map[string]*gtsmodel.Report {
   2010 	return map[string]*gtsmodel.Report{
   2011 		"local_account_2_report_remote_account_1": {
   2012 			ID:              "01GP3AWY4CRDVRNZKW0TEAMB5R",
   2013 			CreatedAt:       TimeMustParse("2022-05-14T12:20:03+02:00"),
   2014 			UpdatedAt:       TimeMustParse("2022-05-14T12:20:03+02:00"),
   2015 			URI:             "http://localhost:8080/reports/01GP3AWY4CRDVRNZKW0TEAMB5R",
   2016 			AccountID:       "01F8MH5NBDF2MV7CTC4Q5128HF",
   2017 			TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
   2018 			Comment:         "dark souls sucks, please yeet this nerd",
   2019 			StatusIDs:       []string{"01FVW7JHQFSFK166WWKR8CBA6M"},
   2020 			Forwarded:       TrueBool(),
   2021 		},
   2022 		"remote_account_1_report_local_account_2": {
   2023 			ID:                     "01GP3DFY9XQ1TJMZT5BGAZPXX7",
   2024 			CreatedAt:              TimeMustParse("2022-05-15T16:20:12+02:00"),
   2025 			UpdatedAt:              TimeMustParse("2022-05-15T16:20:12+02:00"),
   2026 			URI:                    "http://fossbros-anonymous.io/87fb1478-ac46-406a-8463-96ce05645219",
   2027 			AccountID:              "01F8MH5ZK5VRH73AKHQM6Y9VNX",
   2028 			TargetAccountID:        "01F8MH5NBDF2MV7CTC4Q5128HF",
   2029 			Comment:                "this is a turtle, not a person, therefore should not be a poster",
   2030 			StatusIDs:              []string{},
   2031 			Forwarded:              TrueBool(),
   2032 			ActionTaken:            "user was warned not to be a turtle anymore",
   2033 			ActionTakenAt:          TimeMustParse("2022-05-15T17:01:56+02:00"),
   2034 			ActionTakenByAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
   2035 		},
   2036 	}
   2037 }
   2038 
   2039 // ActivityWithSignature wraps a pub.Activity along with its signature headers, for testing.
   2040 type ActivityWithSignature struct {
   2041 	Activity        pub.Activity
   2042 	SignatureHeader string
   2043 	DigestHeader    string
   2044 	DateHeader      string
   2045 }
   2046 
   2047 // NewTestActivities returns a bunch of pub.Activity types for use in testing the federation protocols.
   2048 // A struct of accounts needs to be passed in because the activities will also be bundled along with
   2049 // their requesting signatures.
   2050 func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
   2051 	dmForZork := NewAPNote(
   2052 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6"),
   2053 		URLMustParse("http://fossbros-anonymous.io/@foss_satan/5424b153-4553-4f30-9358-7b92f7cd42f6"),
   2054 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2055 		"hey zork here's a new private note for you",
   2056 		"new note for zork",
   2057 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2058 		[]*url.URL{URLMustParse("http://localhost:8080/users/the_mighty_zork")},
   2059 		nil,
   2060 		true,
   2061 		[]vocab.ActivityStreamsMention{},
   2062 		nil,
   2063 	)
   2064 	createDmForZork := WrapAPNoteInCreate(
   2065 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6/activity"),
   2066 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2067 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2068 		dmForZork)
   2069 	createDmForZorkSig, createDmForZorkDigest, creatDmForZorkDate := GetSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
   2070 
   2071 	replyToTurtle := NewAPNote(
   2072 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/2f1195a6-5cb0-4475-adf5-92ab9a0147fe"),
   2073 		URLMustParse("http://fossbros-anonymous.io/@foss_satan/2f1195a6-5cb0-4475-adf5-92ab9a0147fe"),
   2074 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2075 		"@1happyturtle@localhost:8080 u suck lol",
   2076 		"",
   2077 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2078 		[]*url.URL{URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers")},
   2079 		[]*url.URL{URLMustParse("http://localhost:8080/users/1happyturtle")},
   2080 		false,
   2081 		[]vocab.ActivityStreamsMention{newAPMention(
   2082 			URLMustParse("http://localhost:8080/users/1happyturtle"),
   2083 			"@1happyturtle@localhost:8080",
   2084 		)},
   2085 		nil,
   2086 	)
   2087 	createReplyToTurtle := WrapAPNoteInCreate(
   2088 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/2f1195a6-5cb0-4475-adf5-92ab9a0147fe"),
   2089 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2090 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2091 		replyToTurtle)
   2092 	createReplyToTurtleForZorkSig, createReplyToTurtleForZorkDigest, createReplyToTurtleForZorkDate := GetSignatureForActivity(createReplyToTurtle, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
   2093 	createReplyToTurtleForTurtleSig, createReplyToTurtleForTurtleDigest, createReplyToTurtleForTurtleDate := GetSignatureForActivity(createReplyToTurtle, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_2"].InboxURI))
   2094 
   2095 	forwardedMessage := NewAPNote(
   2096 		URLMustParse("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
   2097 		URLMustParse("http://example.org/@Some_User/afaba698-5740-4e32-a702-af61aa543bc1"),
   2098 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2099 		"this is a public status, please forward it!",
   2100 		"",
   2101 		URLMustParse("http://example.org/users/Some_User"),
   2102 		[]*url.URL{URLMustParse(pub.PublicActivityPubIRI)},
   2103 		nil,
   2104 		false,
   2105 		[]vocab.ActivityStreamsMention{},
   2106 		[]vocab.ActivityStreamsImage{
   2107 			newAPImage(
   2108 				URLMustParse("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1/attachment1.jpg"),
   2109 				"image/jpeg",
   2110 				"trent reznor looking handsome as balls",
   2111 				"LEDara58O=t5EMSOENEN9]}?aK%0"),
   2112 		},
   2113 	)
   2114 	createForwardedMessage := WrapAPNoteInCreate(
   2115 		URLMustParse("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1/activity"),
   2116 		URLMustParse("http://example.org/users/Some_User"),
   2117 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2118 		forwardedMessage)
   2119 	createForwardedMessageSig, createForwardedMessageDigest, createForwardedMessageDate := GetSignatureForActivity(createForwardedMessage, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
   2120 
   2121 	announceForwarded1Zork := newAPAnnounce(
   2122 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/first_announce"),
   2123 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2124 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2125 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"),
   2126 		forwardedMessage,
   2127 	)
   2128 	announceForwarded1ZorkSig, announceForwarded1ZorkDigest, announceForwarded1ZorkDate := GetSignatureForActivity(announceForwarded1Zork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
   2129 
   2130 	announceForwarded1Turtle := newAPAnnounce(
   2131 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/first_announce"),
   2132 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2133 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2134 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"),
   2135 		forwardedMessage,
   2136 	)
   2137 	announceForwarded1TurtleSig, announceForwarded1TurtleDigest, announceForwarded1TurtleDate := GetSignatureForActivity(announceForwarded1Turtle, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_2"].InboxURI))
   2138 
   2139 	announceForwarded2Zork := newAPAnnounce(
   2140 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/second_announce"),
   2141 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2142 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2143 		URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"),
   2144 		forwardedMessage,
   2145 	)
   2146 	announceForwarded2ZorkSig, announceForwarded2ZorkDigest, announceForwarded2ZorkDate := GetSignatureForActivity(announceForwarded2Zork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
   2147 
   2148 	deleteForRemoteAccount3 := newAPDelete(
   2149 		URLMustParse("https://somewhere.mysterious/users/rest_in_piss"),
   2150 		URLMustParse("https://somewhere.mysterious/users/rest_in_piss"),
   2151 		TimeMustParse("2022-07-13T12:13:12+02:00"),
   2152 		URLMustParse(accounts["local_account_1"].URI),
   2153 	)
   2154 	// it doesn't really matter what key we use to sign this, since we're not going to be able to verify if anyway
   2155 	keyToSignDelete := accounts["remote_account_1"].PrivateKey
   2156 	deleteForRemoteAccount3Sig, deleteForRemoteAccount3Digest, deleteForRemoteAccount3Date := GetSignatureForActivity(deleteForRemoteAccount3, "https://somewhere.mysterious/users/rest_in_piss#main-key", keyToSignDelete, URLMustParse(accounts["local_account_1"].InboxURI))
   2157 
   2158 	return map[string]ActivityWithSignature{
   2159 		"dm_for_zork": {
   2160 			Activity:        createDmForZork,
   2161 			SignatureHeader: createDmForZorkSig,
   2162 			DigestHeader:    createDmForZorkDigest,
   2163 			DateHeader:      creatDmForZorkDate,
   2164 		},
   2165 		"reply_to_turtle_for_zork": {
   2166 			Activity:        createReplyToTurtle,
   2167 			SignatureHeader: createReplyToTurtleForZorkSig,
   2168 			DigestHeader:    createReplyToTurtleForZorkDigest,
   2169 			DateHeader:      createReplyToTurtleForZorkDate,
   2170 		},
   2171 		"reply_to_turtle_for_turtle": {
   2172 			Activity:        createReplyToTurtle,
   2173 			SignatureHeader: createReplyToTurtleForTurtleSig,
   2174 			DigestHeader:    createReplyToTurtleForTurtleDigest,
   2175 			DateHeader:      createReplyToTurtleForTurtleDate,
   2176 		},
   2177 		"forwarded_message": {
   2178 			Activity:        createForwardedMessage,
   2179 			SignatureHeader: createForwardedMessageSig,
   2180 			DigestHeader:    createForwardedMessageDigest,
   2181 			DateHeader:      createForwardedMessageDate,
   2182 		},
   2183 		"announce_forwarded_1_zork": {
   2184 			Activity:        announceForwarded1Zork,
   2185 			SignatureHeader: announceForwarded1ZorkSig,
   2186 			DigestHeader:    announceForwarded1ZorkDigest,
   2187 			DateHeader:      announceForwarded1ZorkDate,
   2188 		},
   2189 		"announce_forwarded_1_turtle": {
   2190 			Activity:        announceForwarded1Turtle,
   2191 			SignatureHeader: announceForwarded1TurtleSig,
   2192 			DigestHeader:    announceForwarded1TurtleDigest,
   2193 			DateHeader:      announceForwarded1TurtleDate,
   2194 		},
   2195 		"announce_forwarded_2_zork": {
   2196 			Activity:        announceForwarded2Zork,
   2197 			SignatureHeader: announceForwarded2ZorkSig,
   2198 			DigestHeader:    announceForwarded2ZorkDigest,
   2199 			DateHeader:      announceForwarded2ZorkDate,
   2200 		},
   2201 		"delete_https://somewhere.mysterious/users/rest_in_piss#main-key": {
   2202 			Activity:        deleteForRemoteAccount3,
   2203 			SignatureHeader: deleteForRemoteAccount3Sig,
   2204 			DigestHeader:    deleteForRemoteAccount3Digest,
   2205 			DateHeader:      deleteForRemoteAccount3Date,
   2206 		},
   2207 	}
   2208 }
   2209 
   2210 // NewTestFediPeople returns a bunch of activity pub Person representations for testing converters and so on.
   2211 func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson {
   2212 	newPerson1Priv, err := rsa.GenerateKey(rand.Reader, 2048)
   2213 	if err != nil {
   2214 		panic(err)
   2215 	}
   2216 	newPerson1Pub := &newPerson1Priv.PublicKey
   2217 
   2218 	turnipLover6969Priv, err := rsa.GenerateKey(rand.Reader, 2048)
   2219 	if err != nil {
   2220 		panic(err)
   2221 	}
   2222 	turnipLover6969Pub := &turnipLover6969Priv.PublicKey
   2223 
   2224 	someUserPriv, err := rsa.GenerateKey(rand.Reader, 2048)
   2225 	if err != nil {
   2226 		panic(err)
   2227 	}
   2228 	someUserPub := &someUserPriv.PublicKey
   2229 
   2230 	return map[string]vocab.ActivityStreamsPerson{
   2231 		"https://unknown-instance.com/users/brand_new_person": newAPPerson(
   2232 			URLMustParse("https://unknown-instance.com/users/brand_new_person"),
   2233 			URLMustParse("https://unknown-instance.com/users/brand_new_person/following"),
   2234 			URLMustParse("https://unknown-instance.com/users/brand_new_person/followers"),
   2235 			URLMustParse("https://unknown-instance.com/users/brand_new_person/inbox"),
   2236 			nil,
   2237 			URLMustParse("https://unknown-instance.com/users/brand_new_person/outbox"),
   2238 			URLMustParse("https://unknown-instance.com/users/brand_new_person/collections/featured"),
   2239 			"brand_new_person",
   2240 			"Geoff Brando New Personson",
   2241 			"hey I'm a new person, your instance hasn't seen me yet uwu",
   2242 			URLMustParse("https://unknown-instance.com/@brand_new_person"),
   2243 			true,
   2244 			URLMustParse("https://unknown-instance.com/users/brand_new_person#main-key"),
   2245 			newPerson1Pub,
   2246 			nil,
   2247 			"image/jpeg",
   2248 			nil,
   2249 			"image/png",
   2250 			false,
   2251 		),
   2252 		"https://turnip.farm/users/turniplover6969": newAPPerson(
   2253 			URLMustParse("https://turnip.farm/users/turniplover6969"),
   2254 			URLMustParse("https://turnip.farm/users/turniplover6969/following"),
   2255 			URLMustParse("https://turnip.farm/users/turniplover6969/followers"),
   2256 			URLMustParse("https://turnip.farm/users/turniplover6969/inbox"),
   2257 			URLMustParse("https://turnip.farm/sharedInbox"),
   2258 			URLMustParse("https://turnip.farm/users/turniplover6969/outbox"),
   2259 			URLMustParse("https://turnip.farm/users/turniplover6969/collections/featured"),
   2260 			"turniplover6969",
   2261 			"Turnip Lover 6969",
   2262 			"I just think they're neat",
   2263 			URLMustParse("https://turnip.farm/@turniplover6969"),
   2264 			true,
   2265 			URLMustParse("https://turnip.farm/users/turniplover6969#main-key"),
   2266 			turnipLover6969Pub,
   2267 			nil,
   2268 			"image/jpeg",
   2269 			nil,
   2270 			"image/png",
   2271 			false,
   2272 		),
   2273 		"https://example.org/users/Some_User": newAPPerson(
   2274 			URLMustParse("https://example.org/users/Some_User"),
   2275 			URLMustParse("https://example.org/users/Some_User/following"),
   2276 			URLMustParse("https://example.org/users/Some_User/followers"),
   2277 			URLMustParse("https://example.org/users/Some_User/inbox"),
   2278 			URLMustParse("https://example.org/sharedInbox"),
   2279 			URLMustParse("https://example.org/users/Some_User/outbox"),
   2280 			URLMustParse("https://example.org/users/Some_User/collections/featured"),
   2281 			"Some_User",
   2282 			"just some user, don't mind me",
   2283 			"Peepee poo poo",
   2284 			URLMustParse("https://example.org/@Some_User"),
   2285 			true,
   2286 			URLMustParse("https://example.org/users/Some_User#main-key"),
   2287 			someUserPub,
   2288 			nil,
   2289 			"image/jpeg",
   2290 			nil,
   2291 			"image/png",
   2292 			false,
   2293 		),
   2294 	}
   2295 }
   2296 
   2297 func NewTestFediGroups() map[string]vocab.ActivityStreamsGroup {
   2298 	newGroup1Priv, err := rsa.GenerateKey(rand.Reader, 2048)
   2299 	if err != nil {
   2300 		panic(err)
   2301 	}
   2302 	newGroup1Pub := &newGroup1Priv.PublicKey
   2303 
   2304 	return map[string]vocab.ActivityStreamsGroup{
   2305 		"https://unknown-instance.com/groups/some_group": newAPGroup(
   2306 			URLMustParse("https://unknown-instance.com/groups/some_group"),
   2307 			URLMustParse("https://unknown-instance.com/groups/some_group/following"),
   2308 			URLMustParse("https://unknown-instance.com/groups/some_group/followers"),
   2309 			URLMustParse("https://unknown-instance.com/groups/some_group/inbox"),
   2310 			URLMustParse("https://unknown-instance.com/groups/some_group/outbox"),
   2311 			URLMustParse("https://unknown-instance.com/groups/some_group/collections/featured"),
   2312 			"some_group",
   2313 			"This is a group about... something?",
   2314 			"",
   2315 			URLMustParse("https://unknown-instance.com/@some_group"),
   2316 			true,
   2317 			URLMustParse("https://unknown-instance.com/groups/some_group#main-key"),
   2318 			newGroup1Pub,
   2319 			nil,
   2320 			"image/jpeg",
   2321 			nil,
   2322 			"image/png",
   2323 			false,
   2324 		),
   2325 	}
   2326 }
   2327 
   2328 func NewTestFediServices() map[string]vocab.ActivityStreamsService {
   2329 	newService1Priv, err := rsa.GenerateKey(rand.Reader, 2048)
   2330 	if err != nil {
   2331 		panic(err)
   2332 	}
   2333 	newService1Pub := &newService1Priv.PublicKey
   2334 
   2335 	return map[string]vocab.ActivityStreamsService{
   2336 		"https://owncast.example.org/federation/user/rgh": newAPService(
   2337 			URLMustParse("https://owncast.example.org/federation/user/rgh"),
   2338 			nil,
   2339 			URLMustParse("https://owncast.example.org/federation/user/rgh/followers"),
   2340 			URLMustParse("https://owncast.example.org/federation/user/rgh/inbox"),
   2341 			URLMustParse("https://owncast.example.org/federation/user/rgh/outbox"),
   2342 			nil,
   2343 			"rgh",
   2344 			"linux audio stuff ",
   2345 			"",
   2346 			URLMustParse("https://owncast.example.org/federation/user/rgh"),
   2347 			true,
   2348 			URLMustParse("https://owncast.example.org/federation/user/rgh#main-key"),
   2349 			newService1Pub,
   2350 			nil,
   2351 			"image/jpeg",
   2352 			nil,
   2353 			"image/png",
   2354 			false,
   2355 		),
   2356 	}
   2357 }
   2358 
   2359 func NewTestFediEmojis() map[string]vocab.TootEmoji {
   2360 	return map[string]vocab.TootEmoji{
   2361 		"http://fossbros-anonymous.io/emoji/01GD5HCC2YECT012TK8PAGX4D1": newAPEmoji(
   2362 			URLMustParse("http://fossbros-anonymous.io/emoji/01GD5HCC2YECT012TK8PAGX4D1"),
   2363 			"kip_van_den_bos",
   2364 			TimeMustParse("2022-09-13T12:13:12+02:00"),
   2365 			newAPImage(
   2366 				URLMustParse("http://fossbros-anonymous.io/emoji/kip.gif"),
   2367 				"image/gif",
   2368 				"",
   2369 				"",
   2370 			),
   2371 		),
   2372 	}
   2373 }
   2374 
   2375 // RemoteAttachmentFile mimics a remote (federated) attachment
   2376 type RemoteAttachmentFile struct {
   2377 	Data        []byte
   2378 	ContentType string
   2379 }
   2380 
   2381 func NewTestFediAttachments(relativePath string) map[string]RemoteAttachmentFile {
   2382 	beeBytes, err := os.ReadFile(fmt.Sprintf("%s/beeplushie.jpg", relativePath))
   2383 	if err != nil {
   2384 		panic(err)
   2385 	}
   2386 
   2387 	thoughtsOfDogBytes, err := os.ReadFile(fmt.Sprintf("%s/thoughtsofdog-original.jpg", relativePath))
   2388 	if err != nil {
   2389 		panic(err)
   2390 	}
   2391 
   2392 	massiveFuckingTurnipBytes, err := os.ReadFile(fmt.Sprintf("%s/giant-turnip-world-record.jpg", relativePath))
   2393 	if err != nil {
   2394 		panic(err)
   2395 	}
   2396 
   2397 	peglinBytes, err := os.ReadFile(fmt.Sprintf("%s/peglin.gif", relativePath))
   2398 	if err != nil {
   2399 		panic(err)
   2400 	}
   2401 
   2402 	kipBytes, err := os.ReadFile(fmt.Sprintf("%s/kip-original.gif", relativePath))
   2403 	if err != nil {
   2404 		panic(err)
   2405 	}
   2406 
   2407 	yellBytes, err := os.ReadFile(fmt.Sprintf("%s/yell-original.png", relativePath))
   2408 	if err != nil {
   2409 		panic(err)
   2410 	}
   2411 
   2412 	return map[string]RemoteAttachmentFile{
   2413 		"https://s3-us-west-2.amazonaws.com/plushcity/media_attachments/files/106/867/380/219/163/828/original/88e8758c5f011439.jpg": {
   2414 			Data:        beeBytes,
   2415 			ContentType: "image/jpeg",
   2416 		},
   2417 		"http://fossbros-anonymous.io/attachments/original/13bbc3f8-2b5e-46ea-9531-40b4974d9912.jpg": {
   2418 			Data:        thoughtsOfDogBytes,
   2419 			ContentType: "image/jpeg",
   2420 		},
   2421 		"https://turnip.farm/attachments/f17843c7-015e-4251-9b5a-91389c49ee57.jpg": {
   2422 			Data:        massiveFuckingTurnipBytes,
   2423 			ContentType: "image/jpeg",
   2424 		},
   2425 		"http://example.org/media/emojis/1781772.gif": {
   2426 			Data:        peglinBytes,
   2427 			ContentType: "image/gif",
   2428 		},
   2429 		"http://fossbros-anonymous.io/emoji/kip.gif": {
   2430 			Data:        kipBytes,
   2431 			ContentType: "image/gif",
   2432 		},
   2433 		"http://fossbros-anonymous.io/emoji/yell.gif": {
   2434 			Data:        yellBytes,
   2435 			ContentType: "image/png",
   2436 		},
   2437 	}
   2438 }
   2439 
   2440 func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
   2441 	return map[string]vocab.ActivityStreamsNote{
   2442 		"http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1": NewAPNote(
   2443 			URLMustParse("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
   2444 			URLMustParse("http://example.org/@Some_User/afaba698-5740-4e32-a702-af61aa543bc1"),
   2445 			TimeMustParse("2022-07-13T12:13:12+02:00"),
   2446 			"this is a public status, please forward it!",
   2447 			"",
   2448 			URLMustParse("http://example.org/users/Some_User"),
   2449 			[]*url.URL{URLMustParse(pub.PublicActivityPubIRI)},
   2450 			nil,
   2451 			false,
   2452 			[]vocab.ActivityStreamsMention{},
   2453 			[]vocab.ActivityStreamsImage{
   2454 				newAPImage(
   2455 					URLMustParse("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1/attachment1.jpg"),
   2456 					"image/jpeg",
   2457 					"trent reznor looking handsome as balls",
   2458 					"LEDara58O=t5EMSOENEN9]}?aK%0"),
   2459 			},
   2460 		),
   2461 		"https://unknown-instance.com/users/brand_new_person/statuses/01FE4NTHKWW7THT67EF10EB839": NewAPNote(
   2462 			URLMustParse("https://unknown-instance.com/users/brand_new_person/statuses/01FE4NTHKWW7THT67EF10EB839"),
   2463 			URLMustParse("https://unknown-instance.com/users/@brand_new_person/01FE4NTHKWW7THT67EF10EB839"),
   2464 			TimeMustParse("2022-07-13T12:13:12+02:00"),
   2465 			"Hello world!",
   2466 			"",
   2467 			URLMustParse("https://unknown-instance.com/users/brand_new_person"),
   2468 			[]*url.URL{
   2469 				URLMustParse(pub.PublicActivityPubIRI),
   2470 			},
   2471 			[]*url.URL{},
   2472 			false,
   2473 			nil,
   2474 			nil,
   2475 		),
   2476 		"https://unknown-instance.com/users/brand_new_person/statuses/01FE5Y30E3W4P7TRE0R98KAYQV": NewAPNote(
   2477 			URLMustParse("https://unknown-instance.com/users/brand_new_person/statuses/01FE5Y30E3W4P7TRE0R98KAYQV"),
   2478 			URLMustParse("https://unknown-instance.com/users/@brand_new_person/01FE5Y30E3W4P7TRE0R98KAYQV"),
   2479 			TimeMustParse("2022-07-13T12:13:12+02:00"),
   2480 			"Hey @the_mighty_zork@localhost:8080 how's it going?",
   2481 			"",
   2482 			URLMustParse("https://unknown-instance.com/users/brand_new_person"),
   2483 			[]*url.URL{
   2484 				URLMustParse(pub.PublicActivityPubIRI),
   2485 			},
   2486 			[]*url.URL{},
   2487 			false,
   2488 			[]vocab.ActivityStreamsMention{
   2489 				newAPMention(
   2490 					URLMustParse("http://localhost:8080/users/the_mighty_zork"),
   2491 					"@the_mighty_zork@localhost:8080",
   2492 				),
   2493 			},
   2494 			nil,
   2495 		),
   2496 		"https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042": NewAPNote(
   2497 			URLMustParse("https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042"),
   2498 			URLMustParse("https://turnip.farm/@turniplover6969/70c53e54-3146-42d5-a630-83c8b6c7c042"),
   2499 			TimeMustParse("2022-07-13T12:13:12+02:00"),
   2500 			"",
   2501 			"",
   2502 			URLMustParse("https://turnip.farm/users/turniplover6969"),
   2503 			[]*url.URL{
   2504 				URLMustParse(pub.PublicActivityPubIRI),
   2505 			},
   2506 			[]*url.URL{},
   2507 			false,
   2508 			nil,
   2509 			[]vocab.ActivityStreamsImage{
   2510 				newAPImage(
   2511 					URLMustParse("https://turnip.farm/attachments/f17843c7-015e-4251-9b5a-91389c49ee57.jpg"),
   2512 					"image/jpeg",
   2513 					"",
   2514 					"",
   2515 				),
   2516 			},
   2517 		),
   2518 		"http://fossbros-anonymous.io/users/foss_satan/statuses/106221634728637552": NewAPNote(
   2519 			URLMustParse("http://fossbros-anonymous.io/users/foss_satan/statuses/106221634728637552"),
   2520 			URLMustParse("http://fossbros-anonymous.io/@foss_satan/106221634728637552"),
   2521 			TimeMustParse("2022-07-13T12:13:12+02:00"),
   2522 			`<p><span class="h-card"><a href="http://localhost:8080/@the_mighty_zork" class="u-url mention">@<span>the_mighty_zork</span></a></span> nice there it is:</p><p><a href="http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/activity" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://</span><span class="ellipsis">social.pixie.town/users/f0x/st</span><span class="invisible">atuses/106221628567855262/activity</span></a></p>`,
   2523 			"",
   2524 			URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
   2525 			[]*url.URL{
   2526 				URLMustParse(pub.PublicActivityPubIRI),
   2527 			},
   2528 			[]*url.URL{},
   2529 			false,
   2530 			[]vocab.ActivityStreamsMention{
   2531 				newAPMention(
   2532 					URLMustParse("http://localhost:8080/users/the_mighty_zork"),
   2533 					"@the_mighty_zork@localhost:8080",
   2534 				),
   2535 			},
   2536 			nil,
   2537 		),
   2538 	}
   2539 }
   2540 
   2541 // NewTestBookmarks returns a map of gts model bookmarks, keyed in the format [bookmarking_account]_[target_status]
   2542 func NewTestBookmarks() map[string]*gtsmodel.StatusBookmark {
   2543 	return map[string]*gtsmodel.StatusBookmark{
   2544 		"local_account_1_admin_account_status_1": {
   2545 			ID:              "01F8MHD2QCZSZ6WQS2ATVPEYJ9",
   2546 			CreatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   2547 			AccountID:       "01F8MH1H7YV1Z7D2C8K2730QBF", // local account 1
   2548 			TargetAccountID: "01F8MH17FWEB39HZJ76B6VXSKF", // admin account
   2549 			StatusID:        "01F8MH75CBF9JFX4ZAD54N0W0R", // admin account status 1
   2550 		},
   2551 		"admin_account_local_account_1_status_1": {
   2552 			ID:              "01F8Q0486ANTDWKG02A7DS1Q24",
   2553 			CreatedAt:       TimeMustParse("2022-05-14T13:21:09+02:00"),
   2554 			AccountID:       "01F8MH17FWEB39HZJ76B6VXSKF", // admin account
   2555 			TargetAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", // local account 1
   2556 			StatusID:        "01F8MHAMCHF6Y650WCRSCP4WMY", // local account status 1
   2557 		},
   2558 	}
   2559 }
   2560 
   2561 // NewTestDereferenceRequests returns a map of incoming dereference requests, with their signatures.
   2562 func NewTestDereferenceRequests(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
   2563 	var sig, digest, date string
   2564 	var target *url.URL
   2565 	statuses := NewTestStatuses()
   2566 	emojis := NewTestEmojis()
   2567 
   2568 	target = URLMustParse(accounts["local_account_1"].URI)
   2569 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2570 	fossSatanDereferenceZork := ActivityWithSignature{
   2571 		SignatureHeader: sig,
   2572 		DigestHeader:    digest,
   2573 		DateHeader:      date,
   2574 	}
   2575 
   2576 	target = URLMustParse(accounts["local_account_1"].PublicKeyURI)
   2577 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2578 	fossSatanDereferenceZorkPublicKey := ActivityWithSignature{
   2579 		SignatureHeader: sig,
   2580 		DigestHeader:    digest,
   2581 		DateHeader:      date,
   2582 	}
   2583 
   2584 	target = URLMustParse(statuses["local_account_1_status_1"].URI)
   2585 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2586 	fossSatanDereferenceLocalAccount1Status1 := ActivityWithSignature{
   2587 		SignatureHeader: sig,
   2588 		DigestHeader:    digest,
   2589 		DateHeader:      date,
   2590 	}
   2591 
   2592 	target = URLMustParse(strings.ToLower(statuses["local_account_1_status_1"].URI))
   2593 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2594 	fossSatanDereferenceLocalAccount1Status1Lowercase := ActivityWithSignature{
   2595 		SignatureHeader: sig,
   2596 		DigestHeader:    digest,
   2597 		DateHeader:      date,
   2598 	}
   2599 
   2600 	target = URLMustParse(statuses["local_account_1_status_1"].URI + "/replies")
   2601 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2602 	fossSatanDereferenceLocalAccount1Status1Replies := ActivityWithSignature{
   2603 		SignatureHeader: sig,
   2604 		DigestHeader:    digest,
   2605 		DateHeader:      date,
   2606 	}
   2607 
   2608 	target = URLMustParse(statuses["local_account_1_status_1"].URI + "/replies?only_other_accounts=false&page=true")
   2609 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2610 	fossSatanDereferenceLocalAccount1Status1RepliesNext := ActivityWithSignature{
   2611 		SignatureHeader: sig,
   2612 		DigestHeader:    digest,
   2613 		DateHeader:      date,
   2614 	}
   2615 
   2616 	target = URLMustParse(statuses["local_account_1_status_1"].URI + "/replies?only_other_accounts=false&page=true&min_id=01FF25D5Q0DH7CHD57CTRS6WK0")
   2617 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2618 	fossSatanDereferenceLocalAccount1Status1RepliesLast := ActivityWithSignature{
   2619 		SignatureHeader: sig,
   2620 		DigestHeader:    digest,
   2621 		DateHeader:      date,
   2622 	}
   2623 
   2624 	target = URLMustParse(accounts["local_account_1"].OutboxURI)
   2625 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2626 	fossSatanDereferenceZorkOutbox := ActivityWithSignature{
   2627 		SignatureHeader: sig,
   2628 		DigestHeader:    digest,
   2629 		DateHeader:      date,
   2630 	}
   2631 
   2632 	target = URLMustParse(accounts["local_account_1"].OutboxURI + "?page=true")
   2633 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2634 	fossSatanDereferenceZorkOutboxFirst := ActivityWithSignature{
   2635 		SignatureHeader: sig,
   2636 		DigestHeader:    digest,
   2637 		DateHeader:      date,
   2638 	}
   2639 
   2640 	target = URLMustParse(accounts["local_account_1"].OutboxURI + "?page=true&max_id=01F8MHAMCHF6Y650WCRSCP4WMY")
   2641 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2642 	fossSatanDereferenceZorkOutboxNext := ActivityWithSignature{
   2643 		SignatureHeader: sig,
   2644 		DigestHeader:    digest,
   2645 		DateHeader:      date,
   2646 	}
   2647 
   2648 	target = URLMustParse(emojis["rainbow"].URI)
   2649 	sig, digest, date = GetSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, target)
   2650 	fossSatanDereferenceEmoji := ActivityWithSignature{
   2651 		SignatureHeader: sig,
   2652 		DigestHeader:    digest,
   2653 		DateHeader:      date,
   2654 	}
   2655 
   2656 	return map[string]ActivityWithSignature{
   2657 		"foss_satan_dereference_zork":                                  fossSatanDereferenceZork,
   2658 		"foss_satan_dereference_zork_public_key":                       fossSatanDereferenceZorkPublicKey,
   2659 		"foss_satan_dereference_local_account_1_status_1":              fossSatanDereferenceLocalAccount1Status1,
   2660 		"foss_satan_dereference_local_account_1_status_1_lowercase":    fossSatanDereferenceLocalAccount1Status1Lowercase,
   2661 		"foss_satan_dereference_local_account_1_status_1_replies":      fossSatanDereferenceLocalAccount1Status1Replies,
   2662 		"foss_satan_dereference_local_account_1_status_1_replies_next": fossSatanDereferenceLocalAccount1Status1RepliesNext,
   2663 		"foss_satan_dereference_local_account_1_status_1_replies_last": fossSatanDereferenceLocalAccount1Status1RepliesLast,
   2664 		"foss_satan_dereference_zork_outbox":                           fossSatanDereferenceZorkOutbox,
   2665 		"foss_satan_dereference_zork_outbox_first":                     fossSatanDereferenceZorkOutboxFirst,
   2666 		"foss_satan_dereference_zork_outbox_next":                      fossSatanDereferenceZorkOutboxNext,
   2667 		"foss_satan_dereference_emoji":                                 fossSatanDereferenceEmoji,
   2668 	}
   2669 }
   2670 
   2671 // 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.
   2672 func GetSignatureForActivity(activity pub.Activity, pubKeyID string, privkey *rsa.PrivateKey, destination *url.URL) (signatureHeader string, digestHeader string, dateHeader string) {
   2673 	// convert the activity into json bytes
   2674 	m, err := activity.Serialize()
   2675 	if err != nil {
   2676 		panic(err)
   2677 	}
   2678 	b, err := json.Marshal(m)
   2679 	if err != nil {
   2680 		panic(err)
   2681 	}
   2682 
   2683 	// Prepare HTTP request signer
   2684 	sig, err := transport.NewPOSTSigner(120)
   2685 	if err != nil {
   2686 		panic(err)
   2687 	}
   2688 
   2689 	// Prepare a mock request ready for signing
   2690 	r, err := http.NewRequest("POST", destination.String(), bytes.NewReader(b))
   2691 	if err != nil {
   2692 		panic(err)
   2693 	}
   2694 	r.Header.Set("Host", destination.Host)
   2695 	r.Header.Set("Date", time.Now().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
   2696 
   2697 	// Sign this new HTTP request
   2698 	if err := sig.SignRequest(privkey, pubKeyID, r, b); err != nil {
   2699 		panic(err)
   2700 	}
   2701 
   2702 	// Load signed data from request
   2703 	signatureHeader = r.Header.Get("Signature")
   2704 	digestHeader = r.Header.Get("Digest")
   2705 	dateHeader = r.Header.Get("Date")
   2706 
   2707 	// headers should now be populated
   2708 	return
   2709 }
   2710 
   2711 // GetSignatureForDereference prepares a mock HTTP request as if it were going to dereference destination signed for privkey and pubKeyID, signs the request and returns the header values.
   2712 func GetSignatureForDereference(pubKeyID string, privkey *rsa.PrivateKey, destination *url.URL) (signatureHeader string, digestHeader string, dateHeader string) {
   2713 	// Prepare HTTP request signer
   2714 	sig, err := transport.NewGETSigner(120)
   2715 	if err != nil {
   2716 		panic(err)
   2717 	}
   2718 
   2719 	// Prepare a mock request ready for signing
   2720 	r, err := http.NewRequest("GET", destination.String(), nil)
   2721 	if err != nil {
   2722 		panic(err)
   2723 	}
   2724 	r.Header.Set("Host", destination.Host)
   2725 	r.Header.Set("Date", time.Now().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
   2726 
   2727 	// Sign this new HTTP request
   2728 	if err := sig.SignRequest(privkey, pubKeyID, r, nil); err != nil {
   2729 		panic(err)
   2730 	}
   2731 
   2732 	// Load signed data from request
   2733 	signatureHeader = r.Header.Get("Signature")
   2734 	digestHeader = r.Header.Get("Digest")
   2735 	dateHeader = r.Header.Get("Date")
   2736 
   2737 	// headers should now be populated
   2738 	return
   2739 }
   2740 
   2741 func newAPPerson(
   2742 	profileIDURI *url.URL,
   2743 	followingURI *url.URL,
   2744 	followersURI *url.URL,
   2745 	inboxURI *url.URL,
   2746 	sharedInboxIRI *url.URL,
   2747 	outboxURI *url.URL,
   2748 	featuredURI *url.URL,
   2749 	username string,
   2750 	displayName string,
   2751 	note string,
   2752 	profileURL *url.URL,
   2753 	discoverable bool,
   2754 	publicKeyURI *url.URL,
   2755 	pkey *rsa.PublicKey,
   2756 	avatarURL *url.URL,
   2757 	avatarContentType string,
   2758 	headerURL *url.URL,
   2759 	headerContentType string,
   2760 	manuallyApprovesFollowers bool,
   2761 ) vocab.ActivityStreamsPerson {
   2762 	person := streams.NewActivityStreamsPerson()
   2763 
   2764 	// id should be the activitypub URI of this user
   2765 	// something like https://example.org/users/example_user
   2766 	idProp := streams.NewJSONLDIdProperty()
   2767 	idProp.SetIRI(profileIDURI)
   2768 	person.SetJSONLDId(idProp)
   2769 
   2770 	// following
   2771 	// The URI for retrieving a list of accounts this user is following
   2772 	followingProp := streams.NewActivityStreamsFollowingProperty()
   2773 	followingProp.SetIRI(followingURI)
   2774 	person.SetActivityStreamsFollowing(followingProp)
   2775 
   2776 	// followers
   2777 	// The URI for retrieving a list of this user's followers
   2778 	followersProp := streams.NewActivityStreamsFollowersProperty()
   2779 	followersProp.SetIRI(followersURI)
   2780 	person.SetActivityStreamsFollowers(followersProp)
   2781 
   2782 	// inbox
   2783 	// the activitypub inbox of this user for accepting messages
   2784 	inboxProp := streams.NewActivityStreamsInboxProperty()
   2785 	inboxProp.SetIRI(inboxURI)
   2786 	person.SetActivityStreamsInbox(inboxProp)
   2787 
   2788 	// shared inbox
   2789 	if sharedInboxIRI != nil {
   2790 		endpointsProp := streams.NewActivityStreamsEndpointsProperty()
   2791 		endpoints := streams.NewActivityStreamsEndpoints()
   2792 		sharedInboxProp := streams.NewActivityStreamsSharedInboxProperty()
   2793 		sharedInboxProp.SetIRI(sharedInboxIRI)
   2794 		endpoints.SetActivityStreamsSharedInbox(sharedInboxProp)
   2795 		endpointsProp.AppendActivityStreamsEndpoints(endpoints)
   2796 		person.SetActivityStreamsEndpoints(endpointsProp)
   2797 	}
   2798 
   2799 	// outbox
   2800 	// the activitypub outbox of this user for serving messages
   2801 	outboxProp := streams.NewActivityStreamsOutboxProperty()
   2802 	outboxProp.SetIRI(outboxURI)
   2803 	person.SetActivityStreamsOutbox(outboxProp)
   2804 
   2805 	// featured posts
   2806 	// Pinned posts.
   2807 	featuredProp := streams.NewTootFeaturedProperty()
   2808 	featuredProp.SetIRI(featuredURI)
   2809 	person.SetTootFeatured(featuredProp)
   2810 
   2811 	// featuredTags
   2812 	// NOT IMPLEMENTED
   2813 
   2814 	// preferredUsername
   2815 	// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
   2816 	preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
   2817 	preferredUsernameProp.SetXMLSchemaString(username)
   2818 	person.SetActivityStreamsPreferredUsername(preferredUsernameProp)
   2819 
   2820 	// name
   2821 	// Used as profile display name.
   2822 	nameProp := streams.NewActivityStreamsNameProperty()
   2823 	if displayName != "" {
   2824 		nameProp.AppendXMLSchemaString(displayName)
   2825 	} else {
   2826 		nameProp.AppendXMLSchemaString(username)
   2827 	}
   2828 	person.SetActivityStreamsName(nameProp)
   2829 
   2830 	// summary
   2831 	// Used as profile bio.
   2832 	if note != "" {
   2833 		summaryProp := streams.NewActivityStreamsSummaryProperty()
   2834 		summaryProp.AppendXMLSchemaString(note)
   2835 		person.SetActivityStreamsSummary(summaryProp)
   2836 	}
   2837 
   2838 	// url
   2839 	// Used as profile link.
   2840 	urlProp := streams.NewActivityStreamsUrlProperty()
   2841 	urlProp.AppendIRI(profileURL)
   2842 	person.SetActivityStreamsUrl(urlProp)
   2843 
   2844 	// manuallyApprovesFollowers
   2845 	manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
   2846 	manuallyApprovesFollowersProp.Set(manuallyApprovesFollowers)
   2847 	person.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
   2848 
   2849 	// discoverable
   2850 	// Will be shown in the profile directory.
   2851 	discoverableProp := streams.NewTootDiscoverableProperty()
   2852 	discoverableProp.Set(discoverable)
   2853 	person.SetTootDiscoverable(discoverableProp)
   2854 
   2855 	// devices
   2856 	// NOT IMPLEMENTED, probably won't implement
   2857 
   2858 	// alsoKnownAs
   2859 	// Required for Move activity.
   2860 	// TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool
   2861 
   2862 	// publicKey
   2863 	// Required for signatures.
   2864 	publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty()
   2865 
   2866 	// create the public key
   2867 	publicKey := streams.NewW3IDSecurityV1PublicKey()
   2868 
   2869 	// set ID for the public key
   2870 	publicKeyIDProp := streams.NewJSONLDIdProperty()
   2871 	publicKeyIDProp.SetIRI(publicKeyURI)
   2872 	publicKey.SetJSONLDId(publicKeyIDProp)
   2873 
   2874 	// set owner for the public key
   2875 	publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty()
   2876 	publicKeyOwnerProp.SetIRI(profileIDURI)
   2877 	publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp)
   2878 
   2879 	// set the pem key itself
   2880 	encodedPublicKey, err := x509.MarshalPKIXPublicKey(pkey)
   2881 	if err != nil {
   2882 		panic(err)
   2883 	}
   2884 	publicKeyBytes := pem.EncodeToMemory(&pem.Block{
   2885 		Type:  "PUBLIC KEY",
   2886 		Bytes: encodedPublicKey,
   2887 	})
   2888 	publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty()
   2889 	publicKeyPEMProp.Set(string(publicKeyBytes))
   2890 	publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp)
   2891 
   2892 	// append the public key to the public key property
   2893 	publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
   2894 
   2895 	// set the public key property on the Person
   2896 	person.SetW3IDSecurityV1PublicKey(publicKeyProp)
   2897 
   2898 	// tag
   2899 	// TODO: Any tags used in the summary of this profile
   2900 
   2901 	// attachment
   2902 	// Used for profile fields.
   2903 	// TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
   2904 
   2905 	// endpoints
   2906 	// NOT IMPLEMENTED -- this is for shared inbox which we don't use
   2907 
   2908 	// icon
   2909 	// Used as profile avatar.
   2910 	iconProperty := streams.NewActivityStreamsIconProperty()
   2911 	iconImage := streams.NewActivityStreamsImage()
   2912 	mediaType := streams.NewActivityStreamsMediaTypeProperty()
   2913 	mediaType.Set(avatarContentType)
   2914 	iconImage.SetActivityStreamsMediaType(mediaType)
   2915 	avatarURLProperty := streams.NewActivityStreamsUrlProperty()
   2916 	avatarURLProperty.AppendIRI(avatarURL)
   2917 	iconImage.SetActivityStreamsUrl(avatarURLProperty)
   2918 	iconProperty.AppendActivityStreamsImage(iconImage)
   2919 	person.SetActivityStreamsIcon(iconProperty)
   2920 
   2921 	// image
   2922 	// Used as profile header.
   2923 	headerProperty := streams.NewActivityStreamsImageProperty()
   2924 	headerImage := streams.NewActivityStreamsImage()
   2925 	headerMediaType := streams.NewActivityStreamsMediaTypeProperty()
   2926 	mediaType.Set(headerContentType)
   2927 	headerImage.SetActivityStreamsMediaType(headerMediaType)
   2928 	headerURLProperty := streams.NewActivityStreamsUrlProperty()
   2929 	headerURLProperty.AppendIRI(headerURL)
   2930 	headerImage.SetActivityStreamsUrl(headerURLProperty)
   2931 	headerProperty.AppendActivityStreamsImage(headerImage)
   2932 	person.SetActivityStreamsImage(headerProperty)
   2933 
   2934 	return person
   2935 }
   2936 
   2937 func newAPGroup(
   2938 	profileIDURI *url.URL,
   2939 	followingURI *url.URL,
   2940 	followersURI *url.URL,
   2941 	inboxURI *url.URL,
   2942 	outboxURI *url.URL,
   2943 	featuredURI *url.URL,
   2944 	username string,
   2945 	displayName string,
   2946 	note string,
   2947 	profileURL *url.URL,
   2948 	discoverable bool,
   2949 	publicKeyURI *url.URL,
   2950 	pkey *rsa.PublicKey,
   2951 	avatarURL *url.URL,
   2952 	avatarContentType string,
   2953 	headerURL *url.URL,
   2954 	headerContentType string,
   2955 	manuallyApprovesFollowers bool,
   2956 ) vocab.ActivityStreamsGroup {
   2957 	group := streams.NewActivityStreamsGroup()
   2958 
   2959 	// id should be the activitypub URI of this group
   2960 	// something like https://example.org/users/example_group
   2961 	idProp := streams.NewJSONLDIdProperty()
   2962 	idProp.SetIRI(profileIDURI)
   2963 	group.SetJSONLDId(idProp)
   2964 
   2965 	// following
   2966 	// The URI for retrieving a list of accounts this group is following
   2967 	followingProp := streams.NewActivityStreamsFollowingProperty()
   2968 	followingProp.SetIRI(followingURI)
   2969 	group.SetActivityStreamsFollowing(followingProp)
   2970 
   2971 	// followers
   2972 	// The URI for retrieving a list of this user's followers
   2973 	followersProp := streams.NewActivityStreamsFollowersProperty()
   2974 	followersProp.SetIRI(followersURI)
   2975 	group.SetActivityStreamsFollowers(followersProp)
   2976 
   2977 	// inbox
   2978 	// the activitypub inbox of this user for accepting messages
   2979 	inboxProp := streams.NewActivityStreamsInboxProperty()
   2980 	inboxProp.SetIRI(inboxURI)
   2981 	group.SetActivityStreamsInbox(inboxProp)
   2982 
   2983 	// outbox
   2984 	// the activitypub outbox of this user for serving messages
   2985 	outboxProp := streams.NewActivityStreamsOutboxProperty()
   2986 	outboxProp.SetIRI(outboxURI)
   2987 	group.SetActivityStreamsOutbox(outboxProp)
   2988 
   2989 	// featured posts
   2990 	// Pinned posts.
   2991 	featuredProp := streams.NewTootFeaturedProperty()
   2992 	featuredProp.SetIRI(featuredURI)
   2993 	group.SetTootFeatured(featuredProp)
   2994 
   2995 	// featuredTags
   2996 	// NOT IMPLEMENTED
   2997 
   2998 	// preferredUsername
   2999 	// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
   3000 	preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
   3001 	preferredUsernameProp.SetXMLSchemaString(username)
   3002 	group.SetActivityStreamsPreferredUsername(preferredUsernameProp)
   3003 
   3004 	// name
   3005 	// Used as profile display name.
   3006 	nameProp := streams.NewActivityStreamsNameProperty()
   3007 	if displayName != "" {
   3008 		nameProp.AppendXMLSchemaString(displayName)
   3009 	} else {
   3010 		nameProp.AppendXMLSchemaString(username)
   3011 	}
   3012 	group.SetActivityStreamsName(nameProp)
   3013 
   3014 	// summary
   3015 	// Used as profile bio.
   3016 	if note != "" {
   3017 		summaryProp := streams.NewActivityStreamsSummaryProperty()
   3018 		summaryProp.AppendXMLSchemaString(note)
   3019 		group.SetActivityStreamsSummary(summaryProp)
   3020 	}
   3021 
   3022 	// url
   3023 	// Used as profile link.
   3024 	urlProp := streams.NewActivityStreamsUrlProperty()
   3025 	urlProp.AppendIRI(profileURL)
   3026 	group.SetActivityStreamsUrl(urlProp)
   3027 
   3028 	// manuallyApprovesFollowers
   3029 	manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
   3030 	manuallyApprovesFollowersProp.Set(manuallyApprovesFollowers)
   3031 	group.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
   3032 
   3033 	// discoverable
   3034 	// Will be shown in the profile directory.
   3035 	discoverableProp := streams.NewTootDiscoverableProperty()
   3036 	discoverableProp.Set(discoverable)
   3037 	group.SetTootDiscoverable(discoverableProp)
   3038 
   3039 	// devices
   3040 	// NOT IMPLEMENTED, probably won't implement
   3041 
   3042 	// alsoKnownAs
   3043 	// Required for Move activity.
   3044 	// TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool
   3045 
   3046 	// publicKey
   3047 	// Required for signatures.
   3048 	publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty()
   3049 
   3050 	// create the public key
   3051 	publicKey := streams.NewW3IDSecurityV1PublicKey()
   3052 
   3053 	// set ID for the public key
   3054 	publicKeyIDProp := streams.NewJSONLDIdProperty()
   3055 	publicKeyIDProp.SetIRI(publicKeyURI)
   3056 	publicKey.SetJSONLDId(publicKeyIDProp)
   3057 
   3058 	// set owner for the public key
   3059 	publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty()
   3060 	publicKeyOwnerProp.SetIRI(profileIDURI)
   3061 	publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp)
   3062 
   3063 	// set the pem key itself
   3064 	encodedPublicKey, err := x509.MarshalPKIXPublicKey(pkey)
   3065 	if err != nil {
   3066 		panic(err)
   3067 	}
   3068 	publicKeyBytes := pem.EncodeToMemory(&pem.Block{
   3069 		Type:  "PUBLIC KEY",
   3070 		Bytes: encodedPublicKey,
   3071 	})
   3072 	publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty()
   3073 	publicKeyPEMProp.Set(string(publicKeyBytes))
   3074 	publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp)
   3075 
   3076 	// append the public key to the public key property
   3077 	publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
   3078 
   3079 	// set the public key property on the Person
   3080 	group.SetW3IDSecurityV1PublicKey(publicKeyProp)
   3081 
   3082 	// tag
   3083 	// TODO: Any tags used in the summary of this profile
   3084 
   3085 	// attachment
   3086 	// Used for profile fields.
   3087 	// TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
   3088 
   3089 	// endpoints
   3090 	// NOT IMPLEMENTED -- this is for shared inbox which we don't use
   3091 
   3092 	// icon
   3093 	// Used as profile avatar.
   3094 	iconProperty := streams.NewActivityStreamsIconProperty()
   3095 	iconImage := streams.NewActivityStreamsImage()
   3096 	mediaType := streams.NewActivityStreamsMediaTypeProperty()
   3097 	mediaType.Set(avatarContentType)
   3098 	iconImage.SetActivityStreamsMediaType(mediaType)
   3099 	avatarURLProperty := streams.NewActivityStreamsUrlProperty()
   3100 	avatarURLProperty.AppendIRI(avatarURL)
   3101 	iconImage.SetActivityStreamsUrl(avatarURLProperty)
   3102 	iconProperty.AppendActivityStreamsImage(iconImage)
   3103 	group.SetActivityStreamsIcon(iconProperty)
   3104 
   3105 	// image
   3106 	// Used as profile header.
   3107 	headerProperty := streams.NewActivityStreamsImageProperty()
   3108 	headerImage := streams.NewActivityStreamsImage()
   3109 	headerMediaType := streams.NewActivityStreamsMediaTypeProperty()
   3110 	mediaType.Set(headerContentType)
   3111 	headerImage.SetActivityStreamsMediaType(headerMediaType)
   3112 	headerURLProperty := streams.NewActivityStreamsUrlProperty()
   3113 	headerURLProperty.AppendIRI(headerURL)
   3114 	headerImage.SetActivityStreamsUrl(headerURLProperty)
   3115 	headerProperty.AppendActivityStreamsImage(headerImage)
   3116 	group.SetActivityStreamsImage(headerProperty)
   3117 
   3118 	return group
   3119 }
   3120 
   3121 func newAPService(
   3122 	profileIDURI *url.URL,
   3123 	followingURI *url.URL,
   3124 	followersURI *url.URL,
   3125 	inboxURI *url.URL,
   3126 	outboxURI *url.URL,
   3127 	featuredURI *url.URL,
   3128 	username string,
   3129 	displayName string,
   3130 	note string,
   3131 	profileURL *url.URL,
   3132 	discoverable bool,
   3133 	publicKeyURI *url.URL,
   3134 	pkey *rsa.PublicKey,
   3135 	avatarURL *url.URL,
   3136 	avatarContentType string,
   3137 	headerURL *url.URL,
   3138 	headerContentType string,
   3139 	manuallyApprovesFollowers bool,
   3140 ) vocab.ActivityStreamsService {
   3141 	service := streams.NewActivityStreamsService()
   3142 
   3143 	// id should be the activitypub URI of this group
   3144 	// something like https://example.org/users/example_group
   3145 	idProp := streams.NewJSONLDIdProperty()
   3146 	idProp.SetIRI(profileIDURI)
   3147 	service.SetJSONLDId(idProp)
   3148 
   3149 	// following
   3150 	// The URI for retrieving a list of accounts this group is following
   3151 	followingProp := streams.NewActivityStreamsFollowingProperty()
   3152 	followingProp.SetIRI(followingURI)
   3153 	service.SetActivityStreamsFollowing(followingProp)
   3154 
   3155 	// followers
   3156 	// The URI for retrieving a list of this user's followers
   3157 	followersProp := streams.NewActivityStreamsFollowersProperty()
   3158 	followersProp.SetIRI(followersURI)
   3159 	service.SetActivityStreamsFollowers(followersProp)
   3160 
   3161 	// inbox
   3162 	// the activitypub inbox of this user for accepting messages
   3163 	inboxProp := streams.NewActivityStreamsInboxProperty()
   3164 	inboxProp.SetIRI(inboxURI)
   3165 	service.SetActivityStreamsInbox(inboxProp)
   3166 
   3167 	// outbox
   3168 	// the activitypub outbox of this user for serving messages
   3169 	outboxProp := streams.NewActivityStreamsOutboxProperty()
   3170 	outboxProp.SetIRI(outboxURI)
   3171 	service.SetActivityStreamsOutbox(outboxProp)
   3172 
   3173 	// featured posts
   3174 	// Pinned posts.
   3175 	featuredProp := streams.NewTootFeaturedProperty()
   3176 	featuredProp.SetIRI(featuredURI)
   3177 	service.SetTootFeatured(featuredProp)
   3178 
   3179 	// featuredTags
   3180 	// NOT IMPLEMENTED
   3181 
   3182 	// preferredUsername
   3183 	// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
   3184 	preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
   3185 	preferredUsernameProp.SetXMLSchemaString(username)
   3186 	service.SetActivityStreamsPreferredUsername(preferredUsernameProp)
   3187 
   3188 	// name
   3189 	// Used as profile display name.
   3190 	nameProp := streams.NewActivityStreamsNameProperty()
   3191 	if displayName != "" {
   3192 		nameProp.AppendXMLSchemaString(displayName)
   3193 	} else {
   3194 		nameProp.AppendXMLSchemaString(username)
   3195 	}
   3196 	service.SetActivityStreamsName(nameProp)
   3197 
   3198 	// summary
   3199 	// Used as profile bio.
   3200 	if note != "" {
   3201 		summaryProp := streams.NewActivityStreamsSummaryProperty()
   3202 		summaryProp.AppendXMLSchemaString(note)
   3203 		service.SetActivityStreamsSummary(summaryProp)
   3204 	}
   3205 
   3206 	// url
   3207 	// Used as profile link.
   3208 	urlProp := streams.NewActivityStreamsUrlProperty()
   3209 	urlProp.AppendIRI(profileURL)
   3210 	service.SetActivityStreamsUrl(urlProp)
   3211 
   3212 	// manuallyApprovesFollowers
   3213 	manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
   3214 	manuallyApprovesFollowersProp.Set(manuallyApprovesFollowers)
   3215 	service.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
   3216 
   3217 	// discoverable
   3218 	// Will be shown in the profile directory.
   3219 	discoverableProp := streams.NewTootDiscoverableProperty()
   3220 	discoverableProp.Set(discoverable)
   3221 	service.SetTootDiscoverable(discoverableProp)
   3222 
   3223 	// devices
   3224 	// NOT IMPLEMENTED, probably won't implement
   3225 
   3226 	// alsoKnownAs
   3227 	// Required for Move activity.
   3228 	// TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool
   3229 
   3230 	// publicKey
   3231 	// Required for signatures.
   3232 	publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty()
   3233 
   3234 	// create the public key
   3235 	publicKey := streams.NewW3IDSecurityV1PublicKey()
   3236 
   3237 	// set ID for the public key
   3238 	publicKeyIDProp := streams.NewJSONLDIdProperty()
   3239 	publicKeyIDProp.SetIRI(publicKeyURI)
   3240 	publicKey.SetJSONLDId(publicKeyIDProp)
   3241 
   3242 	// set owner for the public key
   3243 	publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty()
   3244 	publicKeyOwnerProp.SetIRI(profileIDURI)
   3245 	publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp)
   3246 
   3247 	// set the pem key itself
   3248 	encodedPublicKey, err := x509.MarshalPKIXPublicKey(pkey)
   3249 	if err != nil {
   3250 		panic(err)
   3251 	}
   3252 	publicKeyBytes := pem.EncodeToMemory(&pem.Block{
   3253 		Type:  "PUBLIC KEY",
   3254 		Bytes: encodedPublicKey,
   3255 	})
   3256 	publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty()
   3257 	publicKeyPEMProp.Set(string(publicKeyBytes))
   3258 	publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp)
   3259 
   3260 	// append the public key to the public key property
   3261 	publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
   3262 
   3263 	// set the public key property on the Person
   3264 	service.SetW3IDSecurityV1PublicKey(publicKeyProp)
   3265 
   3266 	// tag
   3267 	// TODO: Any tags used in the summary of this profile
   3268 
   3269 	// attachment
   3270 	// Used for profile fields.
   3271 	// TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
   3272 
   3273 	// endpoints
   3274 	// NOT IMPLEMENTED -- this is for shared inbox which we don't use
   3275 
   3276 	// icon
   3277 	// Used as profile avatar.
   3278 	iconProperty := streams.NewActivityStreamsIconProperty()
   3279 	iconImage := streams.NewActivityStreamsImage()
   3280 	mediaType := streams.NewActivityStreamsMediaTypeProperty()
   3281 	mediaType.Set(avatarContentType)
   3282 	iconImage.SetActivityStreamsMediaType(mediaType)
   3283 	avatarURLProperty := streams.NewActivityStreamsUrlProperty()
   3284 	avatarURLProperty.AppendIRI(avatarURL)
   3285 	iconImage.SetActivityStreamsUrl(avatarURLProperty)
   3286 	iconProperty.AppendActivityStreamsImage(iconImage)
   3287 	service.SetActivityStreamsIcon(iconProperty)
   3288 
   3289 	// image
   3290 	// Used as profile header.
   3291 	headerProperty := streams.NewActivityStreamsImageProperty()
   3292 	headerImage := streams.NewActivityStreamsImage()
   3293 	headerMediaType := streams.NewActivityStreamsMediaTypeProperty()
   3294 	mediaType.Set(headerContentType)
   3295 	headerImage.SetActivityStreamsMediaType(headerMediaType)
   3296 	headerURLProperty := streams.NewActivityStreamsUrlProperty()
   3297 	headerURLProperty.AppendIRI(headerURL)
   3298 	headerImage.SetActivityStreamsUrl(headerURLProperty)
   3299 	headerProperty.AppendActivityStreamsImage(headerImage)
   3300 	service.SetActivityStreamsImage(headerProperty)
   3301 
   3302 	return service
   3303 }
   3304 
   3305 func newAPMention(uri *url.URL, namestring string) vocab.ActivityStreamsMention {
   3306 	mention := streams.NewActivityStreamsMention()
   3307 
   3308 	hrefProp := streams.NewActivityStreamsHrefProperty()
   3309 	hrefProp.SetIRI(uri)
   3310 	mention.SetActivityStreamsHref(hrefProp)
   3311 
   3312 	nameProp := streams.NewActivityStreamsNameProperty()
   3313 	nameProp.AppendXMLSchemaString(namestring)
   3314 	mention.SetActivityStreamsName(nameProp)
   3315 
   3316 	return mention
   3317 }
   3318 
   3319 func newAPImage(url *url.URL, mediaType string, imageDescription string, blurhash string) vocab.ActivityStreamsImage {
   3320 	image := streams.NewActivityStreamsImage()
   3321 
   3322 	if url != nil {
   3323 		urlProp := streams.NewActivityStreamsUrlProperty()
   3324 		urlProp.AppendIRI(url)
   3325 		image.SetActivityStreamsUrl(urlProp)
   3326 	}
   3327 
   3328 	if mediaType != "" {
   3329 		mediaTypeProp := streams.NewActivityStreamsMediaTypeProperty()
   3330 		mediaTypeProp.Set(mediaType)
   3331 		image.SetActivityStreamsMediaType(mediaTypeProp)
   3332 	}
   3333 
   3334 	if imageDescription != "" {
   3335 		nameProp := streams.NewActivityStreamsNameProperty()
   3336 		nameProp.AppendXMLSchemaString(imageDescription)
   3337 		image.SetActivityStreamsName(nameProp)
   3338 	}
   3339 
   3340 	if blurhash != "" {
   3341 		blurhashProp := streams.NewTootBlurhashProperty()
   3342 		blurhashProp.Set(blurhash)
   3343 		image.SetTootBlurhash(blurhashProp)
   3344 	}
   3345 
   3346 	return image
   3347 }
   3348 
   3349 func newAPEmoji(id *url.URL, name string, updated time.Time, image vocab.ActivityStreamsImage) vocab.TootEmoji {
   3350 	emoji := streams.NewTootEmoji()
   3351 
   3352 	idProp := streams.NewJSONLDIdProperty()
   3353 	idProp.SetIRI(id)
   3354 	emoji.SetJSONLDId(idProp)
   3355 
   3356 	nameProp := streams.NewActivityStreamsNameProperty()
   3357 	nameProp.AppendXMLSchemaString(`:` + strings.Trim(name, ":") + `:`)
   3358 	emoji.SetActivityStreamsName(nameProp)
   3359 
   3360 	updatedProp := streams.NewActivityStreamsUpdatedProperty()
   3361 	updatedProp.Set(updated)
   3362 	emoji.SetActivityStreamsUpdated(updatedProp)
   3363 
   3364 	iconProp := streams.NewActivityStreamsIconProperty()
   3365 	iconProp.AppendActivityStreamsImage(image)
   3366 	emoji.SetActivityStreamsIcon(iconProp)
   3367 
   3368 	return emoji
   3369 }
   3370 
   3371 // NewAPNote returns a new activity streams note for the given parameters
   3372 func NewAPNote(
   3373 	noteID *url.URL,
   3374 	noteURL *url.URL,
   3375 	noteCreatedAt time.Time,
   3376 	noteContent string,
   3377 	noteSummary string,
   3378 	noteAttributedTo *url.URL,
   3379 	noteTo []*url.URL,
   3380 	noteCC []*url.URL,
   3381 	noteSensitive bool,
   3382 	noteMentions []vocab.ActivityStreamsMention,
   3383 	noteAttachments []vocab.ActivityStreamsImage,
   3384 ) vocab.ActivityStreamsNote {
   3385 	// create the note itself
   3386 	note := streams.NewActivityStreamsNote()
   3387 
   3388 	// set id
   3389 	if noteID != nil {
   3390 		id := streams.NewJSONLDIdProperty()
   3391 		id.Set(noteID)
   3392 		note.SetJSONLDId(id)
   3393 	}
   3394 
   3395 	// set noteURL
   3396 	if noteURL != nil {
   3397 		url := streams.NewActivityStreamsUrlProperty()
   3398 		url.AppendIRI(noteURL)
   3399 		note.SetActivityStreamsUrl(url)
   3400 	}
   3401 
   3402 	published := streams.NewActivityStreamsPublishedProperty()
   3403 	published.Set(noteCreatedAt)
   3404 	note.SetActivityStreamsPublished(published)
   3405 
   3406 	// set noteContent
   3407 	if noteContent != "" {
   3408 		content := streams.NewActivityStreamsContentProperty()
   3409 		content.AppendXMLSchemaString(noteContent)
   3410 		note.SetActivityStreamsContent(content)
   3411 	}
   3412 
   3413 	// set noteSummary (aka content warning)
   3414 	if noteSummary != "" {
   3415 		summary := streams.NewActivityStreamsSummaryProperty()
   3416 		summary.AppendXMLSchemaString(noteSummary)
   3417 		note.SetActivityStreamsSummary(summary)
   3418 	}
   3419 
   3420 	// set noteAttributedTo (the url of the author of the note)
   3421 	if noteAttributedTo != nil {
   3422 		attributedTo := streams.NewActivityStreamsAttributedToProperty()
   3423 		attributedTo.AppendIRI(noteAttributedTo)
   3424 		note.SetActivityStreamsAttributedTo(attributedTo)
   3425 	}
   3426 
   3427 	// set noteTO
   3428 	if noteTo != nil {
   3429 		to := streams.NewActivityStreamsToProperty()
   3430 		for _, r := range noteTo {
   3431 			to.AppendIRI(r)
   3432 		}
   3433 		note.SetActivityStreamsTo(to)
   3434 	}
   3435 
   3436 	// set noteCC
   3437 	if noteCC != nil {
   3438 		cc := streams.NewActivityStreamsCcProperty()
   3439 		for _, r := range noteCC {
   3440 			cc.AppendIRI(r)
   3441 		}
   3442 		note.SetActivityStreamsCc(cc)
   3443 	}
   3444 
   3445 	// mentions
   3446 	tag := streams.NewActivityStreamsTagProperty()
   3447 	for _, m := range noteMentions {
   3448 		tag.AppendActivityStreamsMention(m)
   3449 	}
   3450 	note.SetActivityStreamsTag(tag)
   3451 
   3452 	// append any attachments as ActivityStreamsImage
   3453 	if noteAttachments != nil {
   3454 		attachmentProperty := streams.NewActivityStreamsAttachmentProperty()
   3455 		for _, a := range noteAttachments {
   3456 			attachmentProperty.AppendActivityStreamsImage(a)
   3457 		}
   3458 		note.SetActivityStreamsAttachment(attachmentProperty)
   3459 	}
   3460 
   3461 	return note
   3462 }
   3463 
   3464 // WrapAPNoteInCreate wraps the given activity streams note in a Create activity streams action
   3465 func WrapAPNoteInCreate(createID *url.URL, createActor *url.URL, createPublished time.Time, createNote vocab.ActivityStreamsNote) vocab.ActivityStreamsCreate {
   3466 	// create the.... create
   3467 	create := streams.NewActivityStreamsCreate()
   3468 
   3469 	// set createID
   3470 	if createID != nil {
   3471 		id := streams.NewJSONLDIdProperty()
   3472 		id.Set(createID)
   3473 		create.SetJSONLDId(id)
   3474 	}
   3475 
   3476 	// set createActor
   3477 	if createActor != nil {
   3478 		actor := streams.NewActivityStreamsActorProperty()
   3479 		actor.AppendIRI(createActor)
   3480 		create.SetActivityStreamsActor(actor)
   3481 	}
   3482 
   3483 	// set createPublished (time)
   3484 	if !createPublished.IsZero() {
   3485 		published := streams.NewActivityStreamsPublishedProperty()
   3486 		published.Set(createPublished)
   3487 		create.SetActivityStreamsPublished(published)
   3488 	}
   3489 
   3490 	// setCreateTo
   3491 	if createNote.GetActivityStreamsTo() != nil {
   3492 		create.SetActivityStreamsTo(createNote.GetActivityStreamsTo())
   3493 	}
   3494 
   3495 	// setCreateCC
   3496 	if createNote.GetActivityStreamsCc() != nil {
   3497 		create.SetActivityStreamsCc(createNote.GetActivityStreamsCc())
   3498 	}
   3499 
   3500 	// set createNote
   3501 	if createNote != nil {
   3502 		note := streams.NewActivityStreamsObjectProperty()
   3503 		note.AppendActivityStreamsNote(createNote)
   3504 		create.SetActivityStreamsObject(note)
   3505 	}
   3506 
   3507 	return create
   3508 }
   3509 
   3510 func newAPAnnounce(announceID *url.URL, announceActor *url.URL, announcePublished time.Time, announceTo *url.URL, announceNote vocab.ActivityStreamsNote) vocab.ActivityStreamsAnnounce {
   3511 	announce := streams.NewActivityStreamsAnnounce()
   3512 
   3513 	if announceID != nil {
   3514 		id := streams.NewJSONLDIdProperty()
   3515 		id.Set(announceID)
   3516 		announce.SetJSONLDId(id)
   3517 	}
   3518 
   3519 	if announceActor != nil {
   3520 		actor := streams.NewActivityStreamsActorProperty()
   3521 		actor.AppendIRI(announceActor)
   3522 		announce.SetActivityStreamsActor(actor)
   3523 	}
   3524 
   3525 	if !announcePublished.IsZero() {
   3526 		published := streams.NewActivityStreamsPublishedProperty()
   3527 		published.Set(announcePublished)
   3528 		announce.SetActivityStreamsPublished(published)
   3529 	}
   3530 
   3531 	to := streams.NewActivityStreamsToProperty()
   3532 	to.AppendIRI(announceTo)
   3533 	announce.SetActivityStreamsTo(announceNote.GetActivityStreamsTo())
   3534 
   3535 	cc := streams.NewActivityStreamsCcProperty()
   3536 	cc.AppendIRI(announceNote.GetActivityStreamsAttributedTo().Begin().GetIRI())
   3537 	announce.SetActivityStreamsCc(cc)
   3538 
   3539 	if announceNote != nil {
   3540 		noteIRI := streams.NewActivityStreamsObjectProperty()
   3541 		noteIRI.AppendIRI(announceNote.GetJSONLDId().Get())
   3542 		announce.SetActivityStreamsObject(noteIRI)
   3543 	}
   3544 
   3545 	return announce
   3546 }
   3547 
   3548 func newAPDelete(deleteTarget *url.URL, deleteActor *url.URL, deletePublished time.Time, deleteTo *url.URL) vocab.ActivityStreamsDelete {
   3549 	delete := streams.NewActivityStreamsDelete()
   3550 
   3551 	objectProp := streams.NewActivityStreamsObjectProperty()
   3552 	objectProp.AppendIRI(deleteTarget)
   3553 	delete.SetActivityStreamsObject(objectProp)
   3554 
   3555 	to := streams.NewActivityStreamsToProperty()
   3556 	to.AppendIRI(deleteTo)
   3557 	delete.SetActivityStreamsTo(to)
   3558 
   3559 	actor := streams.NewActivityStreamsActorProperty()
   3560 	actor.AppendIRI(deleteActor)
   3561 	delete.SetActivityStreamsActor(actor)
   3562 
   3563 	published := streams.NewActivityStreamsPublishedProperty()
   3564 	published.Set(deletePublished)
   3565 	delete.SetActivityStreamsPublished(published)
   3566 
   3567 	return delete
   3568 }