diff options
Diffstat (limited to 'internal/api/client/statuses')
| -rw-r--r-- | internal/api/client/statuses/status_test.go | 114 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusboost_test.go | 751 | ||||
| -rw-r--r-- | internal/api/client/statuses/statuscreate_test.go | 92 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusfave_test.go | 300 | 
4 files changed, 916 insertions, 341 deletions
| diff --git a/internal/api/client/statuses/status_test.go b/internal/api/client/statuses/status_test.go index a979f0c00..1a92276a1 100644 --- a/internal/api/client/statuses/status_test.go +++ b/internal/api/client/statuses/status_test.go @@ -18,6 +18,12 @@  package statuses_test  import ( +	"bytes" +	"encoding/json" +	"io" +	"net/http/httptest" +	"strings" +  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -25,6 +31,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/federation"  	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/media"  	"github.com/superseriousbusiness/gotosocial/internal/processing"  	"github.com/superseriousbusiness/gotosocial/internal/state" @@ -59,6 +66,113 @@ type StatusStandardTestSuite struct {  	statusModule *statuses.Module  } +// Normalizes a status response to a determinate +// form, and pretty-prints it to JSON. +func (suite *StatusStandardTestSuite) parseStatusResponse( +	recorder *httptest.ResponseRecorder, +) (string, *httptest.ResponseRecorder) { + +	result := recorder.Result() +	defer result.Body.Close() + +	data, err := io.ReadAll(result.Body) +	if err != nil { +		suite.FailNow(err.Error()) +	} + +	rawMap := make(map[string]any) +	if err := json.Unmarshal(data, &rawMap); err != nil { +		suite.FailNow(err.Error()) +	} + +	// Make status fields determinate. +	suite.determinateStatus(rawMap) + +	// For readability, don't +	// escape HTML, and indent json. +	out := new(bytes.Buffer) +	enc := json.NewEncoder(out) +	enc.SetEscapeHTML(false) +	enc.SetIndent("", "  ") + +	if err := enc.Encode(&rawMap); err != nil { +		suite.FailNow(err.Error()) +	} + +	return strings.TrimSpace(out.String()), recorder +} + +func (suite *StatusStandardTestSuite) determinateStatus(rawMap map[string]any) { +	// Replace any fields from the raw map that +	// aren't determinate (date, id, url, etc). +	if _, ok := rawMap["id"]; ok { +		rawMap["id"] = id.Highest +	} + +	if _, ok := rawMap["uri"]; ok { +		rawMap["uri"] = "http://localhost:8080/some/determinate/url" +	} + +	if _, ok := rawMap["url"]; ok { +		rawMap["url"] = "http://localhost:8080/some/determinate/url" +	} + +	if _, ok := rawMap["created_at"]; ok { +		rawMap["created_at"] = "right the hell just now babyee" +	} + +	// Make ID of any mentions determinate. +	if menchiesRaw, ok := rawMap["mentions"]; ok { +		menchies, ok := menchiesRaw.([]any) +		if !ok { +			suite.FailNow("couldn't coerce menchies") +		} + +		for _, menchieRaw := range menchies { +			menchie, ok := menchieRaw.(map[string]any) +			if !ok { +				suite.FailNow("couldn't coerce menchie") +			} + +			if _, ok := menchie["id"]; ok { +				menchie["id"] = id.Highest +			} +		} +	} + +	// Make fields of any poll determinate. +	if pollRaw, ok := rawMap["poll"]; ok && pollRaw != nil { +		poll, ok := pollRaw.(map[string]any) +		if !ok { +			suite.FailNow("couldn't coerce poll") +		} + +		if _, ok := poll["id"]; ok { +			poll["id"] = id.Highest +		} + +		if _, ok := poll["expires_at"]; ok { +			poll["expires_at"] = "ah like you know whatever dude it's chill" +		} +	} + +	// Replace account since that's not really +	// what we care about for these tests. +	if _, ok := rawMap["account"]; ok { +		rawMap["account"] = "yeah this is my account, what about it punk" +	} + +	// If status contains an embedded +	// reblog do the same thing for that. +	if reblogRaw, ok := rawMap["reblog"]; ok && reblogRaw != nil { +		reblog, ok := reblogRaw.(map[string]any) +		if !ok { +			suite.FailNow("couldn't coerce reblog") +		} +		suite.determinateStatus(reblog) +	} +} +  func (suite *StatusStandardTestSuite) SetupSuite() {  	suite.testTokens = testrig.NewTestTokens()  	suite.testClients = testrig.NewTestClients() diff --git a/internal/api/client/statuses/statusboost_test.go b/internal/api/client/statuses/statusboost_test.go index f6f589a5c..8642ba7aa 100644 --- a/internal/api/client/statuses/statusboost_test.go +++ b/internal/api/client/statuses/statusboost_test.go @@ -17,9 +17,6 @@ package statuses_test  import (  	"context" -	"encoding/json" -	"fmt" -	"io/ioutil"  	"net/http"  	"net/http/httptest"  	"strings" @@ -28,7 +25,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" -	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -38,212 +35,596 @@ type StatusBoostTestSuite struct {  	StatusStandardTestSuite  } -func (suite *StatusBoostTestSuite) TestPostBoost() { -	t := suite.testTokens["local_account_1"] -	oauthToken := oauth.DBTokenToToken(t) - -	targetStatus := suite.testStatuses["admin_account_status_1"] - -	// setup +func (suite *StatusBoostTestSuite) postStatusBoost( +	targetStatusID string, +	app *gtsmodel.Application, +	token *gtsmodel.Token, +	user *gtsmodel.User, +	account *gtsmodel.Account, +) (string, *httptest.ResponseRecorder) {  	recorder := httptest.NewRecorder()  	ctx, _ := testrig.CreateGinTestContext(recorder, nil) -	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) -	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) -	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) -	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Set(oauth.SessionAuthorizedApplication, app) +	ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(token)) +	ctx.Set(oauth.SessionAuthorizedUser, user) +	ctx.Set(oauth.SessionAuthorizedAccount, account) + +	const pathBase = "http://localhost:8080/api" + statuses.ReblogPath +	path := strings.ReplaceAll(pathBase, ":"+apiutil.IDKey, targetStatusID) +	ctx.Request = httptest.NewRequest(http.MethodPost, path, nil)  	ctx.Request.Header.Set("accept", "application/json") -	// normally the router would populate these params from the path values, -	// but because we're calling the function directly, we need to set them manually. +	// Populate target status ID.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   statuses.IDKey, -			Value: targetStatus.ID, +			Key:   apiutil.IDKey, +			Value: targetStatusID,  		},  	} +	// Trigger handler.  	suite.statusModule.StatusBoostPOSTHandler(ctx) +	return suite.parseStatusResponse(recorder) +} -	// check response -	suite.EqualValues(http.StatusOK, recorder.Code) - -	result := recorder.Result() -	defer result.Body.Close() -	b, err := ioutil.ReadAll(result.Body) -	suite.NoError(err) - -	statusReply := &apimodel.Status{} -	err = json.Unmarshal(b, statusReply) -	suite.NoError(err) - -	suite.False(statusReply.Sensitive) -	suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility) - -	suite.Empty(statusReply.SpoilerText) -	suite.Empty(statusReply.Content) -	suite.Equal("the_mighty_zork", statusReply.Account.Username) -	suite.Len(statusReply.MediaAttachments, 0) -	suite.Len(statusReply.Mentions, 0) -	suite.Len(statusReply.Emojis, 0) -	suite.Len(statusReply.Tags, 0) - -	suite.NotNil(statusReply.Application) -	suite.Equal("really cool gts application", statusReply.Application.Name) - -	suite.NotNil(statusReply.Reblog) -	suite.Equal(1, statusReply.Reblog.ReblogsCount) -	suite.Equal(1, statusReply.Reblog.FavouritesCount) -	suite.Equal(targetStatus.Content, statusReply.Reblog.Content) -	suite.Equal(targetStatus.ContentWarning, statusReply.Reblog.SpoilerText) -	suite.Equal(targetStatus.AccountID, statusReply.Reblog.Account.ID) -	suite.Len(statusReply.Reblog.MediaAttachments, 1) -	suite.Len(statusReply.Reblog.Tags, 1) -	suite.Len(statusReply.Reblog.Emojis, 1) -	suite.True(statusReply.Reblogged) -	suite.True(statusReply.Reblog.Reblogged) -	suite.Equal("superseriousbusiness", statusReply.Reblog.Application.Name) +func (suite *StatusBoostTestSuite) TestPostBoost() { +	var ( +		targetStatus = suite.testStatuses["admin_account_status_1"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["local_account_1"] +		user         = suite.testUsers["local_account_1"] +		account      = suite.testAccounts["local_account_1"] +	) + +	out, recorder := suite.postStatusBoost( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) + +	// We should have OK from +	// our call to the function. +	suite.Equal(http.StatusOK, recorder.Code) + +	// Target status should now +	// be "reblogged" by us. +	suite.Equal(`{ +  "account": "yeah this is my account, what about it punk", +  "application": { +    "name": "really cool gts application", +    "website": "https://reallycool.app" +  }, +  "bookmarked": true, +  "card": null, +  "content": "", +  "created_at": "right the hell just now babyee", +  "emojis": [], +  "favourited": true, +  "favourites_count": 0, +  "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +  "in_reply_to_account_id": null, +  "in_reply_to_id": null, +  "interaction_policy": { +    "can_favourite": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reblog": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reply": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    } +  }, +  "language": null, +  "media_attachments": [], +  "mentions": [], +  "muted": false, +  "pinned": false, +  "poll": null, +  "reblog": { +    "account": "yeah this is my account, what about it punk", +    "application": { +      "name": "superseriousbusiness", +      "website": "https://superserious.business" +    }, +    "bookmarked": true, +    "card": null, +    "content": "hello world! #welcome ! first post on the instance :rainbow: !", +    "created_at": "right the hell just now babyee", +    "emojis": [ +      { +        "category": "reactions", +        "shortcode": "rainbow", +        "static_url": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png", +        "url": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png", +        "visible_in_picker": true +      } +    ], +    "favourited": true, +    "favourites_count": 1, +    "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +    "in_reply_to_account_id": null, +    "in_reply_to_id": null, +    "interaction_policy": { +      "can_favourite": { +        "always": [ +          "public", +          "me" +        ], +        "with_approval": [] +      }, +      "can_reblog": { +        "always": [ +          "public", +          "me" +        ], +        "with_approval": [] +      }, +      "can_reply": { +        "always": [ +          "public", +          "me" +        ], +        "with_approval": [] +      } +    }, +    "language": "en", +    "media_attachments": [ +      { +        "blurhash": "LIIE|gRj00WB-;j[t7j[4nWBj[Rj", +        "description": "Black and white image of some 50's style text saying: Welcome On Board", +        "id": "01F8MH6NEM8D7527KZAECTCR76", +        "meta": { +          "focus": { +            "x": 0, +            "y": 0 +          }, +          "original": { +            "aspect": 1.9047619, +            "height": 630, +            "size": "1200x630", +            "width": 1200 +          }, +          "small": { +            "aspect": 1.9104477, +            "height": 268, +            "size": "512x268", +            "width": 512 +          } +        }, +        "preview_remote_url": null, +        "preview_url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.webp", +        "remote_url": null, +        "text_url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg", +        "type": "image", +        "url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg" +      } +    ], +    "mentions": [], +    "muted": false, +    "pinned": false, +    "poll": null, +    "reblog": null, +    "reblogged": true, +    "reblogs_count": 1, +    "replies_count": 1, +    "sensitive": false, +    "spoiler_text": "", +    "tags": [ +      { +        "name": "welcome", +        "url": "http://localhost:8080/tags/welcome" +      } +    ], +    "text": "hello world! #welcome ! first post on the instance :rainbow: !", +    "uri": "http://localhost:8080/some/determinate/url", +    "url": "http://localhost:8080/some/determinate/url", +    "visibility": "public" +  }, +  "reblogged": true, +  "reblogs_count": 0, +  "replies_count": 0, +  "sensitive": false, +  "spoiler_text": "", +  "tags": [], +  "uri": "http://localhost:8080/some/determinate/url", +  "url": "http://localhost:8080/some/determinate/url", +  "visibility": "public" +}`, out)  }  func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() { -	t := suite.testTokens["local_account_1"] -	oauthToken := oauth.DBTokenToToken(t) - -	testStatus := suite.testStatuses["local_account_1_status_5"] -	testAccount := suite.testAccounts["local_account_1"] -	testUser := suite.testUsers["local_account_1"] - -	recorder := httptest.NewRecorder() -	ctx, _ := testrig.CreateGinTestContext(recorder, nil) -	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) -	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) -	ctx.Set(oauth.SessionAuthorizedUser, testUser) -	ctx.Set(oauth.SessionAuthorizedAccount, testAccount) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", testStatus.ID, 1)), nil) -	ctx.Request.Header.Set("accept", "application/json") - -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   statuses.IDKey, -			Value: testStatus.ID, -		}, -	} - -	suite.statusModule.StatusBoostPOSTHandler(ctx) - -	// check response -	suite.EqualValues(http.StatusOK, recorder.Code) - -	result := recorder.Result() -	defer result.Body.Close() -	b, err := ioutil.ReadAll(result.Body) -	suite.NoError(err) - -	responseStatus := &apimodel.Status{} -	err = json.Unmarshal(b, responseStatus) -	suite.NoError(err) - -	suite.False(responseStatus.Sensitive) -	suite.Equal(suite.tc.VisToAPIVis(context.Background(), testStatus.Visibility), responseStatus.Visibility) - -	suite.Empty(responseStatus.SpoilerText) -	suite.Empty(responseStatus.Content) -	suite.Equal("the_mighty_zork", responseStatus.Account.Username) -	suite.Len(responseStatus.MediaAttachments, 0) -	suite.Len(responseStatus.Mentions, 0) -	suite.Len(responseStatus.Emojis, 0) -	suite.Len(responseStatus.Tags, 0) - -	suite.NotNil(responseStatus.Application) -	suite.Equal("really cool gts application", responseStatus.Application.Name) - -	suite.NotNil(responseStatus.Reblog) -	suite.Equal(1, responseStatus.Reblog.ReblogsCount) -	suite.Equal(0, responseStatus.Reblog.FavouritesCount) -	suite.Equal(testStatus.Content, responseStatus.Reblog.Content) -	suite.Equal(testStatus.ContentWarning, responseStatus.Reblog.SpoilerText) -	suite.Equal(testStatus.AccountID, responseStatus.Reblog.Account.ID) -	suite.Equal(suite.tc.VisToAPIVis(context.Background(), testStatus.Visibility), responseStatus.Reblog.Visibility) -	suite.Empty(responseStatus.Reblog.MediaAttachments) -	suite.Empty(responseStatus.Reblog.Tags) -	suite.Empty(responseStatus.Reblog.Emojis) -	suite.True(responseStatus.Reblogged) -	suite.True(responseStatus.Reblog.Reblogged) -	suite.Equal("really cool gts application", responseStatus.Reblog.Application.Name) +	var ( +		targetStatus = suite.testStatuses["local_account_1_status_5"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["local_account_1"] +		user         = suite.testUsers["local_account_1"] +		account      = suite.testAccounts["local_account_1"] +	) + +	out, recorder := suite.postStatusBoost( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) + +	// We should have OK from +	// our call to the function. +	suite.Equal(http.StatusOK, recorder.Code) + +	// Target status should now +	// be "reblogged" by us. +	suite.Equal(`{ +  "account": "yeah this is my account, what about it punk", +  "application": { +    "name": "really cool gts application", +    "website": "https://reallycool.app" +  }, +  "bookmarked": false, +  "card": null, +  "content": "", +  "created_at": "right the hell just now babyee", +  "emojis": [], +  "favourited": false, +  "favourites_count": 0, +  "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +  "in_reply_to_account_id": null, +  "in_reply_to_id": null, +  "interaction_policy": { +    "can_favourite": { +      "always": [ +        "author", +        "followers", +        "mentioned", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reblog": { +      "always": [ +        "author", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reply": { +      "always": [ +        "author", +        "followers", +        "mentioned", +        "me" +      ], +      "with_approval": [] +    } +  }, +  "language": null, +  "media_attachments": [], +  "mentions": [], +  "muted": false, +  "pinned": false, +  "poll": null, +  "reblog": { +    "account": "yeah this is my account, what about it punk", +    "application": { +      "name": "really cool gts application", +      "website": "https://reallycool.app" +    }, +    "bookmarked": false, +    "card": null, +    "content": "hi!", +    "created_at": "right the hell just now babyee", +    "emojis": [], +    "favourited": false, +    "favourites_count": 0, +    "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +    "in_reply_to_account_id": null, +    "in_reply_to_id": null, +    "interaction_policy": { +      "can_favourite": { +        "always": [ +          "author", +          "followers", +          "mentioned", +          "me" +        ], +        "with_approval": [] +      }, +      "can_reblog": { +        "always": [ +          "author", +          "me" +        ], +        "with_approval": [] +      }, +      "can_reply": { +        "always": [ +          "author", +          "followers", +          "mentioned", +          "me" +        ], +        "with_approval": [] +      } +    }, +    "language": "en", +    "media_attachments": [], +    "mentions": [], +    "muted": false, +    "pinned": false, +    "poll": null, +    "reblog": null, +    "reblogged": true, +    "reblogs_count": 1, +    "replies_count": 0, +    "sensitive": false, +    "spoiler_text": "", +    "tags": [], +    "text": "hi!", +    "uri": "http://localhost:8080/some/determinate/url", +    "url": "http://localhost:8080/some/determinate/url", +    "visibility": "private" +  }, +  "reblogged": true, +  "reblogs_count": 0, +  "replies_count": 0, +  "sensitive": false, +  "spoiler_text": "", +  "tags": [], +  "uri": "http://localhost:8080/some/determinate/url", +  "url": "http://localhost:8080/some/determinate/url", +  "visibility": "private" +}`, out)  } -// try to boost a status that's not boostable / visible to us +// Try to boost a status that's +// not boostable / visible to us.  func (suite *StatusBoostTestSuite) TestPostUnboostable() { -	t := suite.testTokens["local_account_1"] -	oauthToken := oauth.DBTokenToToken(t) - -	targetStatus := suite.testStatuses["local_account_2_status_4"] - -	// setup -	recorder := httptest.NewRecorder() -	ctx, _ := testrig.CreateGinTestContext(recorder, nil) -	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) -	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) -	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) -	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting -	ctx.Request.Header.Set("accept", "application/json") - -	// normally the router would populate these params from the path values, -	// but because we're calling the function directly, we need to set them manually. -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   statuses.IDKey, -			Value: targetStatus.ID, -		}, -	} - -	suite.statusModule.StatusBoostPOSTHandler(ctx) - -	// check response +	var ( +		targetStatus = suite.testStatuses["local_account_2_status_4"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["local_account_1"] +		user         = suite.testUsers["local_account_1"] +		account      = suite.testAccounts["local_account_1"] +	) + +	out, recorder := suite.postStatusBoost( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) + +	// We should have 403 from +	// our call to the function.  	suite.Equal(http.StatusForbidden, recorder.Code) -	result := recorder.Result() -	defer result.Body.Close() -	b, err := ioutil.ReadAll(result.Body) -	suite.NoError(err) -	suite.Equal(`{"error":"Forbidden: you do not have permission to boost this status"}`, string(b)) +	// We should have a helpful message. +	suite.Equal(`{ +  "error": "Forbidden: you do not have permission to boost this status" +}`, out)  } -// try to boost a status that's not visible to the user +// Try to boost a status that's not visible to the user.  func (suite *StatusBoostTestSuite) TestPostNotVisible() { -	// stop local_account_2 following zork -	err := suite.db.DeleteByID(context.Background(), suite.testFollows["local_account_2_local_account_1"].ID, >smodel.Follow{}) -	suite.NoError(err) - -	t := suite.testTokens["local_account_2"] -	oauthToken := oauth.DBTokenToToken(t) - -	targetStatus := suite.testStatuses["local_account_1_status_3"] // this is a mutual only status and these accounts aren't mutuals - -	// setup -	recorder := httptest.NewRecorder() -	ctx, _ := testrig.CreateGinTestContext(recorder, nil) -	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) -	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) -	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"]) -	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_2"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting -	ctx.Request.Header.Set("accept", "application/json") - -	// normally the router would populate these params from the path values, -	// but because we're calling the function directly, we need to set them manually. -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   statuses.IDKey, -			Value: targetStatus.ID, -		}, +	// Stop local_account_2 following zork. +	err := suite.db.DeleteFollowByID( +		context.Background(), +		suite.testFollows["local_account_2_local_account_1"].ID, +	) +	if err != nil { +		suite.FailNow(err.Error())  	} -	suite.statusModule.StatusBoostPOSTHandler(ctx) +	var ( +		// This is a mutual only status and +		// these accounts aren't mutuals anymore. +		targetStatus = suite.testStatuses["local_account_1_status_3"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["local_account_2"] +		user         = suite.testUsers["local_account_2"] +		account      = suite.testAccounts["local_account_2"] +	) + +	out, recorder := suite.postStatusBoost( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) + +	// We should have 404 from +	// our call to the function. +	suite.Equal(http.StatusNotFound, recorder.Code) + +	// We should have a helpful message. +	suite.Equal(`{ +  "error": "Not Found: target status not found" +}`, out) +} -	// check response -	suite.Equal(http.StatusNotFound, recorder.Code) // we 404 statuses that aren't visible +// Boost a status that's pending approval by us. +func (suite *StatusBoostTestSuite) TestPostBoostImplicitAccept() { +	var ( +		targetStatus = suite.testStatuses["admin_account_status_5"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["local_account_2"] +		user         = suite.testUsers["local_account_2"] +		account      = suite.testAccounts["local_account_2"] +	) + +	out, recorder := suite.postStatusBoost( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) + +	// We should have OK from +	// our call to the function. +	suite.Equal(http.StatusOK, recorder.Code) + +	// Target status should now +	// be "reblogged" by us. +	suite.Equal(`{ +  "account": "yeah this is my account, what about it punk", +  "application": { +    "name": "really cool gts application", +    "website": "https://reallycool.app" +  }, +  "bookmarked": false, +  "card": null, +  "content": "", +  "created_at": "right the hell just now babyee", +  "emojis": [], +  "favourited": false, +  "favourites_count": 0, +  "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +  "in_reply_to_account_id": null, +  "in_reply_to_id": null, +  "interaction_policy": { +    "can_favourite": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reblog": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reply": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    } +  }, +  "language": null, +  "media_attachments": [], +  "mentions": [], +  "muted": false, +  "pinned": false, +  "poll": null, +  "reblog": { +    "account": "yeah this is my account, what about it punk", +    "application": { +      "name": "superseriousbusiness", +      "website": "https://superserious.business" +    }, +    "bookmarked": false, +    "card": null, +    "content": "<p>Hi <span class=\"h-card\"><a href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>1happyturtle</span></a></span>, can I reply?</p>", +    "created_at": "right the hell just now babyee", +    "emojis": [], +    "favourited": false, +    "favourites_count": 0, +    "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +    "in_reply_to_account_id": "01F8MH5NBDF2MV7CTC4Q5128HF", +    "in_reply_to_id": "01F8MHC8VWDRBQR0N1BATDDEM5", +    "interaction_policy": { +      "can_favourite": { +        "always": [ +          "public", +          "me" +        ], +        "with_approval": [] +      }, +      "can_reblog": { +        "always": [ +          "public", +          "me" +        ], +        "with_approval": [] +      }, +      "can_reply": { +        "always": [ +          "public", +          "me" +        ], +        "with_approval": [] +      } +    }, +    "language": null, +    "media_attachments": [], +    "mentions": [ +      { +        "acct": "1happyturtle", +        "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +        "url": "http://localhost:8080/@1happyturtle", +        "username": "1happyturtle" +      } +    ], +    "muted": false, +    "pinned": false, +    "poll": null, +    "reblog": null, +    "reblogged": true, +    "reblogs_count": 1, +    "replies_count": 0, +    "sensitive": false, +    "spoiler_text": "", +    "tags": [], +    "text": "Hi @1happyturtle, can I reply?", +    "uri": "http://localhost:8080/some/determinate/url", +    "url": "http://localhost:8080/some/determinate/url", +    "visibility": "unlisted" +  }, +  "reblogged": true, +  "reblogs_count": 0, +  "replies_count": 0, +  "sensitive": false, +  "spoiler_text": "", +  "tags": [], +  "uri": "http://localhost:8080/some/determinate/url", +  "url": "http://localhost:8080/some/determinate/url", +  "visibility": "unlisted" +}`, out) + +	// Target status should no +	// longer be pending approval. +	dbStatus, err := suite.state.DB.GetStatusByID( +		context.Background(), +		targetStatus.ID, +	) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.False(*dbStatus.PendingApproval) + +	// There should be an Accept +	// stored for the target status. +	intReq, err := suite.state.DB.GetInteractionRequestByInteractionURI( +		context.Background(), targetStatus.URI, +	) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.NotZero(intReq.AcceptedAt) +	suite.NotEmpty(intReq.URI)  }  func TestStatusBoostTestSuite(t *testing.T) { diff --git a/internal/api/client/statuses/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go index d32feb6c7..8598b5ef0 100644 --- a/internal/api/client/statuses/statuscreate_test.go +++ b/internal/api/client/statuses/statuscreate_test.go @@ -20,18 +20,14 @@ package statuses_test  import (  	"bytes"  	"context" -	"encoding/json"  	"fmt" -	"io"  	"net/http"  	"net/http/httptest" -	"strings"  	"testing"  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" -	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -81,91 +77,7 @@ func (suite *StatusCreateTestSuite) postStatus(  	// Trigger handler.  	suite.statusModule.StatusCreatePOSTHandler(ctx) - -	result := recorder.Result() -	defer result.Body.Close() - -	data, err := io.ReadAll(result.Body) -	if err != nil { -		suite.FailNow(err.Error()) -	} - -	rawMap := make(map[string]any) -	if err := json.Unmarshal(data, &rawMap); err != nil { -		suite.FailNow(err.Error()) -	} - -	// Replace any fields from the raw map that -	// aren't determinate (date, id, url, etc). -	if _, ok := rawMap["id"]; ok { -		rawMap["id"] = id.Highest -	} - -	if _, ok := rawMap["uri"]; ok { -		rawMap["uri"] = "http://localhost:8080/some/determinate/url" -	} - -	if _, ok := rawMap["url"]; ok { -		rawMap["url"] = "http://localhost:8080/some/determinate/url" -	} - -	if _, ok := rawMap["created_at"]; ok { -		rawMap["created_at"] = "right the hell just now babyee" -	} - -	// Make ID of any mentions determinate. -	if menchiesRaw, ok := rawMap["mentions"]; ok { -		menchies, ok := menchiesRaw.([]any) -		if !ok { -			suite.FailNow("couldn't coerce menchies") -		} - -		for _, menchieRaw := range menchies { -			menchie, ok := menchieRaw.(map[string]any) -			if !ok { -				suite.FailNow("couldn't coerce menchie") -			} - -			if _, ok := menchie["id"]; ok { -				menchie["id"] = id.Highest -			} -		} -	} - -	// Make fields of any poll determinate. -	if pollRaw, ok := rawMap["poll"]; ok && pollRaw != nil { -		poll, ok := pollRaw.(map[string]any) -		if !ok { -			suite.FailNow("couldn't coerce poll") -		} - -		if _, ok := poll["id"]; ok { -			poll["id"] = id.Highest -		} - -		if _, ok := poll["expires_at"]; ok { -			poll["expires_at"] = "ah like you know whatever dude it's chill" -		} -	} - -	// Replace account since that's not really -	// what we care about for these tests. -	if _, ok := rawMap["account"]; ok { -		rawMap["account"] = "yeah this is my account, what about it punk" -	} - -	// For readability, don't -	// escape HTML, and indent json. -	out := new(bytes.Buffer) -	enc := json.NewEncoder(out) -	enc.SetEscapeHTML(false) -	enc.SetIndent("", "  ") - -	if err := enc.Encode(&rawMap); err != nil { -		suite.FailNow(err.Error()) -	} - -	return strings.TrimSpace(out.String()), recorder +	return suite.parseStatusResponse(recorder)  }  // Post a new status with some custom visibility settings @@ -447,7 +359,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusMessedUpIntPolicy() {  	suite.Equal(http.StatusBadRequest, recorder.Code)  	// We should have a helpful error -  // message telling us how we screwed up. +	// message telling us how we screwed up.  	suite.Equal(`{    "error": "Bad Request: error converting followers_only.can_reply.always: policyURI public is not feasible for visibility followers_only"  }`, out) diff --git a/internal/api/client/statuses/statusfave_test.go b/internal/api/client/statuses/statusfave_test.go index d1042b10e..fdc8741c7 100644 --- a/internal/api/client/statuses/statusfave_test.go +++ b/internal/api/client/statuses/statusfave_test.go @@ -18,20 +18,18 @@  package statuses_test  import ( -	"encoding/json" -	"fmt" -	"io/ioutil" +	"context"  	"net/http"  	"net/http/httptest"  	"strings"  	"testing"  	"github.com/gin-gonic/gin" -	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -40,90 +38,260 @@ type StatusFaveTestSuite struct {  	StatusStandardTestSuite  } -// fave a status -func (suite *StatusFaveTestSuite) TestPostFave() { -	t := suite.testTokens["local_account_1"] -	oauthToken := oauth.DBTokenToToken(t) - -	targetStatus := suite.testStatuses["admin_account_status_2"] - -	// setup +func (suite *StatusFaveTestSuite) postStatusFave( +	targetStatusID string, +	app *gtsmodel.Application, +	token *gtsmodel.Token, +	user *gtsmodel.User, +	account *gtsmodel.Account, +) (string, *httptest.ResponseRecorder) {  	recorder := httptest.NewRecorder()  	ctx, _ := testrig.CreateGinTestContext(recorder, nil) -	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) -	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) -	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) -	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Set(oauth.SessionAuthorizedApplication, app) +	ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(token)) +	ctx.Set(oauth.SessionAuthorizedUser, user) +	ctx.Set(oauth.SessionAuthorizedAccount, account) + +	const pathBase = "http://localhost:8080/api" + statuses.FavouritePath +	path := strings.ReplaceAll(pathBase, ":"+apiutil.IDKey, targetStatusID) +	ctx.Request = httptest.NewRequest(http.MethodPost, path, nil)  	ctx.Request.Header.Set("accept", "application/json") -	// normally the router would populate these params from the path values, -	// but because we're calling the function directly, we need to set them manually. +	// Populate target status ID.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   statuses.IDKey, -			Value: targetStatus.ID, +			Key:   apiutil.IDKey, +			Value: targetStatusID,  		},  	} +	// Trigger handler.  	suite.statusModule.StatusFavePOSTHandler(ctx) +	return suite.parseStatusResponse(recorder) +} -	// check response -	suite.EqualValues(http.StatusOK, recorder.Code) +// Fave a status we haven't faved yet. +func (suite *StatusFaveTestSuite) TestPostFave() { +	var ( +		targetStatus = suite.testStatuses["admin_account_status_2"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["local_account_1"] +		user         = suite.testUsers["local_account_1"] +		account      = suite.testAccounts["local_account_1"] +	) -	result := recorder.Result() -	defer result.Body.Close() -	b, err := ioutil.ReadAll(result.Body) -	assert.NoError(suite.T(), err) +	out, recorder := suite.postStatusFave( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) -	statusReply := &apimodel.Status{} -	err = json.Unmarshal(b, statusReply) -	assert.NoError(suite.T(), err) +	// We should have OK from +	// our call to the function. +	suite.Equal(http.StatusOK, recorder.Code) -	assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText) -	assert.Equal(suite.T(), targetStatus.Content, statusReply.Content) -	assert.True(suite.T(), statusReply.Sensitive) -	assert.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility) -	assert.True(suite.T(), statusReply.Favourited) -	assert.Equal(suite.T(), 1, statusReply.FavouritesCount) +	// Target status should now +	// be "favourited" by us. +	suite.Equal(`{ +  "account": "yeah this is my account, what about it punk", +  "application": { +    "name": "superseriousbusiness", +    "website": "https://superserious.business" +  }, +  "bookmarked": false, +  "card": null, +  "content": "🐕🐕🐕🐕🐕", +  "created_at": "right the hell just now babyee", +  "emojis": [], +  "favourited": true, +  "favourites_count": 1, +  "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +  "in_reply_to_account_id": null, +  "in_reply_to_id": null, +  "interaction_policy": { +    "can_favourite": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reblog": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reply": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    } +  }, +  "language": "en", +  "media_attachments": [], +  "mentions": [], +  "muted": false, +  "pinned": false, +  "poll": null, +  "reblog": null, +  "reblogged": false, +  "reblogs_count": 0, +  "replies_count": 0, +  "sensitive": true, +  "spoiler_text": "open to see some puppies", +  "tags": [], +  "text": "🐕🐕🐕🐕🐕", +  "uri": "http://localhost:8080/some/determinate/url", +  "url": "http://localhost:8080/some/determinate/url", +  "visibility": "public" +}`, out)  } -// try to fave a status that's not faveable +// Try to fave a status +// that's not faveable by us.  func (suite *StatusFaveTestSuite) TestPostUnfaveable() { -	t := suite.testTokens["admin_account"] -	oauthToken := oauth.DBTokenToToken(t) +	var ( +		targetStatus = suite.testStatuses["local_account_1_status_3"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["admin_account"] +		user         = suite.testUsers["admin_account"] +		account      = suite.testAccounts["admin_account"] +	) -	targetStatus := suite.testStatuses["local_account_1_status_3"] // this one is unlikeable +	out, recorder := suite.postStatusFave( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) -	// setup -	recorder := httptest.NewRecorder() -	ctx, _ := testrig.CreateGinTestContext(recorder, nil) -	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) -	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) -	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["admin_account"]) -	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["admin_account"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting -	ctx.Request.Header.Set("accept", "application/json") +	// We should have 403 from +	// our call to the function. +	suite.Equal(http.StatusForbidden, recorder.Code) -	// normally the router would populate these params from the path values, -	// but because we're calling the function directly, we need to set them manually. -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   statuses.IDKey, -			Value: targetStatus.ID, -		}, -	} +	// We should get a helpful error. +	suite.Equal(`{ +  "error": "Forbidden: you do not have permission to fave this status" +}`, out) +} -	suite.statusModule.StatusFavePOSTHandler(ctx) +// Fave a status that's pending approval by us. +func (suite *StatusFaveTestSuite) TestPostFaveImplicitAccept() { +	var ( +		targetStatus = suite.testStatuses["admin_account_status_5"] +		app          = suite.testApplications["application_1"] +		token        = suite.testTokens["local_account_2"] +		user         = suite.testUsers["local_account_2"] +		account      = suite.testAccounts["local_account_2"] +	) -	// check response -	suite.EqualValues(http.StatusForbidden, recorder.Code) +	out, recorder := suite.postStatusFave( +		targetStatus.ID, +		app, +		token, +		user, +		account, +	) -	result := recorder.Result() -	defer result.Body.Close() -	b, err := ioutil.ReadAll(result.Body) -	assert.NoError(suite.T(), err) -	assert.Equal(suite.T(), `{"error":"Forbidden: you do not have permission to fave this status"}`, string(b)) +	// We should have OK from +	// our call to the function. +	suite.Equal(http.StatusOK, recorder.Code) + +	// Target status should now +	// be "favourited" by us. +	suite.Equal(`{ +  "account": "yeah this is my account, what about it punk", +  "application": { +    "name": "superseriousbusiness", +    "website": "https://superserious.business" +  }, +  "bookmarked": false, +  "card": null, +  "content": "<p>Hi <span class=\"h-card\"><a href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>1happyturtle</span></a></span>, can I reply?</p>", +  "created_at": "right the hell just now babyee", +  "emojis": [], +  "favourited": true, +  "favourites_count": 1, +  "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +  "in_reply_to_account_id": "01F8MH5NBDF2MV7CTC4Q5128HF", +  "in_reply_to_id": "01F8MHC8VWDRBQR0N1BATDDEM5", +  "interaction_policy": { +    "can_favourite": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reblog": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    }, +    "can_reply": { +      "always": [ +        "public", +        "me" +      ], +      "with_approval": [] +    } +  }, +  "language": null, +  "media_attachments": [], +  "mentions": [ +    { +      "acct": "1happyturtle", +      "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", +      "url": "http://localhost:8080/@1happyturtle", +      "username": "1happyturtle" +    } +  ], +  "muted": false, +  "pinned": false, +  "poll": null, +  "reblog": null, +  "reblogged": false, +  "reblogs_count": 0, +  "replies_count": 0, +  "sensitive": false, +  "spoiler_text": "", +  "tags": [], +  "text": "Hi @1happyturtle, can I reply?", +  "uri": "http://localhost:8080/some/determinate/url", +  "url": "http://localhost:8080/some/determinate/url", +  "visibility": "unlisted" +}`, out) + +	// Target status should no +	// longer be pending approval. +	dbStatus, err := suite.state.DB.GetStatusByID( +		context.Background(), +		targetStatus.ID, +	) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.False(*dbStatus.PendingApproval) + +	// There should be an Accept +	// stored for the target status. +	intReq, err := suite.state.DB.GetInteractionRequestByInteractionURI( +		context.Background(), targetStatus.URI, +	) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.NotZero(intReq.AcceptedAt) +	suite.NotEmpty(intReq.URI)  }  func TestStatusFaveTestSuite(t *testing.T) { | 
