mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] filter API v2: Restore keywords_attributes and statuses_attributes (#2995)
These filter API v2 features were cut late in development because the form encoding version is hard to implement correctly and because I thought no clients actually used `keywords_attributes`. Unfortunately, Phanpy does use `keywords_attributes`.
This commit is contained in:
@ -100,6 +100,30 @@ import (
|
||||
// - warn
|
||||
// - hide
|
||||
// default: warn
|
||||
// -
|
||||
// name: keywords_attributes[][keyword]
|
||||
// in: formData
|
||||
// type: array
|
||||
// items:
|
||||
// type: string
|
||||
// description: Keywords to be added (if not using id param) or updated (if using id param).
|
||||
// collectionFormat: multi
|
||||
// -
|
||||
// name: keywords_attributes[][whole_word]
|
||||
// in: formData
|
||||
// type: array
|
||||
// items:
|
||||
// type: boolean
|
||||
// description: Should each keyword consider word boundaries?
|
||||
// collectionFormat: multi
|
||||
// -
|
||||
// name: statuses_attributes[][status_id]
|
||||
// in: formData
|
||||
// type: array
|
||||
// items:
|
||||
// type: string
|
||||
// description: Statuses to be added to the filter.
|
||||
// collectionFormat: multi
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
@ -176,6 +200,30 @@ func validateNormalizeCreateFilter(form *apimodel.FilterCreateRequestV2) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse form variant of normal filter keyword creation structs.
|
||||
if len(form.KeywordsAttributesKeyword) > 0 {
|
||||
form.Keywords = make([]apimodel.FilterKeywordCreateUpdateRequest, 0, len(form.KeywordsAttributesKeyword))
|
||||
for i, keyword := range form.KeywordsAttributesKeyword {
|
||||
formKeyword := apimodel.FilterKeywordCreateUpdateRequest{
|
||||
Keyword: keyword,
|
||||
}
|
||||
if i < len(form.KeywordsAttributesWholeWord) {
|
||||
formKeyword.WholeWord = &form.KeywordsAttributesWholeWord[i]
|
||||
}
|
||||
form.Keywords = append(form.Keywords, formKeyword)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse form variant of normal filter status creation structs.
|
||||
if len(form.StatusesAttributesStatusID) > 0 {
|
||||
form.Statuses = make([]apimodel.FilterStatusCreateRequest, 0, len(form.StatusesAttributesStatusID))
|
||||
for _, statusID := range form.StatusesAttributesStatusID {
|
||||
form.Statuses = append(form.Statuses, apimodel.FilterStatusCreateRequest{
|
||||
StatusID: statusID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Apply defaults for missing fields.
|
||||
form.FilterAction = util.Ptr(action)
|
||||
|
||||
@ -200,5 +248,18 @@ func validateNormalizeCreateFilter(form *apimodel.FilterCreateRequestV2) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize and validate new keywords and statuses.
|
||||
for i, formKeyword := range form.Keywords {
|
||||
if err := validate.FilterKeyword(formKeyword.Keyword); err != nil {
|
||||
return err
|
||||
}
|
||||
form.Keywords[i].WholeWord = util.Ptr(util.PtrValueOr(formKeyword.WholeWord, false))
|
||||
}
|
||||
for _, formStatus := range form.Statuses {
|
||||
if err := validate.ULID(formStatus.StatusID, "status_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -35,7 +36,7 @@ import (
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) postFilter(title *string, context *[]string, action *string, expiresIn *int, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) {
|
||||
func (suite *FiltersTestSuite) postFilter(title *string, context *[]string, action *string, expiresIn *int, keywordsAttributesKeyword *[]string, keywordsAttributesWholeWord *[]bool, statusesAttributesStatusID *[]string, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
@ -64,6 +65,19 @@ func (suite *FiltersTestSuite) postFilter(title *string, context *[]string, acti
|
||||
if expiresIn != nil {
|
||||
ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)}
|
||||
}
|
||||
if keywordsAttributesKeyword != nil {
|
||||
ctx.Request.Form["keywords_attributes[][keyword]"] = *keywordsAttributesKeyword
|
||||
}
|
||||
if keywordsAttributesWholeWord != nil {
|
||||
formatted := []string{}
|
||||
for _, value := range *keywordsAttributesWholeWord {
|
||||
formatted = append(formatted, strconv.FormatBool(value))
|
||||
}
|
||||
ctx.Request.Form["keywords_attributes[][whole_word]"] = formatted
|
||||
}
|
||||
if statusesAttributesStatusID != nil {
|
||||
ctx.Request.Form["statuses_attributes[][status_id]"] = *statusesAttributesStatusID
|
||||
}
|
||||
}
|
||||
|
||||
// trigger the handler
|
||||
@ -111,7 +125,12 @@ func (suite *FiltersTestSuite) TestPostFilterFull() {
|
||||
context := []string{"home", "public"}
|
||||
action := "warn"
|
||||
expiresIn := 86400
|
||||
filter, err := suite.postFilter(&title, &context, &action, &expiresIn, nil, http.StatusOK, "")
|
||||
// Checked in lexical order by keyword, so keep this sorted.
|
||||
keywordsAttributesKeyword := []string{"GNU", "Linux"}
|
||||
keywordsAttributesWholeWord := []bool{true, false}
|
||||
// Checked in lexical order by status ID, so keep this sorted.
|
||||
statusAttributesStatusID := []string{"01HEN2QRFA8H3C6QPN7RD4KSR6", "01HEWV37MHV8BAC8ANFGVRRM5D"}
|
||||
filter, err := suite.postFilter(&title, &context, &action, &expiresIn, &keywordsAttributesKeyword, &keywordsAttributesWholeWord, &statusAttributesStatusID, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -126,8 +145,25 @@ func (suite *FiltersTestSuite) TestPostFilterFull() {
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Empty(filter.Keywords)
|
||||
suite.Empty(filter.Statuses)
|
||||
|
||||
if suite.Len(filter.Keywords, len(keywordsAttributesKeyword)) {
|
||||
slices.SortFunc(filter.Keywords, func(lhs, rhs apimodel.FilterKeyword) int {
|
||||
return strings.Compare(lhs.Keyword, rhs.Keyword)
|
||||
})
|
||||
for i, filterKeyword := range filter.Keywords {
|
||||
suite.Equal(keywordsAttributesKeyword[i], filterKeyword.Keyword)
|
||||
suite.Equal(keywordsAttributesWholeWord[i], filterKeyword.WholeWord)
|
||||
}
|
||||
}
|
||||
|
||||
if suite.Len(filter.Statuses, len(statusAttributesStatusID)) {
|
||||
slices.SortFunc(filter.Statuses, func(lhs, rhs apimodel.FilterStatus) int {
|
||||
return strings.Compare(lhs.StatusID, rhs.StatusID)
|
||||
})
|
||||
for i, filterStatus := range filter.Statuses {
|
||||
suite.Equal(statusAttributesStatusID[i], filterStatus.StatusID)
|
||||
}
|
||||
}
|
||||
|
||||
suite.checkStreamed(homeStream, true, "", stream.EventTypeFiltersChanged)
|
||||
}
|
||||
@ -141,9 +177,27 @@ func (suite *FiltersTestSuite) TestPostFilterFullJSON() {
|
||||
"context": ["home", "public"],
|
||||
"filter_action": "warn",
|
||||
"whole_word": true,
|
||||
"expires_in": 86400.1
|
||||
"expires_in": 86400.1,
|
||||
"keywords_attributes": [
|
||||
{
|
||||
"keyword": "GNU",
|
||||
"whole_word": true
|
||||
},
|
||||
{
|
||||
"keyword": "Linux",
|
||||
"whole_word": false
|
||||
}
|
||||
],
|
||||
"statuses_attributes": [
|
||||
{
|
||||
"status_id": "01HEN2QRFA8H3C6QPN7RD4KSR6"
|
||||
},
|
||||
{
|
||||
"status_id": "01HEWV37MHV8BAC8ANFGVRRM5D"
|
||||
}
|
||||
]
|
||||
}`
|
||||
filter, err := suite.postFilter(nil, nil, nil, nil, &requestJson, http.StatusOK, "")
|
||||
filter, err := suite.postFilter(nil, nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -160,8 +214,28 @@ func (suite *FiltersTestSuite) TestPostFilterFullJSON() {
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Empty(filter.Keywords)
|
||||
suite.Empty(filter.Statuses)
|
||||
|
||||
if suite.Len(filter.Keywords, 2) {
|
||||
slices.SortFunc(filter.Keywords, func(lhs, rhs apimodel.FilterKeyword) int {
|
||||
return strings.Compare(lhs.Keyword, rhs.Keyword)
|
||||
})
|
||||
|
||||
suite.Equal("GNU", filter.Keywords[0].Keyword)
|
||||
suite.True(filter.Keywords[0].WholeWord)
|
||||
|
||||
suite.Equal("Linux", filter.Keywords[1].Keyword)
|
||||
suite.False(filter.Keywords[1].WholeWord)
|
||||
}
|
||||
|
||||
if suite.Len(filter.Statuses, 2) {
|
||||
slices.SortFunc(filter.Statuses, func(lhs, rhs apimodel.FilterStatus) int {
|
||||
return strings.Compare(lhs.StatusID, rhs.StatusID)
|
||||
})
|
||||
|
||||
suite.Equal("01HEN2QRFA8H3C6QPN7RD4KSR6", filter.Statuses[0].StatusID)
|
||||
|
||||
suite.Equal("01HEWV37MHV8BAC8ANFGVRRM5D", filter.Statuses[1].StatusID)
|
||||
}
|
||||
|
||||
suite.checkStreamed(homeStream, true, "", stream.EventTypeFiltersChanged)
|
||||
}
|
||||
@ -171,7 +245,7 @@ func (suite *FiltersTestSuite) TestPostFilterMinimal() {
|
||||
|
||||
title := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
filter, err := suite.postFilter(&title, &context, nil, nil, nil, http.StatusOK, "")
|
||||
filter, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -193,7 +267,7 @@ func (suite *FiltersTestSuite) TestPostFilterMinimal() {
|
||||
func (suite *FiltersTestSuite) TestPostFilterEmptyTitle() {
|
||||
title := ""
|
||||
context := []string{"home"}
|
||||
_, err := suite.postFilter(&title, &context, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
_, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -201,7 +275,7 @@ func (suite *FiltersTestSuite) TestPostFilterEmptyTitle() {
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterMissingTitle() {
|
||||
context := []string{"home"}
|
||||
_, err := suite.postFilter(nil, &context, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
_, err := suite.postFilter(nil, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -210,7 +284,7 @@ func (suite *FiltersTestSuite) TestPostFilterMissingTitle() {
|
||||
func (suite *FiltersTestSuite) TestPostFilterEmptyContext() {
|
||||
title := "GNU/Linux"
|
||||
context := []string{}
|
||||
_, err := suite.postFilter(&title, &context, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
_, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -218,7 +292,7 @@ func (suite *FiltersTestSuite) TestPostFilterEmptyContext() {
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterMissingContext() {
|
||||
title := "GNU/Linux"
|
||||
_, err := suite.postFilter(&title, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
_, err := suite.postFilter(&title, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -227,7 +301,7 @@ func (suite *FiltersTestSuite) TestPostFilterMissingContext() {
|
||||
// Creating another filter with the same title should fail.
|
||||
func (suite *FiltersTestSuite) TestPostFilterTitleConflict() {
|
||||
title := suite.testFilters["local_account_1_filter_1"].Title
|
||||
_, err := suite.postFilter(&title, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
_, err := suite.postFilter(&title, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -68,6 +69,30 @@ import (
|
||||
// minLength: 1
|
||||
// maxLength: 200
|
||||
// -
|
||||
// name: keywords_attributes[][keyword]
|
||||
// in: formData
|
||||
// type: array
|
||||
// items:
|
||||
// type: string
|
||||
// description: Keywords to be added to the created filter.
|
||||
// collectionFormat: multi
|
||||
// -
|
||||
// name: keywords_attributes[][whole_word]
|
||||
// in: formData
|
||||
// type: array
|
||||
// items:
|
||||
// type: boolean
|
||||
// description: Should each keyword consider word boundaries?
|
||||
// collectionFormat: multi
|
||||
// -
|
||||
// name: statuses_attributes[][status_id]
|
||||
// in: formData
|
||||
// type: array
|
||||
// items:
|
||||
// type: string
|
||||
// description: Statuses to be added to the newly created filter.
|
||||
// collectionFormat: multi
|
||||
// -
|
||||
// name: context[]
|
||||
// in: formData
|
||||
// required: true
|
||||
@ -183,6 +208,58 @@ func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse form variant of normal filter keyword update structs.
|
||||
// All filter keyword update struct fields are optional.
|
||||
numFormKeywords := max(
|
||||
len(form.KeywordsAttributesID),
|
||||
len(form.KeywordsAttributesKeyword),
|
||||
len(form.KeywordsAttributesWholeWord),
|
||||
len(form.KeywordsAttributesDestroy),
|
||||
)
|
||||
if numFormKeywords > 0 {
|
||||
form.Keywords = make([]apimodel.FilterKeywordCreateUpdateDeleteRequest, 0, numFormKeywords)
|
||||
for i := 0; i < numFormKeywords; i++ {
|
||||
formKeyword := apimodel.FilterKeywordCreateUpdateDeleteRequest{}
|
||||
if i < len(form.KeywordsAttributesID) && form.KeywordsAttributesID[i] != "" {
|
||||
formKeyword.ID = &form.KeywordsAttributesID[i]
|
||||
}
|
||||
if i < len(form.KeywordsAttributesKeyword) && form.KeywordsAttributesKeyword[i] != "" {
|
||||
formKeyword.Keyword = &form.KeywordsAttributesKeyword[i]
|
||||
}
|
||||
if i < len(form.KeywordsAttributesWholeWord) {
|
||||
formKeyword.WholeWord = &form.KeywordsAttributesWholeWord[i]
|
||||
}
|
||||
if i < len(form.KeywordsAttributesDestroy) {
|
||||
formKeyword.Destroy = &form.KeywordsAttributesDestroy[i]
|
||||
}
|
||||
form.Keywords = append(form.Keywords, formKeyword)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse form variant of normal filter status update structs.
|
||||
// All filter status update struct fields are optional.
|
||||
numFormStatuses := max(
|
||||
len(form.StatusesAttributesID),
|
||||
len(form.StatusesAttributesStatusID),
|
||||
len(form.StatusesAttributesDestroy),
|
||||
)
|
||||
if numFormStatuses > 0 {
|
||||
form.Statuses = make([]apimodel.FilterStatusCreateDeleteRequest, 0, numFormStatuses)
|
||||
for i := 0; i < numFormStatuses; i++ {
|
||||
formStatus := apimodel.FilterStatusCreateDeleteRequest{}
|
||||
if i < len(form.StatusesAttributesID) && form.StatusesAttributesID[i] != "" {
|
||||
formStatus.ID = &form.StatusesAttributesID[i]
|
||||
}
|
||||
if i < len(form.StatusesAttributesStatusID) && form.StatusesAttributesStatusID[i] != "" {
|
||||
formStatus.StatusID = &form.StatusesAttributesStatusID[i]
|
||||
}
|
||||
if i < len(form.StatusesAttributesDestroy) {
|
||||
formStatus.Destroy = &form.StatusesAttributesDestroy[i]
|
||||
}
|
||||
form.Statuses = append(form.Statuses, formStatus)
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize filter expiry if necessary.
|
||||
// If we parsed this as JSON, expires_in
|
||||
// may be either a float64 or a string.
|
||||
@ -204,5 +281,42 @@ func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize and validate updates.
|
||||
for i, formKeyword := range form.Keywords {
|
||||
if formKeyword.Keyword != nil {
|
||||
if err := validate.FilterKeyword(*formKeyword.Keyword); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
destroy := util.PtrValueOr(formKeyword.Destroy, false)
|
||||
form.Keywords[i].Destroy = &destroy
|
||||
|
||||
if destroy && formKeyword.ID == nil {
|
||||
return errors.New("can't delete a filter keyword without an ID")
|
||||
} else if formKeyword.ID == nil && formKeyword.Keyword == nil {
|
||||
return errors.New("can't create a filter keyword without a keyword")
|
||||
}
|
||||
}
|
||||
for i, formStatus := range form.Statuses {
|
||||
if formStatus.StatusID != nil {
|
||||
if err := validate.ULID(*formStatus.StatusID, "status_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
destroy := util.PtrValueOr(formStatus.Destroy, false)
|
||||
form.Statuses[i].Destroy = &destroy
|
||||
|
||||
switch {
|
||||
case destroy && formStatus.ID == nil:
|
||||
return errors.New("can't delete a filter status without an ID")
|
||||
case formStatus.ID != nil:
|
||||
return errors.New("filter status IDs here can only be used to delete them")
|
||||
case formStatus.StatusID == nil:
|
||||
return errors.New("can't create a filter status without a status ID")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -35,7 +36,7 @@ import (
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context *[]string, action *string, expiresIn *int, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) {
|
||||
func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context *[]string, action *string, expiresIn *int, keywordsAttributesID *[]string, keywordsAttributesKeyword *[]string, keywordsAttributesWholeWord *[]bool, keywordsAttributesDestroy *[]bool, statusesAttributesID *[]string, statusesAttributesStatusID *[]string, statusesAttributesDestroy *[]bool, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
@ -64,6 +65,39 @@ func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context
|
||||
if expiresIn != nil {
|
||||
ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)}
|
||||
}
|
||||
if keywordsAttributesID != nil {
|
||||
ctx.Request.Form["keywords_attributes[][id]"] = *keywordsAttributesID
|
||||
}
|
||||
if keywordsAttributesKeyword != nil {
|
||||
ctx.Request.Form["keywords_attributes[][keyword]"] = *keywordsAttributesKeyword
|
||||
}
|
||||
if keywordsAttributesWholeWord != nil {
|
||||
formatted := []string{}
|
||||
for _, value := range *keywordsAttributesWholeWord {
|
||||
formatted = append(formatted, strconv.FormatBool(value))
|
||||
}
|
||||
ctx.Request.Form["keywords_attributes[][whole_word]"] = formatted
|
||||
}
|
||||
if keywordsAttributesWholeWord != nil {
|
||||
formatted := []string{}
|
||||
for _, value := range *keywordsAttributesDestroy {
|
||||
formatted = append(formatted, strconv.FormatBool(value))
|
||||
}
|
||||
ctx.Request.Form["keywords_attributes[][_destroy]"] = formatted
|
||||
}
|
||||
if statusesAttributesID != nil {
|
||||
ctx.Request.Form["statuses_attributes[][id]"] = *statusesAttributesID
|
||||
}
|
||||
if statusesAttributesStatusID != nil {
|
||||
ctx.Request.Form["statuses_attributes[][status_id]"] = *statusesAttributesStatusID
|
||||
}
|
||||
if statusesAttributesDestroy != nil {
|
||||
formatted := []string{}
|
||||
for _, value := range *statusesAttributesDestroy {
|
||||
formatted = append(formatted, strconv.FormatBool(value))
|
||||
}
|
||||
ctx.Request.Form["statuses_attributes[][_destroy]"] = formatted
|
||||
}
|
||||
}
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
@ -114,7 +148,18 @@ func (suite *FiltersTestSuite) TestPutFilterFull() {
|
||||
context := []string{"home", "public"}
|
||||
action := "hide"
|
||||
expiresIn := 86400
|
||||
filter, err := suite.putFilter(id, &title, &context, &action, &expiresIn, nil, http.StatusOK, "")
|
||||
// Tests attributes arrays that aren't the same length, just in case.
|
||||
keywordsAttributesID := []string{
|
||||
suite.testFilterKeywords["local_account_1_filter_2_keyword_1"].ID,
|
||||
suite.testFilterKeywords["local_account_1_filter_2_keyword_2"].ID,
|
||||
}
|
||||
keywordsAttributesKeyword := []string{"fū", "", "blah"}
|
||||
// If using the form version of this API, you have to always set whole_word to the previous value for that keyword;
|
||||
// there's no way to represent a nullable boolean in it.
|
||||
keywordsAttributesWholeWord := []bool{true, false, true}
|
||||
keywordsAttributesDestroy := []bool{false, true}
|
||||
statusesAttributesStatusID := []string{suite.testStatuses["remote_account_1_status_2"].ID}
|
||||
filter, err := suite.putFilter(id, &title, &context, &action, &expiresIn, &keywordsAttributesID, &keywordsAttributesKeyword, &keywordsAttributesWholeWord, &keywordsAttributesDestroy, nil, &statusesAttributesStatusID, nil, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -129,8 +174,29 @@ func (suite *FiltersTestSuite) TestPutFilterFull() {
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Len(filter.Keywords, 3)
|
||||
suite.Len(filter.Statuses, 0)
|
||||
|
||||
if suite.Len(filter.Keywords, 3) {
|
||||
slices.SortFunc(filter.Keywords, func(lhs, rhs apimodel.FilterKeyword) int {
|
||||
return strings.Compare(lhs.ID, rhs.ID)
|
||||
})
|
||||
|
||||
suite.Equal("fū", filter.Keywords[0].Keyword)
|
||||
suite.True(filter.Keywords[0].WholeWord)
|
||||
|
||||
suite.Equal("quux", filter.Keywords[1].Keyword)
|
||||
suite.True(filter.Keywords[1].WholeWord)
|
||||
|
||||
suite.Equal("blah", filter.Keywords[2].Keyword)
|
||||
suite.True(filter.Keywords[1].WholeWord)
|
||||
}
|
||||
|
||||
if suite.Len(filter.Statuses, 1) {
|
||||
slices.SortFunc(filter.Statuses, func(lhs, rhs apimodel.FilterStatus) int {
|
||||
return strings.Compare(lhs.ID, rhs.ID)
|
||||
})
|
||||
|
||||
suite.Equal(suite.testStatuses["remote_account_1_status_2"].ID, filter.Statuses[0].StatusID)
|
||||
}
|
||||
|
||||
suite.checkStreamed(homeStream, true, "", stream.EventTypeFiltersChanged)
|
||||
}
|
||||
@ -144,9 +210,28 @@ func (suite *FiltersTestSuite) TestPutFilterFullJSON() {
|
||||
"title": "messy synoptic varblabbles",
|
||||
"context": ["home", "public"],
|
||||
"filter_action": "hide",
|
||||
"expires_in": 86400.1
|
||||
"expires_in": 86400.1,
|
||||
"keywords_attributes": [
|
||||
{
|
||||
"id": "01HN277Y11ENG4EC1ERMAC9FH4",
|
||||
"keyword": "fū"
|
||||
},
|
||||
{
|
||||
"id": "01HN278494N88BA2FY4DZ5JTNS",
|
||||
"_destroy": true
|
||||
},
|
||||
{
|
||||
"keyword": "blah",
|
||||
"whole_word": true
|
||||
}
|
||||
],
|
||||
"statuses_attributes": [
|
||||
{
|
||||
"status_id": "01HEN2QRFA8H3C6QPN7RD4KSR6"
|
||||
}
|
||||
]
|
||||
}`
|
||||
filter, err := suite.putFilter(id, nil, nil, nil, nil, &requestJson, http.StatusOK, "")
|
||||
filter, err := suite.putFilter(id, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -163,8 +248,29 @@ func (suite *FiltersTestSuite) TestPutFilterFullJSON() {
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Len(filter.Keywords, 3)
|
||||
suite.Len(filter.Statuses, 0)
|
||||
|
||||
if suite.Len(filter.Keywords, 3) {
|
||||
slices.SortFunc(filter.Keywords, func(lhs, rhs apimodel.FilterKeyword) int {
|
||||
return strings.Compare(lhs.ID, rhs.ID)
|
||||
})
|
||||
|
||||
suite.Equal("fū", filter.Keywords[0].Keyword)
|
||||
suite.True(filter.Keywords[0].WholeWord)
|
||||
|
||||
suite.Equal("quux", filter.Keywords[1].Keyword)
|
||||
suite.True(filter.Keywords[1].WholeWord)
|
||||
|
||||
suite.Equal("blah", filter.Keywords[2].Keyword)
|
||||
suite.True(filter.Keywords[1].WholeWord)
|
||||
}
|
||||
|
||||
if suite.Len(filter.Statuses, 1) {
|
||||
slices.SortFunc(filter.Statuses, func(lhs, rhs apimodel.FilterStatus) int {
|
||||
return strings.Compare(lhs.ID, rhs.ID)
|
||||
})
|
||||
|
||||
suite.Equal("01HEN2QRFA8H3C6QPN7RD4KSR6", filter.Statuses[0].StatusID)
|
||||
}
|
||||
|
||||
suite.checkStreamed(homeStream, true, "", stream.EventTypeFiltersChanged)
|
||||
}
|
||||
@ -175,7 +281,7 @@ func (suite *FiltersTestSuite) TestPutFilterMinimal() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
filter, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusOK, "")
|
||||
filter, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -196,7 +302,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyTitle() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := ""
|
||||
context := []string{"home"}
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter title must be provided, and must be no more than 200 chars"}`)
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter title must be provided, and must be no more than 200 chars"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -206,7 +312,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyContext() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := "GNU/Linux"
|
||||
context := []string{}
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: at least one filter context is required"}`)
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: at least one filter context is required"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -216,7 +322,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyContext() {
|
||||
func (suite *FiltersTestSuite) TestPutFilterTitleConflict() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := suite.testFilters["local_account_1_filter_2"].Title
|
||||
_, err := suite.putFilter(id, &title, nil, nil, nil, nil, http.StatusConflict, `{"error":"Conflict: you already have a filter with this title"}`)
|
||||
_, err := suite.putFilter(id, &title, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusConflict, `{"error":"Conflict: you already have a filter with this title"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -226,7 +332,7 @@ func (suite *FiltersTestSuite) TestPutAnotherAccountsFilter() {
|
||||
id := suite.testFilters["local_account_2_filter_1"].ID
|
||||
title := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
@ -236,7 +342,7 @@ func (suite *FiltersTestSuite) TestPutNonexistentFilter() {
|
||||
id := "not_even_a_real_ULID"
|
||||
phrase := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
Reference in New Issue
Block a user