summaryrefslogtreecommitdiff
path: root/testrig
diff options
context:
space:
mode:
authorLibravatar Tobi Smethurst <31960611+tsmethurst@users.noreply.github.com>2021-05-08 14:25:55 +0200
committerLibravatar GitHub <noreply@github.com>2021-05-08 14:25:55 +0200
commit6f5c045284d34ba580d3007f70b97e05d6760527 (patch)
tree7614da22fba906361a918fb3527465b39272ac93 /testrig
parentRevert "make boosts work woo (#12)" (#15) (diff)
downloadgotosocial-6f5c045284d34ba580d3007f70b97e05d6760527.tar.xz
Ap (#14)
Big restructuring and initial work on activitypub
Diffstat (limited to 'testrig')
-rw-r--r--testrig/actions.go71
-rw-r--r--testrig/db.go4
-rw-r--r--testrig/federator.go (renamed from testrig/distributor.go)11
-rw-r--r--testrig/media/test-jpeg.jpgbin0 -> 269739 bytes
-rw-r--r--testrig/processor.go31
-rw-r--r--testrig/testmodels.go558
-rw-r--r--testrig/transportcontroller.go73
-rw-r--r--testrig/typeconverter.go (renamed from testrig/mastoconverter.go)8
-rw-r--r--testrig/util.go11
9 files changed, 669 insertions, 98 deletions
diff --git a/testrig/actions.go b/testrig/actions.go
index 1caa18581..7ed75b18f 100644
--- a/testrig/actions.go
+++ b/testrig/actions.go
@@ -19,24 +19,26 @@
package testrig
import (
+ "bytes"
"context"
"fmt"
+ "io/ioutil"
+ "net/http"
"os"
"os/signal"
"syscall"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/action"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule/account"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule/admin"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule/app"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule/auth"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule/fileserver"
- mediaModule "github.com/superseriousbusiness/gotosocial/internal/apimodule/media"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule/security"
- "github.com/superseriousbusiness/gotosocial/internal/apimodule/status"
- "github.com/superseriousbusiness/gotosocial/internal/cache"
+ "github.com/superseriousbusiness/gotosocial/internal/api"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/account"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/admin"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/app"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver"
+ mediaModule "github.com/superseriousbusiness/gotosocial/internal/api/client/media"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/status"
+ "github.com/superseriousbusiness/gotosocial/internal/api/security"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/gotosocial"
@@ -44,33 +46,39 @@ import (
// Run creates and starts a gotosocial testrig server
var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logrus.Logger) error {
+ c := NewTestConfig()
dbService := NewTestDB()
router := NewTestRouter()
storageBackend := NewTestStorage()
- mediaHandler := NewTestMediaHandler(dbService, storageBackend)
- oauthServer := NewTestOauthServer(dbService)
- distributor := NewTestDistributor()
- if err := distributor.Start(); err != nil {
- return fmt.Errorf("error starting distributor: %s", err)
- }
- mastoConverter := NewTestMastoConverter(dbService)
- c := NewTestConfig()
+ typeConverter := NewTestTypeConverter(dbService)
+ transportController := NewTestTransportController(NewMockHTTPClient(func(req *http.Request) (*http.Response, error) {
+ r := ioutil.NopCloser(bytes.NewReader([]byte{}))
+ return &http.Response{
+ StatusCode: 200,
+ Body: r,
+ }, nil
+ }))
+ federator := federation.NewFederator(dbService, transportController, c, log, typeConverter)
+ processor := NewTestProcessor(dbService, storageBackend, federator)
+ if err := processor.Start(); err != nil {
+ return fmt.Errorf("error starting processor: %s", err)
+ }
StandardDBSetup(dbService)
StandardStorageSetup(storageBackend, "./testrig/media")
// build client api modules
- authModule := auth.New(oauthServer, dbService, log)
- accountModule := account.New(c, dbService, oauthServer, mediaHandler, mastoConverter, log)
- appsModule := app.New(oauthServer, dbService, mastoConverter, log)
- mm := mediaModule.New(dbService, mediaHandler, mastoConverter, c, log)
- fileServerModule := fileserver.New(c, dbService, storageBackend, log)
- adminModule := admin.New(c, dbService, mediaHandler, mastoConverter, log)
- statusModule := status.New(c, dbService, mediaHandler, mastoConverter, distributor, log)
+ authModule := auth.New(c, dbService, NewTestOauthServer(dbService), log)
+ accountModule := account.New(c, processor, log)
+ appsModule := app.New(c, processor, log)
+ mm := mediaModule.New(c, processor, log)
+ fileServerModule := fileserver.New(c, processor, log)
+ adminModule := admin.New(c, processor, log)
+ statusModule := status.New(c, processor, log)
securityModule := security.New(c, log)
- apiModules := []apimodule.ClientAPIModule{
+ apis := []api.ClientModule{
// modules with middleware go first
securityModule,
authModule,
@@ -84,20 +92,13 @@ var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logr
statusModule,
}
- for _, m := range apiModules {
+ for _, m := range apis {
if err := m.Route(router); err != nil {
return fmt.Errorf("routing error: %s", err)
}
- if err := m.CreateTables(dbService); err != nil {
- return fmt.Errorf("table creation error: %s", err)
- }
}
- // if err := dbService.CreateInstanceAccount(); err != nil {
- // return fmt.Errorf("error creating instance account: %s", err)
- // }
-
- gts, err := gotosocial.New(dbService, &cache.MockCache{}, router, federation.New(dbService, log), c)
+ gts, err := gotosocial.New(dbService, router, federator, c)
if err != nil {
return fmt.Errorf("error creating gotosocial service: %s", err)
}
diff --git a/testrig/db.go b/testrig/db.go
index 5974eae69..4d22ab3c8 100644
--- a/testrig/db.go
+++ b/testrig/db.go
@@ -23,7 +23,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
- "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
@@ -54,7 +54,7 @@ func NewTestDB() db.DB {
config := NewTestConfig()
l := logrus.New()
l.SetLevel(logrus.TraceLevel)
- testDB, err := db.New(context.Background(), config, l)
+ testDB, err := db.NewPostgresService(context.Background(), config, l)
if err != nil {
panic(err)
}
diff --git a/testrig/distributor.go b/testrig/federator.go
index a7206e5ea..63ad520db 100644
--- a/testrig/distributor.go
+++ b/testrig/federator.go
@@ -18,9 +18,12 @@
package testrig
-import "github.com/superseriousbusiness/gotosocial/internal/distributor"
+import (
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/federation"
+ "github.com/superseriousbusiness/gotosocial/internal/transport"
+)
-// NewTestDistributor returns a Distributor suitable for testing purposes
-func NewTestDistributor() distributor.Distributor {
- return distributor.New(NewTestLog())
+func NewTestFederator(db db.DB, tc transport.Controller) federation.Federator {
+ return federation.NewFederator(db, tc, NewTestConfig(), NewTestLog(), NewTestTypeConverter(db))
}
diff --git a/testrig/media/test-jpeg.jpg b/testrig/media/test-jpeg.jpg
new file mode 100644
index 000000000..a9ab154d4
--- /dev/null
+++ b/testrig/media/test-jpeg.jpg
Binary files differ
diff --git a/testrig/processor.go b/testrig/processor.go
new file mode 100644
index 000000000..9aa8e2509
--- /dev/null
+++ b/testrig/processor.go
@@ -0,0 +1,31 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package testrig
+
+import (
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/federation"
+ "github.com/superseriousbusiness/gotosocial/internal/message"
+ "github.com/superseriousbusiness/gotosocial/internal/storage"
+)
+
+// NewTestProcessor returns a Processor suitable for testing purposes
+func NewTestProcessor(db db.DB, storage storage.Storage, federator federation.Federator) message.Processor {
+ return message.NewProcessor(NewTestConfig(), NewTestTypeConverter(db), federator, NewTestOauthServer(db), NewTestMediaHandler(db, storage), storage, db, NewTestLog())
+}
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
index 0d95ef21d..e550c66f7 100644
--- a/testrig/testmodels.go
+++ b/testrig/testmodels.go
@@ -19,13 +19,26 @@
package testrig
import (
+ "bytes"
+ "context"
+ "crypto"
"crypto/rand"
"crypto/rsa"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "io/ioutil"
"net"
+ "net/http"
+ "net/url"
"time"
- "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ "github.com/go-fed/activity/pub"
+ "github.com/go-fed/activity/streams"
+ "github.com/go-fed/activity/streams/vocab"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/typeutils"
)
// NewTestTokens returns a map of tokens keyed according to which account the token belongs to.
@@ -274,15 +287,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
URI: "http://localhost:8080/users/weed_lord420",
URL: "http://localhost:8080/@weed_lord420",
LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/weed_lord420/inbox",
- OutboxURL: "http://localhost:8080/users/weed_lord420/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/weed_lord420/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured",
+ InboxURI: "http://localhost:8080/users/weed_lord420/inbox",
+ OutboxURI: "http://localhost:8080/users/weed_lord420/outbox",
+ FollowersURI: "http://localhost:8080/users/weed_lord420/followers",
+ FollowingURI: "http://localhost:8080/users/weed_lord420/following",
+ FeaturedCollectionURI: "http://localhost:8080/users/weed_lord420/collections/featured",
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{},
+ PublicKeyURI: "http://localhost:8080/users/weed_lord420#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
@@ -310,12 +324,13 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Language: "en",
URI: "http://localhost:8080/users/admin",
URL: "http://localhost:8080/@admin",
+ PublicKeyURI: "http://localhost:8080/users/admin#main-key",
LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/admin/inbox",
- OutboxURL: "http://localhost:8080/users/admin/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/admin/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured",
+ InboxURI: "http://localhost:8080/users/admin/inbox",
+ OutboxURI: "http://localhost:8080/users/admin/outbox",
+ FollowersURI: "http://localhost:8080/users/admin/followers",
+ FollowingURI: "http://localhost:8080/users/admin/following",
+ FeaturedCollectionURI: "http://localhost:8080/users/admin/collections/featured",
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
@@ -348,15 +363,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
URI: "http://localhost:8080/users/the_mighty_zork",
URL: "http://localhost:8080/@the_mighty_zork",
LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/the_mighty_zork/inbox",
- OutboxURL: "http://localhost:8080/users/the_mighty_zork/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/the_mighty_zork/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured",
+ InboxURI: "http://localhost:8080/users/the_mighty_zork/inbox",
+ OutboxURI: "http://localhost:8080/users/the_mighty_zork/outbox",
+ FollowersURI: "http://localhost:8080/users/the_mighty_zork/followers",
+ FollowingURI: "http://localhost:8080/users/the_mighty_zork/following",
+ FeaturedCollectionURI: "http://localhost:8080/users/the_mighty_zork/collections/featured",
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{},
+ PublicKeyURI: "http://localhost:8080/users/the_mighty_zork#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
@@ -385,15 +401,16 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
URI: "http://localhost:8080/users/1happyturtle",
URL: "http://localhost:8080/@1happyturtle",
LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/1happyturtle/inbox",
- OutboxURL: "http://localhost:8080/users/1happyturtle/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/1happyturtle/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured",
+ InboxURI: "http://localhost:8080/users/1happyturtle/inbox",
+ OutboxURI: "http://localhost:8080/users/1happyturtle/outbox",
+ FollowersURI: "http://localhost:8080/users/1happyturtle/followers",
+ FollowingURI: "http://localhost:8080/users/1happyturtle/following",
+ FeaturedCollectionURI: "http://localhost:8080/users/1happyturtle/collections/featured",
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{},
+ PublicKeyURI: "http://localhost:8080/users/1happyturtle#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
@@ -426,18 +443,19 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Discoverable: true,
Sensitive: false,
Language: "en",
- URI: "https://fossbros-anonymous.io/users/foss_satan",
- URL: "https://fossbros-anonymous.io/@foss_satan",
+ URI: "http://fossbros-anonymous.io/users/foss_satan",
+ URL: "http://fossbros-anonymous.io/@foss_satan",
LastWebfingeredAt: time.Time{},
- InboxURL: "https://fossbros-anonymous.io/users/foss_satan/inbox",
- OutboxURL: "https://fossbros-anonymous.io/users/foss_satan/outbox",
- SharedInboxURL: "",
- FollowersURL: "https://fossbros-anonymous.io/users/foss_satan/followers",
- FeaturedCollectionURL: "https://fossbros-anonymous.io/users/foss_satan/collections/featured",
+ InboxURI: "http://fossbros-anonymous.io/users/foss_satan/inbox",
+ OutboxURI: "http://fossbros-anonymous.io/users/foss_satan/outbox",
+ FollowersURI: "http://fossbros-anonymous.io/users/foss_satan/followers",
+ FollowingURI: "http://fossbros-anonymous.io/users/foss_satan/following",
+ FeaturedCollectionURI: "http://fossbros-anonymous.io/users/foss_satan/collections/featured",
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
- PrivateKey: &rsa.PrivateKey{},
- PublicKey: nil,
+ PrivateKey: nil,
+ PublicKey: &rsa.PublicKey{},
+ PublicKeyURI: "http://fossbros-anonymous.io/users/foss_satan#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
@@ -468,10 +486,10 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
}
pub := &priv.PublicKey
- // only local accounts get a private key
- if v.Domain == "" {
- v.PrivateKey = priv
- }
+ // normally only local accounts get a private key (obviously)
+ // but for testing purposes and signing requests, we'll give
+ // remote accounts a private key as well
+ v.PrivateKey = priv
v.PublicKey = pub
}
return accounts
@@ -676,25 +694,26 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment {
func NewTestEmojis() map[string]*gtsmodel.Emoji {
return map[string]*gtsmodel.Emoji{
"rainbow": {
- ID: "a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
- Shortcode: "rainbow",
- Domain: "",
- CreatedAt: time.Now(),
- UpdatedAt: time.Now(),
- ImageRemoteURL: "",
- ImageStaticRemoteURL: "",
- ImageURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
- ImagePath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
- ImageStaticURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
- ImageStaticPath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
- ImageContentType: "image/png",
- ImageFileSize: 36702,
- ImageStaticFileSize: 10413,
- ImageUpdatedAt: time.Now(),
- Disabled: false,
- URI: "http://localhost:8080/emoji/a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
- VisibleInPicker: true,
- CategoryID: "",
+ ID: "a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
+ Shortcode: "rainbow",
+ Domain: "",
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ ImageRemoteURL: "",
+ ImageStaticRemoteURL: "",
+ ImageURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
+ ImagePath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
+ ImageStaticURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
+ ImageStaticPath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png",
+ ImageContentType: "image/png",
+ ImageStaticContentType: "image/png",
+ ImageFileSize: 36702,
+ ImageStaticFileSize: 10413,
+ ImageUpdatedAt: time.Now(),
+ Disabled: false,
+ URI: "http://localhost:8080/emoji/a96ec4f3-1cae-47e4-a508-f9d66a6b221b",
+ VisibleInPicker: true,
+ CategoryID: "",
},
}
}
@@ -993,3 +1012,436 @@ func NewTestFaves() map[string]*gtsmodel.StatusFave {
},
}
}
+
+type ActivityWithSignature struct {
+ Activity pub.Activity
+ SignatureHeader string
+ DigestHeader string
+ DateHeader string
+}
+
+// NewTestActivities returns a bunch of pub.Activity types for use in testing the federation protocols.
+// A struct of accounts needs to be passed in because the activities will also be bundled along with
+// their requesting signatures.
+func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
+ dmForZork := newNote(
+ URLMustParse("https://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6"),
+ URLMustParse("https://fossbros-anonymous.io/@foss_satan/5424b153-4553-4f30-9358-7b92f7cd42f6"),
+ "hey zork here's a new private note for you",
+ "new note for zork",
+ URLMustParse("https://fossbros-anonymous.io/users/foss_satan"),
+ []*url.URL{URLMustParse("http://localhost:8080/users/the_mighty_zork")},
+ nil,
+ true)
+ createDmForZork := wrapNoteInCreate(
+ URLMustParse("https://fossbros-anonymous.io/users/foss_satan/statuses/5424b153-4553-4f30-9358-7b92f7cd42f6/activity"),
+ URLMustParse("https://fossbros-anonymous.io/users/foss_satan"),
+ time.Now(),
+ dmForZork)
+ sig, digest, date := getSignatureForActivity(createDmForZork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI))
+
+ return map[string]ActivityWithSignature{
+ "dm_for_zork": {
+ Activity: createDmForZork,
+ SignatureHeader: sig,
+ DigestHeader: digest,
+ DateHeader: date,
+ },
+ }
+}
+
+// NewTestFediPeople returns a bunch of activity pub Person representations for testing converters and so on.
+func NewTestFediPeople() map[string]typeutils.Accountable {
+ new_person_1priv, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ panic(err)
+ }
+ new_person_1pub := &new_person_1priv.PublicKey
+
+ return map[string]typeutils.Accountable{
+ "new_person_1": newPerson(
+ URLMustParse("https://unknown-instance.com/users/brand_new_person"),
+ URLMustParse("https://unknown-instance.com/users/brand_new_person/following"),
+ URLMustParse("https://unknown-instance.com/users/brand_new_person/followers"),
+ URLMustParse("https://unknown-instance.com/users/brand_new_person/inbox"),
+ URLMustParse("https://unknown-instance.com/users/brand_new_person/outbox"),
+ URLMustParse("https://unknown-instance.com/users/brand_new_person/collections/featured"),
+ "brand_new_person",
+ "Geoff Brando New Personson",
+ "hey I'm a new person, your instance hasn't seen me yet uwu",
+ URLMustParse("https://unknown-instance.com/@brand_new_person"),
+ true,
+ URLMustParse("https://unknown-instance.com/users/brand_new_person#main-key"),
+ new_person_1pub,
+ URLMustParse("https://unknown-instance.com/media/some_avatar_filename.jpeg"),
+ "image/jpeg",
+ URLMustParse("https://unknown-instance.com/media/some_header_filename.jpeg"),
+ "image/png",
+ ),
+ }
+}
+
+func NewTestDereferenceRequests(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature {
+ sig, digest, date := getSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].URI))
+ return map[string]ActivityWithSignature{
+ "foss_satan_dereference_zork": {
+ SignatureHeader: sig,
+ DigestHeader: digest,
+ DateHeader: date,
+ },
+ }
+}
+
+// getSignatureForActivity does some sneaky sneaky work with a mock http client and a test transport controller, in order to derive
+// the HTTP Signature for the given activity, public key ID, private key, and destination.
+func getSignatureForActivity(activity pub.Activity, pubKeyID string, privkey crypto.PrivateKey, destination *url.URL) (signatureHeader string, digestHeader string, dateHeader string) {
+ // create a client that basically just pulls the signature out of the request and sets it
+ client := &mockHTTPClient{
+ do: func(req *http.Request) (*http.Response, error) {
+ signatureHeader = req.Header.Get("Signature")
+ digestHeader = req.Header.Get("Digest")
+ dateHeader = req.Header.Get("Date")
+ r := ioutil.NopCloser(bytes.NewReader([]byte{})) // we only need this so the 'close' func doesn't nil out
+ return &http.Response{
+ StatusCode: 200,
+ Body: r,
+ }, nil
+ },
+ }
+
+ // use the client to create a new transport
+ c := NewTestTransportController(client)
+ tp, err := c.NewTransport(pubKeyID, privkey)
+ if err != nil {
+ panic(err)
+ }
+
+ // convert the activity into json bytes
+ m, err := activity.Serialize()
+ if err != nil {
+ panic(err)
+ }
+ bytes, err := json.Marshal(m)
+ if err != nil {
+ panic(err)
+ }
+
+ // trigger the delivery function, which will trigger the 'do' function of the recorder above
+ if err := tp.Deliver(context.Background(), bytes, destination); err != nil {
+ panic(err)
+ }
+
+ // headers should now be populated
+ return
+}
+
+// getSignatureForDereference does some sneaky sneaky work with a mock http client and a test transport controller, in order to derive
+// the HTTP Signature for the given derefence GET request using public key ID, private key, and destination.
+func getSignatureForDereference(pubKeyID string, privkey crypto.PrivateKey, destination *url.URL) (signatureHeader string, digestHeader string, dateHeader string) {
+ // create a client that basically just pulls the signature out of the request and sets it
+ client := &mockHTTPClient{
+ do: func(req *http.Request) (*http.Response, error) {
+ signatureHeader = req.Header.Get("Signature")
+ digestHeader = req.Header.Get("Digest")
+ dateHeader = req.Header.Get("Date")
+ r := ioutil.NopCloser(bytes.NewReader([]byte{})) // we only need this so the 'close' func doesn't nil out
+ return &http.Response{
+ StatusCode: 200,
+ Body: r,
+ }, nil
+ },
+ }
+
+ // use the client to create a new transport
+ c := NewTestTransportController(client)
+ tp, err := c.NewTransport(pubKeyID, privkey)
+ if err != nil {
+ panic(err)
+ }
+
+ // trigger the delivery function, which will trigger the 'do' function of the recorder above
+ if _, err := tp.Dereference(context.Background(), destination); err != nil {
+ panic(err)
+ }
+
+ // headers should now be populated
+ return
+}
+
+func newPerson(
+ profileIDURI *url.URL,
+ followingURI *url.URL,
+ followersURI *url.URL,
+ inboxURI *url.URL,
+ outboxURI *url.URL,
+ featuredURI *url.URL,
+ username string,
+ displayName string,
+ note string,
+ profileURL *url.URL,
+ discoverable bool,
+ publicKeyURI *url.URL,
+ pkey *rsa.PublicKey,
+ avatarURL *url.URL,
+ avatarContentType string,
+ headerURL *url.URL,
+ headerContentType string) typeutils.Accountable {
+ person := streams.NewActivityStreamsPerson()
+
+ // id should be the activitypub URI of this user
+ // something like https://example.org/users/example_user
+ idProp := streams.NewJSONLDIdProperty()
+ idProp.SetIRI(profileIDURI)
+ person.SetJSONLDId(idProp)
+
+ // following
+ // The URI for retrieving a list of accounts this user is following
+ followingProp := streams.NewActivityStreamsFollowingProperty()
+ followingProp.SetIRI(followingURI)
+ person.SetActivityStreamsFollowing(followingProp)
+
+ // followers
+ // The URI for retrieving a list of this user's followers
+ followersProp := streams.NewActivityStreamsFollowersProperty()
+ followersProp.SetIRI(followersURI)
+ person.SetActivityStreamsFollowers(followersProp)
+
+ // inbox
+ // the activitypub inbox of this user for accepting messages
+ inboxProp := streams.NewActivityStreamsInboxProperty()
+ inboxProp.SetIRI(inboxURI)
+ person.SetActivityStreamsInbox(inboxProp)
+
+ // outbox
+ // the activitypub outbox of this user for serving messages
+ outboxProp := streams.NewActivityStreamsOutboxProperty()
+ outboxProp.SetIRI(outboxURI)
+ person.SetActivityStreamsOutbox(outboxProp)
+
+ // featured posts
+ // Pinned posts.
+ featuredProp := streams.NewTootFeaturedProperty()
+ featuredProp.SetIRI(featuredURI)
+ person.SetTootFeatured(featuredProp)
+
+ // featuredTags
+ // NOT IMPLEMENTED
+
+ // preferredUsername
+ // Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
+ preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
+ preferredUsernameProp.SetXMLSchemaString(username)
+ person.SetActivityStreamsPreferredUsername(preferredUsernameProp)
+
+ // name
+ // Used as profile display name.
+ nameProp := streams.NewActivityStreamsNameProperty()
+ if displayName != "" {
+ nameProp.AppendXMLSchemaString(displayName)
+ } else {
+ nameProp.AppendXMLSchemaString(username)
+ }
+ person.SetActivityStreamsName(nameProp)
+
+ // summary
+ // Used as profile bio.
+ if note != "" {
+ summaryProp := streams.NewActivityStreamsSummaryProperty()
+ summaryProp.AppendXMLSchemaString(note)
+ person.SetActivityStreamsSummary(summaryProp)
+ }
+
+ // url
+ // Used as profile link.
+ urlProp := streams.NewActivityStreamsUrlProperty()
+ urlProp.AppendIRI(profileURL)
+ person.SetActivityStreamsUrl(urlProp)
+
+ // manuallyApprovesFollowers
+ // Will be shown as a locked account.
+ // 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
+
+ // discoverable
+ // Will be shown in the profile directory.
+ discoverableProp := streams.NewTootDiscoverableProperty()
+ discoverableProp.Set(discoverable)
+ person.SetTootDiscoverable(discoverableProp)
+
+ // devices
+ // NOT IMPLEMENTED, probably won't implement
+
+ // alsoKnownAs
+ // Required for Move activity.
+ // 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
+
+ // publicKey
+ // Required for signatures.
+ publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty()
+
+ // create the public key
+ publicKey := streams.NewW3IDSecurityV1PublicKey()
+
+ // set ID for the public key
+ publicKeyIDProp := streams.NewJSONLDIdProperty()
+ publicKeyIDProp.SetIRI(publicKeyURI)
+ publicKey.SetJSONLDId(publicKeyIDProp)
+
+ // set owner for the public key
+ publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty()
+ publicKeyOwnerProp.SetIRI(profileIDURI)
+ publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp)
+
+ // set the pem key itself
+ encodedPublicKey, err := x509.MarshalPKIXPublicKey(pkey)
+ if err != nil {
+ panic(err)
+ }
+ publicKeyBytes := pem.EncodeToMemory(&pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: encodedPublicKey,
+ })
+ publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty()
+ publicKeyPEMProp.Set(string(publicKeyBytes))
+ publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp)
+
+ // append the public key to the public key property
+ publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
+
+ // set the public key property on the Person
+ person.SetW3IDSecurityV1PublicKey(publicKeyProp)
+
+ // tag
+ // TODO: Any tags used in the summary of this profile
+
+ // attachment
+ // Used for profile fields.
+ // TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
+
+ // endpoints
+ // NOT IMPLEMENTED -- this is for shared inbox which we don't use
+
+ // icon
+ // Used as profile avatar.
+ iconProperty := streams.NewActivityStreamsIconProperty()
+ iconImage := streams.NewActivityStreamsImage()
+ mediaType := streams.NewActivityStreamsMediaTypeProperty()
+ mediaType.Set(avatarContentType)
+ iconImage.SetActivityStreamsMediaType(mediaType)
+ avatarURLProperty := streams.NewActivityStreamsUrlProperty()
+ avatarURLProperty.AppendIRI(avatarURL)
+ iconImage.SetActivityStreamsUrl(avatarURLProperty)
+ iconProperty.AppendActivityStreamsImage(iconImage)
+ person.SetActivityStreamsIcon(iconProperty)
+
+ // image
+ // Used as profile header.
+ headerProperty := streams.NewActivityStreamsImageProperty()
+ headerImage := streams.NewActivityStreamsImage()
+ headerMediaType := streams.NewActivityStreamsMediaTypeProperty()
+ mediaType.Set(headerContentType)
+ headerImage.SetActivityStreamsMediaType(headerMediaType)
+ headerURLProperty := streams.NewActivityStreamsUrlProperty()
+ headerURLProperty.AppendIRI(headerURL)
+ headerImage.SetActivityStreamsUrl(headerURLProperty)
+ headerProperty.AppendActivityStreamsImage(headerImage)
+
+ return person
+}
+
+// newNote returns a new activity streams note for the given parameters
+func newNote(
+ noteID *url.URL,
+ noteURL *url.URL,
+ noteContent string,
+ noteSummary string,
+ noteAttributedTo *url.URL,
+ noteTo []*url.URL,
+ noteCC []*url.URL,
+ noteSensitive bool) vocab.ActivityStreamsNote {
+
+ // create the note itself
+ note := streams.NewActivityStreamsNote()
+
+ // set id
+ if noteID != nil {
+ id := streams.NewJSONLDIdProperty()
+ id.Set(noteID)
+ note.SetJSONLDId(id)
+ }
+
+ // set noteURL
+ if noteURL != nil {
+ url := streams.NewActivityStreamsUrlProperty()
+ url.AppendIRI(noteURL)
+ note.SetActivityStreamsUrl(url)
+ }
+
+ // set noteContent
+ if noteContent != "" {
+ content := streams.NewActivityStreamsContentProperty()
+ content.AppendXMLSchemaString(noteContent)
+ note.SetActivityStreamsContent(content)
+ }
+
+ // set noteSummary (aka content warning)
+ if noteSummary != "" {
+ summary := streams.NewActivityStreamsSummaryProperty()
+ summary.AppendXMLSchemaString(noteSummary)
+ note.SetActivityStreamsSummary(summary)
+ }
+
+ // set noteAttributedTo (the url of the author of the note)
+ if noteAttributedTo != nil {
+ attributedTo := streams.NewActivityStreamsAttributedToProperty()
+ attributedTo.AppendIRI(noteAttributedTo)
+ note.SetActivityStreamsAttributedTo(attributedTo)
+ }
+
+ return note
+}
+
+// wrapNoteInCreate wraps the given activity streams note in a Create activity streams action
+func wrapNoteInCreate(createID *url.URL, createActor *url.URL, createPublished time.Time, createNote vocab.ActivityStreamsNote) vocab.ActivityStreamsCreate {
+ // create the.... create
+ create := streams.NewActivityStreamsCreate()
+
+ // set createID
+ if createID != nil {
+ id := streams.NewJSONLDIdProperty()
+ id.Set(createID)
+ create.SetJSONLDId(id)
+ }
+
+ // set createActor
+ if createActor != nil {
+ actor := streams.NewActivityStreamsActorProperty()
+ actor.AppendIRI(createActor)
+ create.SetActivityStreamsActor(actor)
+ }
+
+ // set createPublished (time)
+ if !createPublished.IsZero() {
+ published := streams.NewActivityStreamsPublishedProperty()
+ published.Set(createPublished)
+ create.SetActivityStreamsPublished(published)
+ }
+
+ // setCreateTo
+ if createNote.GetActivityStreamsTo() != nil {
+ create.SetActivityStreamsTo(createNote.GetActivityStreamsTo())
+ }
+
+ // setCreateCC
+ if createNote.GetActivityStreamsCc() != nil {
+ create.SetActivityStreamsCc(createNote.GetActivityStreamsCc())
+ }
+
+ // set createNote
+ if createNote != nil {
+ note := streams.NewActivityStreamsObjectProperty()
+ note.AppendActivityStreamsNote(createNote)
+ create.SetActivityStreamsObject(note)
+ }
+
+ return create
+}
diff --git a/testrig/transportcontroller.go b/testrig/transportcontroller.go
new file mode 100644
index 000000000..f2b5b93f7
--- /dev/null
+++ b/testrig/transportcontroller.go
@@ -0,0 +1,73 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package testrig
+
+import (
+ "bytes"
+ "io/ioutil"
+ "net/http"
+
+ "github.com/go-fed/activity/pub"
+ "github.com/superseriousbusiness/gotosocial/internal/federation"
+ "github.com/superseriousbusiness/gotosocial/internal/transport"
+)
+
+// NewTestTransportController returns a test transport controller with the given http client.
+//
+// Obviously for testing purposes you should not be making actual http calls to other servers.
+// To obviate this, use the function NewMockHTTPClient in this package to return a mock http
+// client that doesn't make any remote calls but just returns whatever you tell it to.
+//
+// Unlike the other test interfaces provided in this package, you'll probably want to call this function
+// PER TEST rather than per suite, so that the do function can be set on a test by test (or even more granular)
+// basis.
+func NewTestTransportController(client pub.HttpClient) transport.Controller {
+ return transport.NewController(NewTestConfig(), &federation.Clock{}, client, NewTestLog())
+}
+
+// NewMockHTTPClient returns a client that conforms to the pub.HttpClient interface,
+// but will always just execute the given `do` function, allowing responses to be mocked.
+//
+// If 'do' is nil, then a no-op function will be used instead, that just returns status 200.
+//
+// Note that you should never ever make ACTUAL http calls with this thing.
+func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error)) pub.HttpClient {
+ if do == nil {
+ return &mockHTTPClient{
+ do: func(req *http.Request) (*http.Response, error) {
+ r := ioutil.NopCloser(bytes.NewReader([]byte{}))
+ return &http.Response{
+ StatusCode: 200,
+ Body: r,
+ }, nil
+ },
+ }
+ }
+ return &mockHTTPClient{
+ do: do,
+ }
+}
+
+type mockHTTPClient struct {
+ do func(req *http.Request) (*http.Response, error)
+}
+
+func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
+ return m.do(req)
+}
diff --git a/testrig/mastoconverter.go b/testrig/typeconverter.go
index 10bdbdc95..9d49e6c99 100644
--- a/testrig/mastoconverter.go
+++ b/testrig/typeconverter.go
@@ -20,10 +20,10 @@ package testrig
import (
"github.com/superseriousbusiness/gotosocial/internal/db"
- "github.com/superseriousbusiness/gotosocial/internal/mastotypes"
+ "github.com/superseriousbusiness/gotosocial/internal/typeutils"
)
-// NewTestMastoConverter returned a mastotypes converter with the given db and the default test config
-func NewTestMastoConverter(db db.DB) mastotypes.Converter {
- return mastotypes.New(NewTestConfig(), db)
+// NewTestTypeConverter returned a type converter with the given db and the default test config
+func NewTestTypeConverter(db db.DB) typeutils.TypeConverter {
+ return typeutils.NewConverter(NewTestConfig(), db)
}
diff --git a/testrig/util.go b/testrig/util.go
index 96a979342..0fb8aa887 100644
--- a/testrig/util.go
+++ b/testrig/util.go
@@ -22,6 +22,7 @@ import (
"bytes"
"io"
"mime/multipart"
+ "net/url"
"os"
)
@@ -62,3 +63,13 @@ func CreateMultipartFormData(fieldName string, fileName string, extraFields map[
}
return b, w, nil
}
+
+// URLMustParse tries to parse the given URL and panics if it can't.
+// Should only be used in tests.
+func URLMustParse(stringURL string) *url.URL {
+ u, err := url.Parse(stringURL)
+ if err != nil {
+ panic(err)
+ }
+ return u
+}