gotosocial/internal/api/client/statuses/statusmute_test.go
ewwwin 424f62dd70
[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 16:31:52 +00:00

271 lines
8.5 KiB
Go

// 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 (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type StatusMuteTestSuite struct {
StatusStandardTestSuite
}
func (suite *StatusMuteTestSuite) post(path string, handler func(*gin.Context), targetStatusID string) (int, string) {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.DBTokenToToken(t)
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, path, 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: apiutil.IDKey,
Value: targetStatusID,
},
}
handler(ctx)
result := recorder.Result()
defer result.Body.Close()
b, err := io.ReadAll(result.Body)
if err != nil {
suite.FailNow(err.Error())
}
indented := bytes.Buffer{}
if err := json.Indent(&indented, b, "", " "); err != nil {
suite.FailNow(err.Error())
}
return recorder.Code, indented.String()
}
func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
var (
targetStatus = suite.testStatuses["local_account_1_status_1"]
path = fmt.Sprintf("http://localhost:8080/api%s", strings.ReplaceAll(statuses.MutePath, ":id", targetStatus.ID))
)
// Mute the status, ensure `muted` is `true`.
code, muted := suite.post(path, suite.statusModule.StatusMutePOSTHandler, targetStatus.ID)
suite.Equal(http.StatusOK, code)
suite.Equal(`{
"id": "01F8MHAMCHF6Y650WCRSCP4WMY",
"created_at": "2021-10-20T10:40:37.000Z",
"edited_at": null,
"in_reply_to_id": null,
"in_reply_to_account_id": null,
"sensitive": true,
"spoiler_text": "introduction post",
"visibility": "public",
"language": "en",
"uri": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"url": "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"replies_count": 2,
"reblogs_count": 1,
"favourites_count": 1,
"favourited": false,
"reblogged": false,
"muted": true,
"bookmarked": false,
"pinned": false,
"content": "hello everyone!",
"reblog": null,
"application": {
"name": "really cool gts application",
"website": "https://reallycool.app"
},
"account": {
"id": "01F8MH1H7YV1Z7D2C8K2730QBF",
"username": "the_mighty_zork",
"acct": "the_mighty_zork",
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.webp",
"avatar_description": "a green goblin looking nasty",
"avatar_media_id": "01F8MH58A357CV5K7R7TJMSH6S",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.webp",
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
"header_media_id": "01PFPMWK2FF0D9WMHEJHR07C3Q",
"followers_count": 2,
"following_count": 2,
"statuses_count": 9,
"last_status_at": "2024-11-01",
"emojis": [],
"fields": [],
"enable_rss": true,
"group": false
},
"media_attachments": [],
"mentions": [],
"tags": [],
"emojis": [],
"card": null,
"poll": null,
"text": "hello everyone!",
"content_type": "text/plain",
"interaction_policy": {
"can_favourite": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public",
"me"
],
"with_approval": []
}
}
}`, muted)
// Unmute the status, ensure `muted` is `false`.
code, unmuted := suite.post(path, suite.statusModule.StatusUnmutePOSTHandler, targetStatus.ID)
suite.Equal(http.StatusOK, code)
suite.Equal(`{
"id": "01F8MHAMCHF6Y650WCRSCP4WMY",
"created_at": "2021-10-20T10:40:37.000Z",
"edited_at": null,
"in_reply_to_id": null,
"in_reply_to_account_id": null,
"sensitive": true,
"spoiler_text": "introduction post",
"visibility": "public",
"language": "en",
"uri": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"url": "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY",
"replies_count": 2,
"reblogs_count": 1,
"favourites_count": 1,
"favourited": false,
"reblogged": false,
"muted": false,
"bookmarked": false,
"pinned": false,
"content": "hello everyone!",
"reblog": null,
"application": {
"name": "really cool gts application",
"website": "https://reallycool.app"
},
"account": {
"id": "01F8MH1H7YV1Z7D2C8K2730QBF",
"username": "the_mighty_zork",
"acct": "the_mighty_zork",
"display_name": "original zork (he/they)",
"locked": false,
"discoverable": true,
"bot": false,
"created_at": "2022-05-20T11:09:18.000Z",
"note": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"url": "http://localhost:8080/@the_mighty_zork",
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.webp",
"avatar_description": "a green goblin looking nasty",
"avatar_media_id": "01F8MH58A357CV5K7R7TJMSH6S",
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.webp",
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
"header_media_id": "01PFPMWK2FF0D9WMHEJHR07C3Q",
"followers_count": 2,
"following_count": 2,
"statuses_count": 9,
"last_status_at": "2024-11-01",
"emojis": [],
"fields": [],
"enable_rss": true,
"group": false
},
"media_attachments": [],
"mentions": [],
"tags": [],
"emojis": [],
"card": null,
"poll": null,
"text": "hello everyone!",
"content_type": "text/plain",
"interaction_policy": {
"can_favourite": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public",
"me"
],
"with_approval": []
}
}
}`, unmuted)
}
func TestStatusMuteTestSuite(t *testing.T) {
suite.Run(t, new(StatusMuteTestSuite))
}