mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 23:42:25 -05: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( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"grant_type":    "client_credentials", | 			"grant_type":    {"client_credentials"}, | ||||||
| 			"client_id":     testClient.ID, | 			"client_id":     {testClient.ID}, | ||||||
| 			"client_secret": testClient.Secret, | 			"client_secret": {testClient.Secret}, | ||||||
| 			"redirect_uri":  "http://localhost:8080", | 			"redirect_uri":  {"http://localhost:8080"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -104,12 +104,12 @@ func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeOK() { | ||||||
| 
 | 
 | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"grant_type":    "authorization_code", | 			"grant_type":    {"authorization_code"}, | ||||||
| 			"client_id":     testClient.ID, | 			"client_id":     {testClient.ID}, | ||||||
| 			"client_secret": testClient.Secret, | 			"client_secret": {testClient.Secret}, | ||||||
| 			"redirect_uri":  "http://localhost:8080", | 			"redirect_uri":  {"http://localhost:8080"}, | ||||||
| 			"code":          testUserAuthorizationToken.Code, | 			"code":          {testUserAuthorizationToken.Code}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -149,11 +149,11 @@ func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeNoCode() { | ||||||
| 
 | 
 | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"grant_type":    "authorization_code", | 			"grant_type":    {"authorization_code"}, | ||||||
| 			"client_id":     testClient.ID, | 			"client_id":     {testClient.ID}, | ||||||
| 			"client_secret": testClient.Secret, | 			"client_secret": {testClient.Secret}, | ||||||
| 			"redirect_uri":  "http://localhost:8080", | 			"redirect_uri":  {"http://localhost:8080"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -181,12 +181,12 @@ func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeWrongGrantType() { | ||||||
| 
 | 
 | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"grant_type":    "client_credentials", | 			"grant_type":    {"client_credentials"}, | ||||||
| 			"client_id":     testClient.ID, | 			"client_id":     {testClient.ID}, | ||||||
| 			"client_secret": testClient.Secret, | 			"client_secret": {testClient.Secret}, | ||||||
| 			"redirect_uri":  "http://localhost:8080", | 			"redirect_uri":  {"http://localhost:8080"}, | ||||||
| 			"code":          "peepeepoopoo", | 			"code":          {"peepeepoopoo"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  |  | ||||||
|  | @ -36,8 +36,8 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandler() { | ||||||
| 	// we're deleting zork | 	// we're deleting zork | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"password": "password", | 			"password": {"password"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -58,8 +58,8 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerWrongPassword() | ||||||
| 	// we're deleting zork | 	// we're deleting zork | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"password": "aaaaaaaaaaaaaaaaaaaaaaaaaaaa", | 			"password": {"aaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -80,7 +80,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerNoPassword() { | ||||||
| 	// we're deleting zork | 	// we're deleting zork | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{}) | 		map[string][]string{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -38,15 +38,19 @@ type AccountUpdateTestSuite struct { | ||||||
| 	AccountStandardTestSuite | 	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{} | 	form := url.Values{} | ||||||
| 	for key, val := range data { | 	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) | 	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) | 	requestBody, w, err := testrig.CreateMultipartFormData("", "", data) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		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) | 	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) | 	requestBody, w, err := testrig.CreateMultipartFormData(fieldName, fileName, data) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
|  | @ -116,12 +120,12 @@ func (suite *AccountUpdateTestSuite) updateAccount( | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicForm() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicForm() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"note":                        "this is my new bio read it and weep", | 		"note":                        {"this is my new bio read it and weep"}, | ||||||
| 		"fields_attributes[0][name]":  "pronouns", | 		"fields_attributes[0][name]":  {"pronouns"}, | ||||||
| 		"fields_attributes[0][value]": "they/them", | 		"fields_attributes[0][value]": {"they/them"}, | ||||||
| 		"fields_attributes[1][name]":  "Website", | 		"fields_attributes[1][name]":  {"Website"}, | ||||||
| 		"fields_attributes[1][value]": "https://example.com", | 		"fields_attributes[1][value]": {"https://example.com"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | ||||||
|  | @ -142,12 +146,12 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicForm() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicFormData() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicFormData() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"note":                        "this is my new bio read it and weep", | 		"note":                        {"this is my new bio read it and weep"}, | ||||||
| 		"fields_attributes[0][name]":  "pronouns", | 		"fields_attributes[0][name]":  {"pronouns"}, | ||||||
| 		"fields_attributes[0][value]": "they/them", | 		"fields_attributes[0][value]": {"they/them"}, | ||||||
| 		"fields_attributes[1][name]":  "Website", | 		"fields_attributes[1][name]":  {"Website"}, | ||||||
| 		"fields_attributes[1][value]": "https://example.com", | 		"fields_attributes[1][value]": {"https://example.com"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | ||||||
|  | @ -202,8 +206,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountBasicJSON() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountLockForm() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountLockForm() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"locked": "true", | 		"locked": {"true"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | ||||||
|  | @ -215,8 +219,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountLockForm() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountLockFormData() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountLockFormData() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"locked": "true", | 		"locked": {"true"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | ||||||
|  | @ -242,8 +246,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountLockJSON() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockForm() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockForm() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"locked": "false", | 		"locked": {"false"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | ||||||
|  | @ -255,8 +259,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockForm() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockFormData() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountUnlockFormData() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"locked": "false", | 		"locked": {"false"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | ||||||
|  | @ -289,8 +293,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountCache() { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"note": "this is my new bio read it and weep", | 		"note": {"this is my new bio read it and weep"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | ||||||
|  | @ -302,8 +306,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountCache() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableForm() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableForm() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"discoverable": "false", | 		"discoverable": {"false"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | ||||||
|  | @ -320,8 +324,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableForm() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableFormData() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableFormData() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"discoverable": "false", | 		"discoverable": {"false"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | ||||||
|  | @ -357,10 +361,10 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountDiscoverableJSON() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountWithImageFormData() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountWithImageFormData() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"display_name": "updated zork display name!!!", | 		"display_name": {"updated zork display name!!!"}, | ||||||
| 		"note":         "", | 		"note":         {""}, | ||||||
| 		"locked":       "true", | 		"locked":       {"true"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormDataWithFile("header", "../../../../testrig/media/test-jpeg.jpg", data, http.StatusOK, "") | 	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.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.Equal(data["display_name"], apimodelAccount.DisplayName) | 	suite.Equal(data["display_name"][0], apimodelAccount.DisplayName) | ||||||
| 	suite.True(apimodelAccount.Locked) | 	suite.True(apimodelAccount.Locked) | ||||||
| 	suite.Empty(apimodelAccount.Note) | 	suite.Empty(apimodelAccount.Note) | ||||||
| 	suite.Empty(apimodelAccount.Source.Note) | 	suite.Empty(apimodelAccount.Source.Note) | ||||||
|  | @ -382,7 +386,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountWithImageFormData() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyForm() { | 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"}`) | 	_, err := suite.updateAccountFromForm(data, http.StatusBadRequest, `{"error":"Bad Request: empty form submitted"}`) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -391,7 +395,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyForm() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyFormData() { | 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"}`) | 	_, err := suite.updateAccountFromFormData(data, http.StatusBadRequest, `{"error":"Bad Request: empty form submitted"}`) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -400,11 +404,11 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountEmptyFormData() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceForm() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceForm() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"source[privacy]":   string(apimodel.VisibilityPrivate), | 		"source[privacy]":   {string(apimodel.VisibilityPrivate)}, | ||||||
| 		"source[language]":  "de", | 		"source[language]":  {"de"}, | ||||||
| 		"source[sensitive]": "true", | 		"source[sensitive]": {"true"}, | ||||||
| 		"locked":            "true", | 		"locked":            {"true"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromForm(data, http.StatusOK, "") | ||||||
|  | @ -412,18 +416,18 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceForm() { | ||||||
| 		suite.FailNow(err.Error()) | 		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.EqualValues(apimodel.VisibilityPrivate, apimodelAccount.Source.Privacy) | ||||||
| 	suite.True(apimodelAccount.Source.Sensitive) | 	suite.True(apimodelAccount.Source.Sensitive) | ||||||
| 	suite.True(apimodelAccount.Locked) | 	suite.True(apimodelAccount.Locked) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceFormData() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceFormData() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"source[privacy]":   string(apimodel.VisibilityPrivate), | 		"source[privacy]":   {string(apimodel.VisibilityPrivate)}, | ||||||
| 		"source[language]":  "de", | 		"source[language]":  {"de"}, | ||||||
| 		"source[sensitive]": "true", | 		"source[sensitive]": {"true"}, | ||||||
| 		"locked":            "true", | 		"locked":            {"true"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | ||||||
|  | @ -431,7 +435,7 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceFormData() { | ||||||
| 		suite.FailNow(err.Error()) | 		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.EqualValues(apimodel.VisibilityPrivate, apimodelAccount.Source.Privacy) | ||||||
| 	suite.True(apimodelAccount.Source.Sensitive) | 	suite.True(apimodelAccount.Source.Sensitive) | ||||||
| 	suite.True(apimodelAccount.Locked) | 	suite.True(apimodelAccount.Locked) | ||||||
|  | @ -461,8 +465,8 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceJSON() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormData() { | func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormData() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"source[status_content_type]": "text/markdown", | 		"source[status_content_type]": {"text/markdown"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | 	apimodelAccount, err := suite.updateAccountFromFormData(data, http.StatusOK, "") | ||||||
|  | @ -470,19 +474,19 @@ func (suite *AccountUpdateTestSuite) TestUpdateAccountSourceBadContentTypeFormDa | ||||||
| 		suite.FailNow(err.Error()) | 		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. | 	// Check the account in the database too. | ||||||
| 	dbAccount, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID) | 	dbAccount, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		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() { | func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateStatusContentTypeBad() { | ||||||
| 	data := map[string]string{ | 	data := map[string][]string{ | ||||||
| 		"source[status_content_type]": "peepeepoopoo", | 		"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'"}`) | 	_, 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 | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"image", "../../../../testrig/media/rainbow-original.png", | 		"image", "../../../../testrig/media/rainbow-original.png", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"shortcode": "new_emoji", | 			"shortcode": {"new_emoji"}, | ||||||
| 			"category":  "Test Emojis", // this category doesn't exist yet | 			"category":  {"Test Emojis"}, // this category doesn't exist yet | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -112,9 +112,9 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreateExistingCategory() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"image", "../../../../testrig/media/rainbow-original.png", | 		"image", "../../../../testrig/media/rainbow-original.png", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"shortcode": "new_emoji", | 			"shortcode": {"new_emoji"}, | ||||||
| 			"category":  "cute stuff", // this category already exists | 			"category":  {"cute stuff"}, // this category already exists | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -185,9 +185,9 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreateNoCategory() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"image", "../../../../testrig/media/rainbow-original.png", | 		"image", "../../../../testrig/media/rainbow-original.png", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"shortcode": "new_emoji", | 			"shortcode": {"new_emoji"}, | ||||||
| 			"category":  "", | 			"category":  {""}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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 | 	// set up the request -- use a shortcode that already exists for an emoji in the database | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"image", "../../../../testrig/media/rainbow-original.png", | 		"image", "../../../../testrig/media/rainbow-original.png", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"shortcode": "rainbow", | 			"shortcode": {"rainbow"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  |  | ||||||
|  | @ -43,9 +43,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateNewCategory() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"category": "New Category", // this category doesn't exist yet | 			"category": {"New Category"}, // this category doesn't exist yet | ||||||
| 			"type":     "modify", | 			"type":     {"modify"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -120,9 +120,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateSwitchCategory() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type":     "modify", | 			"type":     {"modify"}, | ||||||
| 			"category": "cute stuff", | 			"category": {"cute stuff"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -197,10 +197,10 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyRemoteToLocal() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type":      "copy", | 			"type":      {"copy"}, | ||||||
| 			"category":  "emojis i stole", | 			"category":  {"emojis i stole"}, | ||||||
| 			"shortcode": "yell", | 			"shortcode": {"yell"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -275,8 +275,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableEmoji() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type": "disable", | 			"type": {"disable"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -316,8 +316,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableLocalEmoji() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type": "disable", | 			"type": {"disable"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -349,8 +349,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyRemoteEmoji() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"image", "../../../../testrig/media/kip-original.gif", | 		"image", "../../../../testrig/media/kip-original.gif", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type": "modify", | 			"type": {"modify"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -382,8 +382,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyNoParams() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type": "modify", | 			"type": {"modify"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -415,9 +415,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyLocalToLocal() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type":      "copy", | 			"type":      {"copy"}, | ||||||
| 			"shortcode": "bottoms", | 			"shortcode": {"bottoms"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -449,9 +449,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyEmptyShortcode() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type":      "copy", | 			"type":      {"copy"}, | ||||||
| 			"shortcode": "", | 			"shortcode": {""}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -483,8 +483,8 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyNoShortcode() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type": "copy", | 			"type": {"copy"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -516,9 +516,9 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyShortcodeAlreadyInUse() { | ||||||
| 	// set up the request | 	// set up the request | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"type":      "copy", | 			"type":      {"copy"}, | ||||||
| 			"shortcode": "rainbow", | 			"shortcode": {"rainbow"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ type InstancePatchTestSuite struct { | ||||||
| 	InstanceStandardTestSuite | 	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) | 	requestBody, w, err := testrig.CreateMultipartFormData(fieldName, fileName, extraFields) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
|  | @ -59,10 +59,10 @@ func (suite *InstancePatchTestSuite) instancePatch(fieldName string, fileName st | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch1() { | func (suite *InstancePatchTestSuite) TestInstancePatch1() { | ||||||
| 	code, b := suite.instancePatch("", "", map[string]string{ | 	code, b := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"title":            "Example Instance", | 		"title":            {"Example Instance"}, | ||||||
| 		"contact_username": "admin", | 		"contact_username": {"admin"}, | ||||||
| 		"contact_email":    "someone@example.org", | 		"contact_email":    {"someone@example.org"}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusOK; code != expectedCode { | 	if expectedCode := http.StatusOK; code != expectedCode { | ||||||
|  | @ -175,8 +175,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch2() { | func (suite *InstancePatchTestSuite) TestInstancePatch2() { | ||||||
| 	code, b := suite.instancePatch("", "", map[string]string{ | 	code, b := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"title": "<p>Geoff's Instance</p>", | 		"title": {"<p>Geoff's Instance</p>"}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusOK; code != expectedCode { | 	if expectedCode := http.StatusOK; code != expectedCode { | ||||||
|  | @ -289,8 +289,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch3() { | func (suite *InstancePatchTestSuite) TestInstancePatch3() { | ||||||
| 	code, b := suite.instancePatch("", "", map[string]string{ | 	code, b := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"short_description": "<p>This is some html, which is <em>allowed</em> in short descriptions.</p>", | 		"short_description": {"<p>This is some html, which is <em>allowed</em> in short descriptions.</p>"}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusOK; code != expectedCode { | 	if expectedCode := http.StatusOK; code != expectedCode { | ||||||
|  | @ -403,8 +403,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch4() { | func (suite *InstancePatchTestSuite) TestInstancePatch4() { | ||||||
| 	code, b := suite.instancePatch("", "", map[string]string{ | 	code, b := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"": "", | 		"": {""}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusBadRequest; code != expectedCode { | 	if expectedCode := http.StatusBadRequest; code != expectedCode { | ||||||
|  | @ -422,8 +422,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch4() { | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch5() { | func (suite *InstancePatchTestSuite) TestInstancePatch5() { | ||||||
| 	requestBody, w, err := testrig.CreateMultipartFormData( | 	requestBody, w, err := testrig.CreateMultipartFormData( | ||||||
| 		"", "", | 		"", "", | ||||||
| 		map[string]string{ | 		map[string][]string{ | ||||||
| 			"short_description": "<p>This is some html, which is <em>allowed</em> in short descriptions.</p>", | 			"short_description": {"<p>This is some html, which is <em>allowed</em> in short descriptions.</p>"}, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -454,8 +454,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch5() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch6() { | func (suite *InstancePatchTestSuite) TestInstancePatch6() { | ||||||
| 	code, b := suite.instancePatch("", "", map[string]string{ | 	code, b := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"contact_email": "", | 		"contact_email": {""}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusOK; code != expectedCode { | 	if expectedCode := http.StatusOK; code != expectedCode { | ||||||
|  | @ -568,8 +568,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch7() { | func (suite *InstancePatchTestSuite) TestInstancePatch7() { | ||||||
| 	code, b := suite.instancePatch("", "", map[string]string{ | 	code, b := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"contact_email": "not.an.email.address", | 		"contact_email": {"not.an.email.address"}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusBadRequest; code != expectedCode { | 	if expectedCode := http.StatusBadRequest; code != expectedCode { | ||||||
|  | @ -585,8 +585,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch7() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch8() { | func (suite *InstancePatchTestSuite) TestInstancePatch8() { | ||||||
| 	code, b := suite.instancePatch("thumbnail", "../../../../testrig/media/peglin.gif", map[string]string{ | 	code, b := suite.instancePatch("thumbnail", "../../../../testrig/media/peglin.gif", map[string][]string{ | ||||||
| 		"thumbnail_description": "A bouncing little green peglin.", | 		"thumbnail_description": {"A bouncing little green peglin."}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusOK; code != expectedCode { | 	if expectedCode := http.StatusOK; code != expectedCode { | ||||||
|  | @ -723,8 +723,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() { | ||||||
| }`, string(instanceV2ThumbnailJson)) | }`, string(instanceV2ThumbnailJson)) | ||||||
| 
 | 
 | ||||||
| 	// double extra special bonus: now update the image description without changing the image | 	// double extra special bonus: now update the image description without changing the image | ||||||
| 	code2, b2 := suite.instancePatch("", "", map[string]string{ | 	code2, b2 := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"thumbnail_description": "updating the thumbnail description without changing anything else!", | 		"thumbnail_description": {"updating the thumbnail description without changing anything else!"}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusOK; code2 != expectedCode { | 	if expectedCode := http.StatusOK; code2 != expectedCode { | ||||||
|  | @ -741,8 +741,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstancePatchTestSuite) TestInstancePatch9() { | func (suite *InstancePatchTestSuite) TestInstancePatch9() { | ||||||
| 	code, b := suite.instancePatch("", "", map[string]string{ | 	code, b := suite.instancePatch("", "", map[string][]string{ | ||||||
| 		"thumbnail_description": "setting a new description without having a custom image set; this should change nothing!", | 		"thumbnail_description": {"setting a new description without having a custom image set; this should change nothing!"}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if expectedCode := http.StatusOK; code != expectedCode { | 	if expectedCode := http.StatusOK; code != expectedCode { | ||||||
|  |  | ||||||
|  | @ -160,9 +160,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// create the request | 	// create the request | ||||||
| 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{ | 	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", | 		"description": {"this is a test image -- a cool background from somewhere"}, | ||||||
| 		"focus":       "-0.5,0.5", | 		"focus":       {"-0.5,0.5"}, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -245,9 +245,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// create the request | 	// create the request | ||||||
| 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{ | 	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", | 		"description": {"this is a test image -- a cool background from somewhere"}, | ||||||
| 		"focus":       "-0.5,0.5", | 		"focus":       {"-0.5,0.5"}, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -328,9 +328,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() { | ||||||
| 	description := base64.RawStdEncoding.EncodeToString(descriptionBytes) | 	description := base64.RawStdEncoding.EncodeToString(descriptionBytes) | ||||||
| 
 | 
 | ||||||
| 	// create the request | 	// create the request | ||||||
| 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{ | 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string][]string{ | ||||||
| 		"description": description, | 		"description": {description}, | ||||||
| 		"focus":       "-0.5,0.5", | 		"focus":       {"-0.5,0.5"}, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -369,9 +369,9 @@ func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) | ||||||
| 
 | 
 | ||||||
| 	// create the request | 	// create the request | ||||||
| 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{ | 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string][]string{ | ||||||
| 		"description": "", // provide an empty description | 		"description": {""}, // provide an empty description | ||||||
| 		"focus":       "-0.5,0.5", | 		"focus":       {"-0.5,0.5"}, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  |  | ||||||
|  | @ -149,10 +149,10 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) | ||||||
| 
 | 
 | ||||||
| 	// create the request | 	// create the request | ||||||
| 	buf, w, err := testrig.CreateMultipartFormData("", "", map[string]string{ | 	buf, w, err := testrig.CreateMultipartFormData("", "", map[string][]string{ | ||||||
| 		"id":          toUpdate.ID, | 		"id":          {toUpdate.ID}, | ||||||
| 		"description": "new description!", | 		"description": {"new description!"}, | ||||||
| 		"focus":       "-0.1,0.3", | 		"focus":       {"-0.1,0.3"}, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
|  | @ -210,10 +210,10 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) | ||||||
| 
 | 
 | ||||||
| 	// create the request | 	// create the request | ||||||
| 	buf, w, err := testrig.CreateMultipartFormData("", "", map[string]string{ | 	buf, w, err := testrig.CreateMultipartFormData("", "", map[string][]string{ | ||||||
| 		"id":          toUpdate.ID, | 		"id":          {toUpdate.ID}, | ||||||
| 		"description": "new description!", | 		"description": {"new description!"}, | ||||||
| 		"focus":       "-0.1,0.3", | 		"focus":       {"-0.1,0.3"}, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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 | package polls | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
|  | @ -97,9 +99,8 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var form apimodel.PollVoteRequest | 	choices, err := bindChoices(c) | ||||||
| 
 | 	if err != nil { | ||||||
| 	if err := c.ShouldBind(&form); err != nil { |  | ||||||
| 		errWithCode := gtserror.NewErrorBadRequest(err, err.Error()) | 		errWithCode := gtserror.NewErrorBadRequest(err, err.Error()) | ||||||
| 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | ||||||
| 		return | 		return | ||||||
|  | @ -109,7 +110,7 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) { | ||||||
| 		c.Request.Context(), | 		c.Request.Context(), | ||||||
| 		authed.Account, | 		authed.Account, | ||||||
| 		pollID, | 		pollID, | ||||||
| 		form.Choices, | 		choices, | ||||||
| 	) | 	) | ||||||
| 	if errWithCode != nil { | 	if errWithCode != nil { | ||||||
| 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | ||||||
|  | @ -118,3 +119,51 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) { | ||||||
| 
 | 
 | ||||||
| 	c.JSON(http.StatusOK, poll) | 	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" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
|  | @ -117,7 +118,10 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { | ||||||
| 	c.JSON(http.StatusOK, apiStatus) | 	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. | // Side effect: normalizes the post's language tag. | ||||||
| func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) error { | func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) error { | ||||||
| 	hasStatus := form.Status != "" | 	hasStatus := form.Status != "" | ||||||
|  | @ -134,8 +138,6 @@ func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) erro | ||||||
| 
 | 
 | ||||||
| 	maxChars := config.GetStatusesMaxChars() | 	maxChars := config.GetStatusesMaxChars() | ||||||
| 	maxMediaFiles := config.GetStatusesMediaMaxFiles() | 	maxMediaFiles := config.GetStatusesMediaMaxFiles() | ||||||
| 	maxPollOptions := config.GetStatusesPollMaxOptions() |  | ||||||
| 	maxPollChars := config.GetStatusesPollOptionMaxChars() |  | ||||||
| 	maxCwChars := config.GetStatusesCWMaxChars() | 	maxCwChars := config.GetStatusesCWMaxChars() | ||||||
| 
 | 
 | ||||||
| 	if form.Status != "" { | 	if form.Status != "" { | ||||||
|  | @ -149,16 +151,8 @@ func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) erro | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if form.Poll != nil { | 	if form.Poll != nil { | ||||||
| 		if len(form.Poll.Options) == 0 { | 		if err := validateNormalizeCreatePoll(form); err != nil { | ||||||
| 			return errors.New("poll with no options") | 			return err | ||||||
| 		} |  | ||||||
| 		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) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -178,3 +172,45 @@ func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) erro | ||||||
| 
 | 
 | ||||||
| 	return nil | 	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. | 	// Duration the poll should be open, in seconds. | ||||||
| 	// If provided, media_ids cannot be used, and poll[options] must be provided. | 	// 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. | 	// Allow multiple choices on this poll. | ||||||
| 	Multiple bool `form:"multiple" json:"multiple" xml:"multiple"` | 	Multiple bool `form:"multiple" json:"multiple" xml:"multiple"` | ||||||
|  | @ -93,7 +97,10 @@ type PollRequest struct { | ||||||
| // | // | ||||||
| // swagger:ignore | // swagger:ignore | ||||||
| type PollVoteRequest struct { | type PollVoteRequest struct { | ||||||
| 	// Choices contains poll vote choice indices. Note that form | 	// Choices contains poll vote choice indices. | ||||||
| 	// uses a different key than the JSON, i.e. the '[]' suffix. | 	Choices []int `form:"choices[]" xml:"choices"` | ||||||
| 	Choices []int `form:"choices[]" json:"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: | // 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()) | //	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 | 	var b bytes.Buffer | ||||||
| 
 | 
 | ||||||
| 	w := multipart.NewWriter(&b) | 	w := multipart.NewWriter(&b) | ||||||
|  | @ -104,13 +104,11 @@ func CreateMultipartFormData(fieldName string, fileName string, extraFields map[ | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for k, v := range extraFields { | 	for k, vs := range extraFields { | ||||||
| 		f, err := w.CreateFormField(k) | 		for _, v := range vs { | ||||||
| 		if err != nil { | 			if err := w.WriteField(k, v); err != nil { | ||||||
| 				return b, nil, err | 				return b, nil, err | ||||||
| 			} | 			} | ||||||
| 		if _, err := io.Copy(f, bytes.NewBufferString(v)); err != nil { |  | ||||||
| 			return b, nil, err |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue