mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-11-29 15:50:27 +00:00
[chore/bugfix/horror] Allow expires_in
and poll choices to be parsed from strings (#2346)
This commit is contained in:
parent
7ce3a1e6f3
commit
c7ecab9e6f
14 changed files with 579 additions and 194 deletions
|
@ -58,11 +58,11 @@ func (suite *TokenTestSuite) TestRetrieveClientCredentialsOK() {
|
|||
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": testClient.ID,
|
||||
"client_secret": testClient.Secret,
|
||||
"redirect_uri": "http://localhost:8080",
|
||||
map[string][]string{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {testClient.ID},
|
||||
"client_secret": {testClient.Secret},
|
||||
"redirect_uri": {"http://localhost:8080"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -104,12 +104,12 @@ func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeOK() {
|
|||
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": testClient.ID,
|
||||
"client_secret": testClient.Secret,
|
||||
"redirect_uri": "http://localhost:8080",
|
||||
"code": testUserAuthorizationToken.Code,
|
||||
map[string][]string{
|
||||
"grant_type": {"authorization_code"},
|
||||
"client_id": {testClient.ID},
|
||||
"client_secret": {testClient.Secret},
|
||||
"redirect_uri": {"http://localhost:8080"},
|
||||
"code": {testUserAuthorizationToken.Code},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -149,11 +149,11 @@ func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeNoCode() {
|
|||
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": testClient.ID,
|
||||
"client_secret": testClient.Secret,
|
||||
"redirect_uri": "http://localhost:8080",
|
||||
map[string][]string{
|
||||
"grant_type": {"authorization_code"},
|
||||
"client_id": {testClient.ID},
|
||||
"client_secret": {testClient.Secret},
|
||||
"redirect_uri": {"http://localhost:8080"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -181,12 +181,12 @@ func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeWrongGrantType() {
|
|||
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": testClient.ID,
|
||||
"client_secret": testClient.Secret,
|
||||
"redirect_uri": "http://localhost:8080",
|
||||
"code": "peepeepoopoo",
|
||||
map[string][]string{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {testClient.ID},
|
||||
"client_secret": {testClient.Secret},
|
||||
"redirect_uri": {"http://localhost:8080"},
|
||||
"code": {"peepeepoopoo"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -36,8 +36,8 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandler() {
|
|||
// we're deleting zork
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"password": "password",
|
||||
map[string][]string{
|
||||
"password": {"password"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -58,8 +58,8 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerWrongPassword()
|
|||
// we're deleting zork
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"password": "aaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
map[string][]string{
|
||||
"password": {"aaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -80,7 +80,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerNoPassword() {
|
|||
// we're deleting zork
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{})
|
||||
map[string][]string{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -38,15 +38,19 @@ type AccountUpdateTestSuite struct {
|
|||
AccountStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) updateAccountFromForm(data map[string]string, expectedHTTPStatus int, expectedBody string) (*apimodel.Account, error) {
|
||||
func (suite *AccountUpdateTestSuite) updateAccountFromForm(data map[string][]string, expectedHTTPStatus int, expectedBody string) (*apimodel.Account, error) {
|
||||
form := url.Values{}
|
||||
for key, val := range data {
|
||||
form[key] = []string{val}
|
||||
if form.Has(key) {
|
||||
form[key] = append(form[key], val...)
|
||||
} else {
|
||||
form[key] = val
|
||||
}
|
||||
}
|
||||
return suite.updateAccount([]byte(form.Encode()), "application/x-www-form-urlencoded", expectedHTTPStatus, expectedBody)
|
||||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) updateAccountFromFormData(data map[string]string, expectedHTTPStatus int, expectedBody string) (*apimodel.Account, error) {
|
||||
func (suite *AccountUpdateTestSuite) updateAccountFromFormData(data map[string][]string, expectedHTTPStatus int, expectedBody string) (*apimodel.Account, error) {
|
||||
requestBody, w, err := testrig.CreateMultipartFormData("", "", data)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -55,7 +59,7 @@ func (suite *AccountUpdateTestSuite) updateAccountFromFormData(data map[string]s
|
|||
return suite.updateAccount(requestBody.Bytes(), w.FormDataContentType(), expectedHTTPStatus, expectedBody)
|
||||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) updateAccountFromFormDataWithFile(fieldName string, fileName string, data map[string]string, expectedHTTPStatus int, expectedBody string) (*apimodel.Account, error) {
|
||||
func (suite *AccountUpdateTestSuite) updateAccountFromFormDataWithFile(fieldName string, fileName string, data map[string][]string, expectedHTTPStatus int, expectedBody string) (*apimodel.Account, error) {
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(fieldName, fileName, data)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -116,12 +120,12 @@ func (suite *AccountUpdateTestSuite) updateAccount(
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicForm() {
|
||||
data := map[string]string{
|
||||
"note": "this is my new bio read it and weep",
|
||||
"fields_attributes[0][name]": "pronouns",
|
||||
"fields_attributes[0][value]": "they/them",
|
||||
"fields_attributes[1][name]": "Website",
|
||||
"fields_attributes[1][value]": "https://example.com",
|
||||
data := map[string][]string{
|
||||
"note": {"this is my new bio read it and weep"},
|
||||
"fields_attributes[0][name]": {"pronouns"},
|
||||
"fields_attributes[0][value]": {"they/them"},
|
||||
"fields_attributes[1][name]": {"Website"},
|
||||
"fields_attributes[1][value]": {"https://example.com"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "")
|
||||
|
@ -142,12 +146,12 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicForm() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicFormData() {
|
||||
data := map[string]string{
|
||||
"note": "this is my new bio read it and weep",
|
||||
"fields_attributes[0][name]": "pronouns",
|
||||
"fields_attributes[0][value]": "they/them",
|
||||
"fields_attributes[1][name]": "Website",
|
||||
"fields_attributes[1][value]": "https://example.com",
|
||||
data := map[string][]string{
|
||||
"note": {"this is my new bio read it and weep"},
|
||||
"fields_attributes[0][name]": {"pronouns"},
|
||||
"fields_attributes[0][value]": {"they/them"},
|
||||
"fields_attributes[1][name]": {"Website"},
|
||||
"fields_attributes[1][value]": {"https://example.com"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
|
||||
|
@ -202,8 +206,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicJSON() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountLockForm() {
|
||||
data := map[string]string{
|
||||
"locked": "true",
|
||||
data := map[string][]string{
|
||||
"locked": {"true"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "")
|
||||
|
@ -215,8 +219,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountLockForm() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountLockFormData() {
|
||||
data := map[string]string{
|
||||
"locked": "true",
|
||||
data := map[string][]string{
|
||||
"locked": {"true"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
|
||||
|
@ -242,8 +246,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountLockJSON() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockForm() {
|
||||
data := map[string]string{
|
||||
"locked": "false",
|
||||
data := map[string][]string{
|
||||
"locked": {"false"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "")
|
||||
|
@ -255,8 +259,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockForm() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockFormData() {
|
||||
data := map[string]string{
|
||||
"locked": "false",
|
||||
data := map[string][]string{
|
||||
"locked": {"false"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
|
||||
|
@ -289,8 +293,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountCache() {
|
|||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
data := map[string]string{
|
||||
"note": "this is my new bio read it and weep",
|
||||
data := map[string][]string{
|
||||
"note": {"this is my new bio read it and weep"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
|
||||
|
@ -302,8 +306,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountCache() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableForm() {
|
||||
data := map[string]string{
|
||||
"discoverable": "false",
|
||||
data := map[string][]string{
|
||||
"discoverable": {"false"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "")
|
||||
|
@ -320,8 +324,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableForm() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableFormData() {
|
||||
data := map[string]string{
|
||||
"discoverable": "false",
|
||||
data := map[string][]string{
|
||||
"discoverable": {"false"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
|
||||
|
@ -357,10 +361,10 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableJSON() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountWithImageFormData() {
|
||||
data := map[string]string{
|
||||
"display_name": "updated zork display name!!!",
|
||||
"note": "",
|
||||
"locked": "true",
|
||||
data := map[string][]string{
|
||||
"display_name": {"updated zork display name!!!"},
|
||||
"note": {""},
|
||||
"locked": {"true"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormDataWithFile("header", "../../../../testrig/media/test-jpeg.jpg", data, http.StatusOK, "")
|
||||
|
@ -368,7 +372,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountWithImageFormData() {
|
|||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(data["display_name"], apimodelAccount.DisplayName)
|
||||
suite.Equal(data["display_name"][0], apimodelAccount.DisplayName)
|
||||
suite.True(apimodelAccount.Locked)
|
||||
suite.Empty(apimodelAccount.Note)
|
||||
suite.Empty(apimodelAccount.Source.Note)
|
||||
|
@ -382,7 +386,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountWithImageFormData() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyForm() {
|
||||
data := make(map[string]string)
|
||||
data := make(map[string][]string)
|
||||
|
||||
_, err := suite.updateAccountFromForm(data, http.StatusBadRequest, `{"error":"Bad Request: empty form submitted"}`)
|
||||
if err != nil {
|
||||
|
@ -391,7 +395,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyForm() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyFormData() {
|
||||
data := make(map[string]string)
|
||||
data := make(map[string][]string)
|
||||
|
||||
_, err := suite.updateAccountFromFormData(data, http.StatusBadRequest, `{"error":"Bad Request: empty form submitted"}`)
|
||||
if err != nil {
|
||||
|
@ -400,11 +404,11 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyFormData() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceForm() {
|
||||
data := map[string]string{
|
||||
"source[privacy]": string(apimodel.VisibilityPrivate),
|
||||
"source[language]": "de",
|
||||
"source[sensitive]": "true",
|
||||
"locked": "true",
|
||||
data := map[string][]string{
|
||||
"source[privacy]": {string(apimodel.VisibilityPrivate)},
|
||||
"source[language]": {"de"},
|
||||
"source[sensitive]": {"true"},
|
||||
"locked": {"true"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "")
|
||||
|
@ -412,18 +416,18 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceForm() {
|
|||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(data["source[language]"], apimodelAccount.Source.Language)
|
||||
suite.Equal(data["source[language]"][0], apimodelAccount.Source.Language)
|
||||
suite.EqualValues(apimodel.VisibilityPrivate, apimodelAccount.Source.Privacy)
|
||||
suite.True(apimodelAccount.Source.Sensitive)
|
||||
suite.True(apimodelAccount.Locked)
|
||||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceFormData() {
|
||||
data := map[string]string{
|
||||
"source[privacy]": string(apimodel.VisibilityPrivate),
|
||||
"source[language]": "de",
|
||||
"source[sensitive]": "true",
|
||||
"locked": "true",
|
||||
data := map[string][]string{
|
||||
"source[privacy]": {string(apimodel.VisibilityPrivate)},
|
||||
"source[language]": {"de"},
|
||||
"source[sensitive]": {"true"},
|
||||
"locked": {"true"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
|
||||
|
@ -431,7 +435,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceFormData() {
|
|||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(data["source[language]"], apimodelAccount.Source.Language)
|
||||
suite.Equal(data["source[language]"][0], apimodelAccount.Source.Language)
|
||||
suite.EqualValues(apimodel.VisibilityPrivate, apimodelAccount.Source.Privacy)
|
||||
suite.True(apimodelAccount.Source.Sensitive)
|
||||
suite.True(apimodelAccount.Locked)
|
||||
|
@ -461,8 +465,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceJSON() {
|
|||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormData() {
|
||||
data := map[string]string{
|
||||
"source[status_content_type]": "text/markdown",
|
||||
data := map[string][]string{
|
||||
"source[status_content_type]": {"text/markdown"},
|
||||
}
|
||||
|
||||
apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "")
|
||||
|
@ -470,19 +474,19 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormDa
|
|||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(data["source[status_content_type]"], apimodelAccount.Source.StatusContentType)
|
||||
suite.Equal(data["source[status_content_type]"][0], apimodelAccount.Source.StatusContentType)
|
||||
|
||||
// Check the account in the database too.
|
||||
dbAccount, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.Equal(data["source[status_content_type]"], dbAccount.StatusContentType)
|
||||
suite.Equal(data["source[status_content_type]"][0], dbAccount.StatusContentType)
|
||||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeBad() {
|
||||
data := map[string]string{
|
||||
"source[status_content_type]": "peepeepoopoo",
|
||||
data := map[string][]string{
|
||||
"source[status_content_type]": {"peepeepoopoo"},
|
||||
}
|
||||
|
||||
_, err := suite.updateAccountFromFormData(data, http.StatusBadRequest, `{"error":"Bad Request: status content type 'peepeepoopoo' was not recognized, valid options are 'text/plain', 'text/markdown'"}`)
|
||||
|
|
|
@ -39,9 +39,9 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreateNewCategory() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"image", "../../../../testrig/media/rainbow-original.png",
|
||||
map[string]string{
|
||||
"shortcode": "new_emoji",
|
||||
"category": "Test Emojis", // this category doesn't exist yet
|
||||
map[string][]string{
|
||||
"shortcode": {"new_emoji"},
|
||||
"category": {"Test Emojis"}, // this category doesn't exist yet
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -112,9 +112,9 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreateExistingCategory() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"image", "../../../../testrig/media/rainbow-original.png",
|
||||
map[string]string{
|
||||
"shortcode": "new_emoji",
|
||||
"category": "cute stuff", // this category already exists
|
||||
map[string][]string{
|
||||
"shortcode": {"new_emoji"},
|
||||
"category": {"cute stuff"}, // this category already exists
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -185,9 +185,9 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreateNoCategory() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"image", "../../../../testrig/media/rainbow-original.png",
|
||||
map[string]string{
|
||||
"shortcode": "new_emoji",
|
||||
"category": "",
|
||||
map[string][]string{
|
||||
"shortcode": {"new_emoji"},
|
||||
"category": {""},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -258,8 +258,8 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreateAlreadyExists() {
|
|||
// set up the request -- use a shortcode that already exists for an emoji in the database
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"image", "../../../../testrig/media/rainbow-original.png",
|
||||
map[string]string{
|
||||
"shortcode": "rainbow",
|
||||
map[string][]string{
|
||||
"shortcode": {"rainbow"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -43,9 +43,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateNewCategory() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"category": "New Category", // this category doesn't exist yet
|
||||
"type": "modify",
|
||||
map[string][]string{
|
||||
"category": {"New Category"}, // this category doesn't exist yet
|
||||
"type": {"modify"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -120,9 +120,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateSwitchCategory() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "modify",
|
||||
"category": "cute stuff",
|
||||
map[string][]string{
|
||||
"type": {"modify"},
|
||||
"category": {"cute stuff"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -197,10 +197,10 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyRemoteToLocal() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "copy",
|
||||
"category": "emojis i stole",
|
||||
"shortcode": "yell",
|
||||
map[string][]string{
|
||||
"type": {"copy"},
|
||||
"category": {"emojis i stole"},
|
||||
"shortcode": {"yell"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -275,8 +275,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableEmoji() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "disable",
|
||||
map[string][]string{
|
||||
"type": {"disable"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -316,8 +316,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableLocalEmoji() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "disable",
|
||||
map[string][]string{
|
||||
"type": {"disable"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -349,8 +349,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyRemoteEmoji() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"image", "../../../../testrig/media/kip-original.gif",
|
||||
map[string]string{
|
||||
"type": "modify",
|
||||
map[string][]string{
|
||||
"type": {"modify"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -382,8 +382,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyNoParams() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "modify",
|
||||
map[string][]string{
|
||||
"type": {"modify"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -415,9 +415,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyLocalToLocal() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "copy",
|
||||
"shortcode": "bottoms",
|
||||
map[string][]string{
|
||||
"type": {"copy"},
|
||||
"shortcode": {"bottoms"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -449,9 +449,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyEmptyShortcode() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "copy",
|
||||
"shortcode": "",
|
||||
map[string][]string{
|
||||
"type": {"copy"},
|
||||
"shortcode": {""},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -483,8 +483,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyNoShortcode() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "copy",
|
||||
map[string][]string{
|
||||
"type": {"copy"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -516,9 +516,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyShortcodeAlreadyInUse() {
|
|||
// set up the request
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"type": "copy",
|
||||
"shortcode": "rainbow",
|
||||
map[string][]string{
|
||||
"type": {"copy"},
|
||||
"shortcode": {"rainbow"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -36,7 +36,7 @@ type InstancePatchTestSuite struct {
|
|||
InstanceStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) instancePatch(fieldName string, fileName string, extraFields map[string]string) (code int, body []byte) {
|
||||
func (suite *InstancePatchTestSuite) instancePatch(fieldName string, fileName string, extraFields map[string][]string) (code int, body []byte) {
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(fieldName, fileName, extraFields)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -59,10 +59,10 @@ func (suite *InstancePatchTestSuite) instancePatch(fieldName string, fileName st
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch1() {
|
||||
code, b := suite.instancePatch("", "", map[string]string{
|
||||
"title": "Example Instance",
|
||||
"contact_username": "admin",
|
||||
"contact_email": "someone@example.org",
|
||||
code, b := suite.instancePatch("", "", map[string][]string{
|
||||
"title": {"Example Instance"},
|
||||
"contact_username": {"admin"},
|
||||
"contact_email": {"someone@example.org"},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusOK; code != expectedCode {
|
||||
|
@ -175,8 +175,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch2() {
|
||||
code, b := suite.instancePatch("", "", map[string]string{
|
||||
"title": "<p>Geoff's Instance</p>",
|
||||
code, b := suite.instancePatch("", "", map[string][]string{
|
||||
"title": {"<p>Geoff's Instance</p>"},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusOK; code != expectedCode {
|
||||
|
@ -289,8 +289,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch3() {
|
||||
code, b := suite.instancePatch("", "", map[string]string{
|
||||
"short_description": "<p>This is some html, which is <em>allowed</em> in short descriptions.</p>",
|
||||
code, b := suite.instancePatch("", "", map[string][]string{
|
||||
"short_description": {"<p>This is some html, which is <em>allowed</em> in short descriptions.</p>"},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusOK; code != expectedCode {
|
||||
|
@ -403,8 +403,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch4() {
|
||||
code, b := suite.instancePatch("", "", map[string]string{
|
||||
"": "",
|
||||
code, b := suite.instancePatch("", "", map[string][]string{
|
||||
"": {""},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusBadRequest; code != expectedCode {
|
||||
|
@ -422,8 +422,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch4() {
|
|||
func (suite *InstancePatchTestSuite) TestInstancePatch5() {
|
||||
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||
"", "",
|
||||
map[string]string{
|
||||
"short_description": "<p>This is some html, which is <em>allowed</em> in short descriptions.</p>",
|
||||
map[string][]string{
|
||||
"short_description": {"<p>This is some html, which is <em>allowed</em> in short descriptions.</p>"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -454,8 +454,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch5() {
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch6() {
|
||||
code, b := suite.instancePatch("", "", map[string]string{
|
||||
"contact_email": "",
|
||||
code, b := suite.instancePatch("", "", map[string][]string{
|
||||
"contact_email": {""},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusOK; code != expectedCode {
|
||||
|
@ -568,8 +568,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch7() {
|
||||
code, b := suite.instancePatch("", "", map[string]string{
|
||||
"contact_email": "not.an.email.address",
|
||||
code, b := suite.instancePatch("", "", map[string][]string{
|
||||
"contact_email": {"not.an.email.address"},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusBadRequest; code != expectedCode {
|
||||
|
@ -585,8 +585,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch7() {
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch8() {
|
||||
code, b := suite.instancePatch("thumbnail", "../../../../testrig/media/peglin.gif", map[string]string{
|
||||
"thumbnail_description": "A bouncing little green peglin.",
|
||||
code, b := suite.instancePatch("thumbnail", "../../../../testrig/media/peglin.gif", map[string][]string{
|
||||
"thumbnail_description": {"A bouncing little green peglin."},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusOK; code != expectedCode {
|
||||
|
@ -723,8 +723,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
|
|||
}`, string(instanceV2ThumbnailJson))
|
||||
|
||||
// double extra special bonus: now update the image description without changing the image
|
||||
code2, b2 := suite.instancePatch("", "", map[string]string{
|
||||
"thumbnail_description": "updating the thumbnail description without changing anything else!",
|
||||
code2, b2 := suite.instancePatch("", "", map[string][]string{
|
||||
"thumbnail_description": {"updating the thumbnail description without changing anything else!"},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusOK; code2 != expectedCode {
|
||||
|
@ -741,8 +741,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
|
|||
}
|
||||
|
||||
func (suite *InstancePatchTestSuite) TestInstancePatch9() {
|
||||
code, b := suite.instancePatch("", "", map[string]string{
|
||||
"thumbnail_description": "setting a new description without having a custom image set; this should change nothing!",
|
||||
code, b := suite.instancePatch("", "", map[string][]string{
|
||||
"thumbnail_description": {"setting a new description without having a custom image set; this should change nothing!"},
|
||||
})
|
||||
|
||||
if expectedCode := http.StatusOK; code != expectedCode {
|
||||
|
|
|
@ -160,9 +160,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {
|
|||
}
|
||||
|
||||
// create the request
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
|
||||
"description": "this is a test image -- a cool background from somewhere",
|
||||
"focus": "-0.5,0.5",
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string][]string{
|
||||
"description": {"this is a test image -- a cool background from somewhere"},
|
||||
"focus": {"-0.5,0.5"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -245,9 +245,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() {
|
|||
}
|
||||
|
||||
// create the request
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
|
||||
"description": "this is a test image -- a cool background from somewhere",
|
||||
"focus": "-0.5,0.5",
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string][]string{
|
||||
"description": {"this is a test image -- a cool background from somewhere"},
|
||||
"focus": {"-0.5,0.5"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -328,9 +328,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() {
|
|||
description := base64.RawStdEncoding.EncodeToString(descriptionBytes)
|
||||
|
||||
// create the request
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
|
||||
"description": description,
|
||||
"focus": "-0.5,0.5",
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string][]string{
|
||||
"description": {description},
|
||||
"focus": {"-0.5,0.5"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -369,9 +369,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() {
|
|||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
|
||||
// create the request
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
|
||||
"description": "", // provide an empty description
|
||||
"focus": "-0.5,0.5",
|
||||
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string][]string{
|
||||
"description": {""}, // provide an empty description
|
||||
"focus": {"-0.5,0.5"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -149,10 +149,10 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() {
|
|||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
|
||||
// create the request
|
||||
buf, w, err := testrig.CreateMultipartFormData("", "", map[string]string{
|
||||
"id": toUpdate.ID,
|
||||
"description": "new description!",
|
||||
"focus": "-0.1,0.3",
|
||||
buf, w, err := testrig.CreateMultipartFormData("", "", map[string][]string{
|
||||
"id": {toUpdate.ID},
|
||||
"description": {"new description!"},
|
||||
"focus": {"-0.1,0.3"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -210,10 +210,10 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() {
|
|||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
|
||||
// create the request
|
||||
buf, w, err := testrig.CreateMultipartFormData("", "", map[string]string{
|
||||
"id": toUpdate.ID,
|
||||
"description": "new description!",
|
||||
"focus": "-0.1,0.3",
|
||||
buf, w, err := testrig.CreateMultipartFormData("", "", map[string][]string{
|
||||
"id": {toUpdate.ID},
|
||||
"description": {"new description!"},
|
||||
"focus": {"-0.1,0.3"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
102
internal/api/client/polls/polls_test.go
Normal file
102
internal/api/client/polls/polls_test.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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 polls_test
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/polls"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type PollsStandardTestSuite struct {
|
||||
suite.Suite
|
||||
db db.DB
|
||||
storage *storage.Driver
|
||||
mediaManager *media.Manager
|
||||
federator *federation.Federator
|
||||
processor *processing.Processor
|
||||
emailSender email.Sender
|
||||
sentEmails map[string]string
|
||||
state state.State
|
||||
|
||||
// standard suite models
|
||||
testTokens map[string]*gtsmodel.Token
|
||||
testClients map[string]*gtsmodel.Client
|
||||
testApplications map[string]*gtsmodel.Application
|
||||
testUsers map[string]*gtsmodel.User
|
||||
testAccounts map[string]*gtsmodel.Account
|
||||
testStatuses map[string]*gtsmodel.Status
|
||||
testPolls map[string]*gtsmodel.Poll
|
||||
|
||||
// module being tested
|
||||
pollsModule *polls.Module
|
||||
}
|
||||
|
||||
func (suite *PollsStandardTestSuite) SetupSuite() {
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testClients = testrig.NewTestClients()
|
||||
suite.testApplications = testrig.NewTestApplications()
|
||||
suite.testUsers = testrig.NewTestUsers()
|
||||
suite.testAccounts = testrig.NewTestAccounts()
|
||||
suite.testStatuses = testrig.NewTestStatuses()
|
||||
suite.testPolls = testrig.NewTestPolls()
|
||||
}
|
||||
|
||||
func (suite *PollsStandardTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
testrig.StartWorkers(&suite.state)
|
||||
|
||||
testrig.InitTestConfig()
|
||||
testrig.InitTestLog()
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
suite.state.DB = suite.db
|
||||
suite.storage = testrig.NewInMemoryStorage()
|
||||
suite.state.Storage = suite.storage
|
||||
|
||||
testrig.StartTimelines(
|
||||
&suite.state,
|
||||
visibility.NewFilter(&suite.state),
|
||||
typeutils.NewConverter(&suite.state),
|
||||
)
|
||||
|
||||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.pollsModule = polls.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
}
|
||||
|
||||
func (suite *PollsStandardTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
testrig.StandardStorageTeardown(suite.storage)
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
|
@ -18,7 +18,9 @@
|
|||
package polls
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
|
@ -97,9 +99,8 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
var form apimodel.PollVoteRequest
|
||||
|
||||
if err := c.ShouldBind(&form); err != nil {
|
||||
choices, err := bindChoices(c)
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorBadRequest(err, err.Error())
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@ -109,7 +110,7 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) {
|
|||
c.Request.Context(),
|
||||
authed.Account,
|
||||
pollID,
|
||||
form.Choices,
|
||||
choices,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
|
@ -118,3 +119,51 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, poll)
|
||||
}
|
||||
|
||||
func bindChoices(c *gin.Context) ([]int, error) {
|
||||
var form apimodel.PollVoteRequest
|
||||
if err := c.ShouldBind(&form); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if form.Choices != nil {
|
||||
// Easiest option: we parsed
|
||||
// from a form successfully.
|
||||
return form.Choices, nil
|
||||
}
|
||||
|
||||
// More difficult option: we
|
||||
// parsed choices from json.
|
||||
//
|
||||
// Convert submitted choices
|
||||
// into the ints we need.
|
||||
choices := make([]int, 0, len(form.ChoicesI))
|
||||
for _, choiceI := range form.ChoicesI {
|
||||
switch i := choiceI.(type) {
|
||||
|
||||
// JSON numbers normally
|
||||
// parse into float64.
|
||||
//
|
||||
// This is the most likely
|
||||
// option so try it first.
|
||||
case float64:
|
||||
choices = append(choices, int(i))
|
||||
|
||||
// Fallback option for funky
|
||||
// clients (pinafore, semaphore).
|
||||
case string:
|
||||
choice, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
choices = append(choices, choice)
|
||||
|
||||
default:
|
||||
// Nothing else will do.
|
||||
return nil, fmt.Errorf("could not parse json poll choice %T to integer", choiceI)
|
||||
}
|
||||
}
|
||||
|
||||
return choices, nil
|
||||
}
|
||||
|
|
189
internal/api/client/polls/polls_vote_test.go
Normal file
189
internal/api/client/polls/polls_vote_test.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
// 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 polls_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/polls"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type PollCreateTestSuite struct {
|
||||
PollsStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *PollCreateTestSuite) voteInPoll(
|
||||
pollID string,
|
||||
contentType string,
|
||||
body io.Reader,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.Poll, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["admin_account"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["admin_account"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["admin_account"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, config.GetProtocol()+"://"+config.GetHost()+"/api/"+polls.BasePath+"/"+pollID, body)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
ctx.Request.Header.Set("content-type", contentType)
|
||||
|
||||
ctx.AddParam("id", pollID)
|
||||
|
||||
// trigger the handler
|
||||
suite.pollsModule.PollVotePOSTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.Poll{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *PollCreateTestSuite) formVoteInPoll(
|
||||
pollID string,
|
||||
choices []int,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.Poll, error) {
|
||||
choicesStrs := make([]string, 0, len(choices))
|
||||
for _, choice := range choices {
|
||||
choicesStrs = append(choicesStrs, strconv.Itoa(choice))
|
||||
}
|
||||
|
||||
body, w, err := testrig.CreateMultipartFormData("", "", map[string][]string{
|
||||
"choices[]": choicesStrs,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
b := body.Bytes()
|
||||
suite.T().Log(string(b))
|
||||
|
||||
return suite.voteInPoll(
|
||||
pollID,
|
||||
w.FormDataContentType(),
|
||||
bytes.NewReader(b),
|
||||
expectedHTTPStatus,
|
||||
expectedBody,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *PollCreateTestSuite) jsonVoteInPoll(
|
||||
pollID string,
|
||||
choices []interface{},
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.Poll, error) {
|
||||
form := apimodel.PollVoteRequest{ChoicesI: choices}
|
||||
|
||||
b, err := json.Marshal(&form)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.T().Log(string(b))
|
||||
|
||||
return suite.voteInPoll(
|
||||
pollID,
|
||||
"application/json",
|
||||
bytes.NewReader(b),
|
||||
expectedHTTPStatus,
|
||||
expectedBody,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *PollCreateTestSuite) TestPollVoteForm() {
|
||||
targetPoll := suite.testPolls["local_account_1_status_6_poll"]
|
||||
|
||||
poll, err := suite.formVoteInPoll(targetPoll.ID, []int{2}, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.NotEmpty(poll)
|
||||
}
|
||||
|
||||
func (suite *PollCreateTestSuite) TestPollVoteJSONInt() {
|
||||
targetPoll := suite.testPolls["local_account_1_status_6_poll"]
|
||||
|
||||
poll, err := suite.jsonVoteInPoll(targetPoll.ID, []interface{}{2}, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.NotEmpty(poll)
|
||||
}
|
||||
|
||||
func (suite *PollCreateTestSuite) TestPollVoteJSONStr() {
|
||||
targetPoll := suite.testPolls["local_account_1_status_6_poll"]
|
||||
|
||||
poll, err := suite.jsonVoteInPoll(targetPoll.ID, []interface{}{"2"}, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.NotEmpty(poll)
|
||||
}
|
||||
|
||||
func TestPollCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, &PollCreateTestSuite{})
|
||||
}
|
|
@ -21,6 +21,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
|
@ -117,7 +118,10 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, apiStatus)
|
||||
}
|
||||
|
||||
// validateNormalizeCreateStatus checks the form for disallowed combinations of attachments and overlength inputs.
|
||||
// validateNormalizeCreateStatus checks the form
|
||||
// for disallowed combinations of attachments and
|
||||
// overlength inputs.
|
||||
//
|
||||
// Side effect: normalizes the post's language tag.
|
||||
func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {
|
||||
hasStatus := form.Status != ""
|
||||
|
@ -134,8 +138,6 @@ func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) erro
|
|||
|
||||
maxChars := config.GetStatusesMaxChars()
|
||||
maxMediaFiles := config.GetStatusesMediaMaxFiles()
|
||||
maxPollOptions := config.GetStatusesPollMaxOptions()
|
||||
maxPollChars := config.GetStatusesPollOptionMaxChars()
|
||||
maxCwChars := config.GetStatusesCWMaxChars()
|
||||
|
||||
if form.Status != "" {
|
||||
|
@ -149,16 +151,8 @@ func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) erro
|
|||
}
|
||||
|
||||
if form.Poll != nil {
|
||||
if len(form.Poll.Options) == 0 {
|
||||
return errors.New("poll with no options")
|
||||
}
|
||||
if len(form.Poll.Options) > maxPollOptions {
|
||||
return fmt.Errorf("too many poll options provided, %d provided but limit is %d", len(form.Poll.Options), maxPollOptions)
|
||||
}
|
||||
for _, p := range form.Poll.Options {
|
||||
if length := len([]rune(p)); length > maxPollChars {
|
||||
return fmt.Errorf("poll option too long, %d characters provided but limit is %d", length, maxPollChars)
|
||||
}
|
||||
if err := validateNormalizeCreatePoll(form); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,3 +172,45 @@ func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) erro
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNormalizeCreatePoll(form *apimodel.AdvancedStatusCreateForm) error {
|
||||
maxPollOptions := config.GetStatusesPollMaxOptions()
|
||||
maxPollChars := config.GetStatusesPollOptionMaxChars()
|
||||
|
||||
// Normalize poll expiry if necessary.
|
||||
// If we parsed this as JSON, expires_in
|
||||
// may be either a float64 or a string.
|
||||
if ei := form.Poll.ExpiresInI; ei != nil {
|
||||
switch e := ei.(type) {
|
||||
case float64:
|
||||
form.Poll.ExpiresIn = int(e)
|
||||
|
||||
case string:
|
||||
expiresIn, err := strconv.Atoi(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse expires_in value %s as integer: %w", e, err)
|
||||
}
|
||||
|
||||
form.Poll.ExpiresIn = expiresIn
|
||||
|
||||
default:
|
||||
return fmt.Errorf("could not parse expires_in type %T as integer", ei)
|
||||
}
|
||||
}
|
||||
|
||||
if len(form.Poll.Options) == 0 {
|
||||
return errors.New("poll with no options")
|
||||
}
|
||||
|
||||
if len(form.Poll.Options) > maxPollOptions {
|
||||
return fmt.Errorf("too many poll options provided, %d provided but limit is %d", len(form.Poll.Options), maxPollOptions)
|
||||
}
|
||||
|
||||
for _, p := range form.Poll.Options {
|
||||
if length := len([]rune(p)); length > maxPollChars {
|
||||
return fmt.Errorf("poll option too long, %d characters provided but limit is %d", length, maxPollChars)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -80,7 +80,11 @@ type PollRequest struct {
|
|||
|
||||
// Duration the poll should be open, in seconds.
|
||||
// If provided, media_ids cannot be used, and poll[options] must be provided.
|
||||
ExpiresIn int `form:"expires_in" json:"expires_in" xml:"expires_in"`
|
||||
ExpiresIn int `form:"expires_in" xml:"expires_in"`
|
||||
|
||||
// Duration the poll should be open, in seconds.
|
||||
// If provided, media_ids cannot be used, and poll[options] must be provided.
|
||||
ExpiresInI interface{} `json:"expires_in"`
|
||||
|
||||
// Allow multiple choices on this poll.
|
||||
Multiple bool `form:"multiple" json:"multiple" xml:"multiple"`
|
||||
|
@ -93,7 +97,10 @@ type PollRequest struct {
|
|||
//
|
||||
// swagger:ignore
|
||||
type PollVoteRequest struct {
|
||||
// Choices contains poll vote choice indices. Note that form
|
||||
// uses a different key than the JSON, i.e. the '[]' suffix.
|
||||
Choices []int `form:"choices[]" json:"choices" xml:"choices"`
|
||||
// Choices contains poll vote choice indices.
|
||||
Choices []int `form:"choices[]" xml:"choices"`
|
||||
|
||||
// ChoicesI contains poll vote choice
|
||||
// indices. Can be strings or integers.
|
||||
ChoicesI []interface{} `json:"choices"`
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ func StartTimelines(state *state.State, filter *visibility.Filter, converter *ty
|
|||
// The returned *multipart.Writer w can be used to set the content type of the request, like so:
|
||||
//
|
||||
// req.Header.Set("Content-Type", w.FormDataContentType())
|
||||
func CreateMultipartFormData(fieldName string, fileName string, extraFields map[string]string) (bytes.Buffer, *multipart.Writer, error) {
|
||||
func CreateMultipartFormData(fieldName string, fileName string, extraFields map[string][]string) (bytes.Buffer, *multipart.Writer, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
w := multipart.NewWriter(&b)
|
||||
|
@ -104,13 +104,11 @@ func CreateMultipartFormData(fieldName string, fileName string, extraFields map[
|
|||
}
|
||||
}
|
||||
|
||||
for k, v := range extraFields {
|
||||
f, err := w.CreateFormField(k)
|
||||
if err != nil {
|
||||
return b, nil, err
|
||||
}
|
||||
if _, err := io.Copy(f, bytes.NewBufferString(v)); err != nil {
|
||||
return b, nil, err
|
||||
for k, vs := range extraFields {
|
||||
for _, v := range vs {
|
||||
if err := w.WriteField(k, v); err != nil {
|
||||
return b, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue