gotosocial/internal/api/client/statuses/statusfave_test.go

355 lines
9.1 KiB
Go
Raw Normal View History

// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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 statuses_test
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"code.superseriousbusiness.org/gotosocial/internal/api/client/statuses"
"code.superseriousbusiness.org/gotosocial/internal/filter/visibility"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
"code.superseriousbusiness.org/gotosocial/internal/oauth"
"code.superseriousbusiness.org/gotosocial/testrig"
)
type StatusFaveTestSuite struct {
StatusStandardTestSuite
}
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, 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")
// Populate target status ID.
ctx.Params = gin.Params{
gin.Param{
Key: apiutil.IDKey,
Value: targetStatusID,
},
}
// Trigger handler.
suite.statusModule.StatusFavePOSTHandler(ctx)
return suite.parseStatusResponse(recorder)
}
// 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"]
)
out, recorder := suite.postStatusFave(
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 "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>🐕🐕🐕🐕🐕</p>",
[bugfix] Store and expose status content type (#3870) * Add ContentType to internal models * Add ContentType to API models StatusSource and StatusEdit * Add helpers to convert between API/internal StatusContentType * Write status content type on create/edit * Add migration * Update API docs go run github.com/go-swagger/go-swagger/cmd/swagger generate spec --scan-models --exclude-deps --output docs/api/swagger.yaml * ensure ContentType is updated anywhere Text is * Update docs, take care of TODOs * Set ContentType in more places where Text is set * We don't actually use ContentType on the API status model * Update StatusSource test * Remove unused helper function I copied * Revert change to StatusContentType swagger annotation I'm going to include this in a follow-on PR instead. * Add test for updating content type in edits * Return a value from processContentType instead of modifying the existing status Fixes an issue that was caught by the test I just added - the recorded edit would be marked with the *new* content type instead of the old one, which is obviously bad * Add test for handling of statuses with no stored content type * repurpose an existing test status instead of adding a new one to avoid breaking other tests * Add test to ensure newly created statuses always have content type saved * Do include content type on status API model actually This is mostly important when deleting and redrafting. The comment on `apimodel.Status.Text` implies that it's not sent except in response to status deletion, but actually this doesn't seem to be the case; it also appears to be present in responses to creations and normal fetches and stuff. So I'm treating `ContentType` the same here. * Update new tests to check content type on API statuses * Check content type of API statuses in all tests where text is checked * update other api tests with status content type field * Add test ensuring text and content type are returned when deleting a status * Convert processContentType to free function and remove unused parameter * check for the correct value in the deletion test * Be explicit about this test status having an empty content type * Use omitempty consistently on API models * clean up the final diff a bit * one more swagger regen for the road * Handle nil statuses in processContentType * Don't pass processContentType the entire edit form, it doesn't need it * Move processContentType to common.go and use for creation as well * Remove unused parameters to ContentTypeToAPIContentType
2025-03-06 11:31:52 -05:00
"content_type": "text/plain",
"created_at": "right the hell just now babyee",
[feature] add support for receiving federated status edits (#3597) * add support for extracting Updated field from Statusable implementers * add support for status edits in the database, and update status dereferencer to handle them * remove unused AdditionalInfo{}.CreatedAt * remove unused AdditionalEmojiInfo{}.CreatedAt * update new mention creation to use status.UpdatedAt * remove mention.UpdatedAt, fixes related to NewULIDFromTime() change * add migration to remove Mention{}.UpdatedAt field * add migration to add the StatusEdit{} table * start adding tests, add delete function for status edits * add more of status edit migrations, fill in more of the necessary edit delete functionality * remove unused function * allow generating gotosocial compatible ulid via CLI with `go run ./cmd/gen-ulid` * add StatusEdit{} test models * fix new statusedits sql * use model instead of table name * actually remove the Mention.UpdatedAt field... * fix tests now new models are added, add more status edit DB tests * fix panic wording * add test for deleting status edits * don't automatically set `updated_at` field on updated statuses * flesh out more of the dereferencer status edit tests, ensure updated at field set on outgoing AS statuses * remove media_attachments.updated_at column * fix up more tests, further complete the dereferencer status edit tests * update more status serialization tests not expecting 'updated' AS property * gah!! json serialization tests!! * undo some gtscontext wrapping changes * more serialization test fixing :smiling_face_with_tear: * more test fixing, ensure the edit.status_id field is actually set :facepalm: * fix status edit test * grrr linter * add edited_at field to apimodel status * remove the choice of paging on the timeline public filtered test (otherwise it needs updating every time you add statuses ...) * ensure that status.updated_at always fits chronologically * fix more serialization tests ... * add more code comments * fix envparsing * update swagger file * properly handle media description changes during status edits * slight formatting tweak * code comment
2024-12-05 13:35:07 +00:00
"edited_at": null,
"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"
],
"automatic_approval": [
"public",
"me"
],
"manual_approval": [],
"with_approval": []
},
"can_reblog": {
"always": [
"public",
"me"
],
"automatic_approval": [
"public",
"me"
],
"manual_approval": [],
"with_approval": []
},
"can_reply": {
"always": [
"public",
"me"
],
"automatic_approval": [
"public",
"me"
],
"manual_approval": [],
"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 by us.
func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
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"]
)
out, recorder := suite.postStatusFave(
targetStatus.ID,
app,
token,
user,
account,
)
// We should have 403 from
// our call to the function.
suite.Equal(http.StatusForbidden, recorder.Code)
// We should get a helpful error.
suite.Equal(`{
"error": "Forbidden: you do not have permission to fave this status"
}`, out)
}
// Fave a status that's pending approval by us.
func (suite *StatusFaveTestSuite) TestPostFaveImplicitAccept() {
var (
ctx = suite.T().Context()
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"]
visFilter = visibility.NewFilter(&suite.state)
)
// Check visibility of status to public before posting fave.
visible, err := visFilter.StatusVisible(ctx, nil, targetStatus)
if err != nil {
suite.FailNow(err.Error())
}
if visible {
suite.FailNow("status should not be visible yet")
}
out, recorder := suite.postStatusFave(
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 "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>",
[bugfix] Store and expose status content type (#3870) * Add ContentType to internal models * Add ContentType to API models StatusSource and StatusEdit * Add helpers to convert between API/internal StatusContentType * Write status content type on create/edit * Add migration * Update API docs go run github.com/go-swagger/go-swagger/cmd/swagger generate spec --scan-models --exclude-deps --output docs/api/swagger.yaml * ensure ContentType is updated anywhere Text is * Update docs, take care of TODOs * Set ContentType in more places where Text is set * We don't actually use ContentType on the API status model * Update StatusSource test * Remove unused helper function I copied * Revert change to StatusContentType swagger annotation I'm going to include this in a follow-on PR instead. * Add test for updating content type in edits * Return a value from processContentType instead of modifying the existing status Fixes an issue that was caught by the test I just added - the recorded edit would be marked with the *new* content type instead of the old one, which is obviously bad * Add test for handling of statuses with no stored content type * repurpose an existing test status instead of adding a new one to avoid breaking other tests * Add test to ensure newly created statuses always have content type saved * Do include content type on status API model actually This is mostly important when deleting and redrafting. The comment on `apimodel.Status.Text` implies that it's not sent except in response to status deletion, but actually this doesn't seem to be the case; it also appears to be present in responses to creations and normal fetches and stuff. So I'm treating `ContentType` the same here. * Update new tests to check content type on API statuses * Check content type of API statuses in all tests where text is checked * update other api tests with status content type field * Add test ensuring text and content type are returned when deleting a status * Convert processContentType to free function and remove unused parameter * check for the correct value in the deletion test * Be explicit about this test status having an empty content type * Use omitempty consistently on API models * clean up the final diff a bit * one more swagger regen for the road * Handle nil statuses in processContentType * Don't pass processContentType the entire edit form, it doesn't need it * Move processContentType to common.go and use for creation as well * Remove unused parameters to ContentTypeToAPIContentType
2025-03-06 11:31:52 -05:00
"content_type": "text/markdown",
"created_at": "right the hell just now babyee",
[feature] add support for receiving federated status edits (#3597) * add support for extracting Updated field from Statusable implementers * add support for status edits in the database, and update status dereferencer to handle them * remove unused AdditionalInfo{}.CreatedAt * remove unused AdditionalEmojiInfo{}.CreatedAt * update new mention creation to use status.UpdatedAt * remove mention.UpdatedAt, fixes related to NewULIDFromTime() change * add migration to remove Mention{}.UpdatedAt field * add migration to add the StatusEdit{} table * start adding tests, add delete function for status edits * add more of status edit migrations, fill in more of the necessary edit delete functionality * remove unused function * allow generating gotosocial compatible ulid via CLI with `go run ./cmd/gen-ulid` * add StatusEdit{} test models * fix new statusedits sql * use model instead of table name * actually remove the Mention.UpdatedAt field... * fix tests now new models are added, add more status edit DB tests * fix panic wording * add test for deleting status edits * don't automatically set `updated_at` field on updated statuses * flesh out more of the dereferencer status edit tests, ensure updated at field set on outgoing AS statuses * remove media_attachments.updated_at column * fix up more tests, further complete the dereferencer status edit tests * update more status serialization tests not expecting 'updated' AS property * gah!! json serialization tests!! * undo some gtscontext wrapping changes * more serialization test fixing :smiling_face_with_tear: * more test fixing, ensure the edit.status_id field is actually set :facepalm: * fix status edit test * grrr linter * add edited_at field to apimodel status * remove the choice of paging on the timeline public filtered test (otherwise it needs updating every time you add statuses ...) * ensure that status.updated_at always fits chronologically * fix more serialization tests ... * add more code comments * fix envparsing * update swagger file * properly handle media description changes during status edits * slight formatting tweak * code comment
2024-12-05 13:35:07 +00:00
"edited_at": null,
"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"
],
"automatic_approval": [
"public",
"me"
],
"manual_approval": [],
"with_approval": []
},
"can_reblog": {
"always": [
"public",
"me"
],
"automatic_approval": [
"public",
"me"
],
"manual_approval": [],
"with_approval": []
},
"can_reply": {
"always": [
"public",
"me"
],
"automatic_approval": [
"public",
"me"
],
"manual_approval": [],
"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": "public"
}`, out)
// Target status should no
// longer be pending approval.
dbStatus, err := suite.state.DB.GetStatusByID(
ctx,
targetStatus.ID,
)
if err != nil {
suite.FailNow(err.Error())
}
suite.False(*dbStatus.PendingApproval)
suite.NotEmpty(dbStatus.ApprovedByURI)
// There should be an Accept
// stored for the target status.
intReq, err := suite.state.DB.GetInteractionRequestByInteractionURI(
ctx, targetStatus.URI,
)
if err != nil {
suite.FailNow(err.Error())
}
suite.NotZero(intReq.AcceptedAt)
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394) ~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
suite.NotEmpty(intReq.InteractionURI)
// Check visibility of status to public after posting fave.
visible, err = visFilter.StatusVisible(ctx, nil, dbStatus)
if err != nil {
suite.FailNow(err.Error())
}
if !visible {
suite.FailNow("status should be visible")
}
}
func TestStatusFaveTestSuite(t *testing.T) {
suite.Run(t, new(StatusFaveTestSuite))
}