summaryrefslogtreecommitdiff
path: root/internal/api/client/search
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2023-06-21 18:26:40 +0200
committerLibravatar GitHub <noreply@github.com>2023-06-21 17:26:40 +0100
commit831ae09f8bab04af854243421047371339c3e190 (patch)
treef7709d478cc363dc1899bdb658fe20e2dc7986f3 /internal/api/client/search
parent[docs] Disambiguate docker version, don't recommend opening localhost (#1913) (diff)
downloadgotosocial-831ae09f8bab04af854243421047371339c3e190.tar.xz
[feature] Add partial text search for accounts + statuses (#1836)
Diffstat (limited to 'internal/api/client/search')
-rw-r--r--internal/api/client/search/search.go35
-rw-r--r--internal/api/client/search/searchget.go195
-rw-r--r--internal/api/client/search/searchget_test.go1046
3 files changed, 1096 insertions, 180 deletions
diff --git a/internal/api/client/search/search.go b/internal/api/client/search/search.go
index eaa3102f9..219e30280 100644
--- a/internal/api/client/search/search.go
+++ b/internal/api/client/search/search.go
@@ -25,39 +25,8 @@ import (
)
const (
- // BasePathV1 is the base path for serving v1 of the search API, minus the 'api' prefix
- BasePathV1 = "/v1/search"
-
- // BasePathV2 is the base path for serving v2 of the search API, minus the 'api' prefix
- BasePathV2 = "/v2/search"
-
- // AccountIDKey -- If provided, statuses returned will be authored only by this account
- AccountIDKey = "account_id"
- // MaxIDKey -- Return results older than this id
- MaxIDKey = "max_id"
- // MinIDKey -- Return results immediately newer than this id
- MinIDKey = "min_id"
- // TypeKey -- Enum(accounts, hashtags, statuses)
- TypeKey = "type"
- // ExcludeUnreviewedKey -- Filter out unreviewed tags? Defaults to false. Use true when trying to find trending tags.
- ExcludeUnreviewedKey = "exclude_unreviewed"
- // QueryKey -- The search query
- QueryKey = "q"
- // ResolveKey -- Attempt WebFinger lookup. Defaults to false.
- ResolveKey = "resolve"
- // LimitKey -- Maximum number of results to load, per type. Defaults to 20. Max 40.
- LimitKey = "limit"
- // OffsetKey -- Offset in search results. Used for pagination. Defaults to 0.
- OffsetKey = "offset"
- // FollowingKey -- Only include accounts that the user is following. Defaults to false.
- FollowingKey = "following"
-
- // TypeAccounts --
- TypeAccounts = "accounts"
- // TypeHashtags --
- TypeHashtags = "hashtags"
- // TypeStatuses --
- TypeStatuses = "statuses"
+ BasePathV1 = "/v1/search" // Base path for serving v1 of the search API, minus the 'api' prefix.
+ BasePathV2 = "/v2/search" // Base path for serving v2 of the search API, minus the 'api' prefix.
)
type Module struct {
diff --git a/internal/api/client/search/searchget.go b/internal/api/client/search/searchget.go
index d129bf4d6..33a90e078 100644
--- a/internal/api/client/search/searchget.go
+++ b/internal/api/client/search/searchget.go
@@ -18,10 +18,7 @@
package search
import (
- "errors"
- "fmt"
"net/http"
- "strconv"
"github.com/gin-gonic/gin"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
@@ -40,6 +37,98 @@ import (
// tags:
// - search
//
+// produces:
+// - application/json
+//
+// parameters:
+// -
+// name: max_id
+// type: string
+// description: >-
+// Return only items *OLDER* than the given max ID.
+// The item with the specified ID will not be included in the response.
+// Currently only used if 'type' is set to a specific type.
+// in: query
+// required: false
+// -
+// name: min_id
+// type: string
+// description: >-
+// Return only items *immediately newer* than the given min ID.
+// The item with the specified ID will not be included in the response.
+// Currently only used if 'type' is set to a specific type.
+// in: query
+// required: false
+// -
+// name: limit
+// type: integer
+// description: Number of each type of item to return.
+// default: 20
+// maximum: 40
+// minimum: 1
+// in: query
+// required: false
+// -
+// name: offset
+// type: integer
+// description: >-
+// Page number of results to return (starts at 0).
+// This parameter is currently not used, page by selecting
+// a specific query type and using maxID and minID instead.
+// default: 0
+// maximum: 10
+// minimum: 0
+// in: query
+// required: false
+// -
+// name: q
+// type: string
+// description: |-
+// Query string to search for. This can be in the following forms:
+// - `@[username]` -- search for an account with the given username on any domain. Can return multiple results.
+// - @[username]@[domain]` -- search for a remote account with exact username and domain. Will only ever return 1 result at most.
+// - `https://example.org/some/arbitrary/url` -- search for an account OR a status with the given URL. Will only ever return 1 result at most.
+// - any arbitrary string -- search for accounts or statuses containing the given string. Can return multiple results.
+// in: query
+// required: true
+// -
+// name: type
+// type: string
+// description: |-
+// Type of item to return. One of:
+// - `` -- empty string; return any/all results.
+// - `accounts` -- return account(s).
+// - `statuses` -- return status(es).
+// - `hashtags` -- return hashtag(s).
+// If `type` is specified, paging can be performed using max_id and min_id parameters.
+// If `type` is not specified, see the `offset` parameter for paging.
+// in: query
+// -
+// name: resolve
+// type: boolean
+// description: >-
+// If searching query is for `@[username]@[domain]`, or a URL, allow the GoToSocial
+// instance to resolve the search by making calls to remote instances (webfinger, ActivityPub, etc).
+// default: false
+// in: query
+// -
+// name: following
+// type: boolean
+// description: >-
+// If search type includes accounts, and search query is an arbitrary string, show only accounts
+// that the requesting account follows. If this is set to `true`, then the GoToSocial instance will
+// enhance the search by also searching within account notes, not just in usernames and display names.
+// default: false
+// in: query
+// -
+// name: exclude_unreviewed
+// type: boolean
+// description: >-
+// If searching for hashtags, exclude those not yet approved by instance admin.
+// Currently this parameter is unused.
+// default: false
+// in: query
+//
// security:
// - OAuth2 Bearer:
// - read:search
@@ -74,93 +163,55 @@ func (m *Module) SearchGETHandler(c *gin.Context) {
return
}
- excludeUnreviewed := false
- excludeUnreviewedString := c.Query(ExcludeUnreviewedKey)
- if excludeUnreviewedString != "" {
- var err error
- excludeUnreviewed, err = strconv.ParseBool(excludeUnreviewedString)
- if err != nil {
- err := fmt.Errorf("error parsing %s: %s", ExcludeUnreviewedKey, err)
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
+ limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 20, 40, 1)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
}
- query := c.Query(QueryKey)
- if query == "" {
- err := errors.New("query parameter q was empty")
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
+ offset, errWithCode := apiutil.ParseSearchOffset(c.Query(apiutil.SearchOffsetKey), 0, 10, 0)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
- resolve := false
- resolveString := c.Query(ResolveKey)
- if resolveString != "" {
- var err error
- resolve, err = strconv.ParseBool(resolveString)
- if err != nil {
- err := fmt.Errorf("error parsing %s: %s", ResolveKey, err)
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
+ query, errWithCode := apiutil.ParseSearchQuery(c.Query(apiutil.SearchQueryKey))
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
}
- limit := 2
- limitString := c.Query(LimitKey)
- if limitString != "" {
- i, err := strconv.ParseInt(limitString, 10, 32)
- if err != nil {
- err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
- limit = int(i)
- }
- if limit > 40 {
- limit = 40
- }
- if limit < 1 {
- limit = 1
+ resolve, errWithCode := apiutil.ParseSearchResolve(c.Query(apiutil.SearchResolveKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
}
- offset := 0
- offsetString := c.Query(OffsetKey)
- if offsetString != "" {
- i, err := strconv.ParseInt(offsetString, 10, 32)
- if err != nil {
- err := fmt.Errorf("error parsing %s: %s", OffsetKey, err)
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
- offset = int(i)
+ following, errWithCode := apiutil.ParseSearchFollowing(c.Query(apiutil.SearchFollowingKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
}
- following := false
- followingString := c.Query(FollowingKey)
- if followingString != "" {
- var err error
- following, err = strconv.ParseBool(followingString)
- if err != nil {
- err := fmt.Errorf("error parsing %s: %s", FollowingKey, err)
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
+ excludeUnreviewed, errWithCode := apiutil.ParseSearchExcludeUnreviewed(c.Query(apiutil.SearchExcludeUnreviewedKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
}
- searchQuery := &apimodel.SearchQuery{
- AccountID: c.Query(AccountIDKey),
- MaxID: c.Query(MaxIDKey),
- MinID: c.Query(MinIDKey),
- Type: c.Query(TypeKey),
- ExcludeUnreviewed: excludeUnreviewed,
- Query: query,
- Resolve: resolve,
+ searchRequest := &apimodel.SearchRequest{
+ MaxID: c.Query(apiutil.MaxIDKey),
+ MinID: c.Query(apiutil.MinIDKey),
Limit: limit,
Offset: offset,
+ Query: query,
+ QueryType: c.Query(apiutil.SearchTypeKey),
+ Resolve: resolve,
Following: following,
+ ExcludeUnreviewed: excludeUnreviewed,
}
- results, errWithCode := m.processor.SearchGet(c.Request.Context(), authed, searchQuery)
+ results, errWithCode := m.processor.Search().Get(c.Request.Context(), authed.Account, searchRequest)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
diff --git a/internal/api/client/search/searchget_test.go b/internal/api/client/search/searchget_test.go
index fe817099f..9e0a8eb67 100644
--- a/internal/api/client/search/searchget_test.go
+++ b/internal/api/client/search/searchget_test.go
@@ -18,55 +18,174 @@
package search_test
import (
+ "context"
"encoding/json"
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"net/http/httptest"
+ "net/url"
+ "strconv"
+ "strings"
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/search"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/testrig"
)
type SearchGetTestSuite struct {
SearchStandardTestSuite
}
-func (suite *SearchGetTestSuite) testSearch(query string, resolve bool, expectedHTTPStatus int) (*apimodel.SearchResult, error) {
- requestPath := fmt.Sprintf("%s?q=%s&resolve=%t", search.BasePathV1, query, resolve)
- recorder := httptest.NewRecorder()
+func (suite *SearchGetTestSuite) getSearch(
+ requestingAccount *gtsmodel.Account,
+ token *gtsmodel.Token,
+ user *gtsmodel.User,
+ maxID *string,
+ minID *string,
+ limit *int,
+ offset *int,
+ query string,
+ queryType *string,
+ resolve *bool,
+ following *bool,
+ expectedHTTPStatus int,
+ expectedBody string,
+) (*apimodel.SearchResult, error) {
+ var (
+ recorder = httptest.NewRecorder()
+ ctx, _ = testrig.CreateGinTestContext(recorder, nil)
+ requestURL = testrig.URLMustParse("/api" + search.BasePathV1)
+ queryParts []string
+ )
- ctx := suite.newContext(recorder, requestPath)
+ // Put the request together.
+ if maxID != nil {
+ queryParts = append(queryParts, apiutil.MaxIDKey+"="+url.QueryEscape(*maxID))
+ }
+
+ if minID != nil {
+ queryParts = append(queryParts, apiutil.MinIDKey+"="+url.QueryEscape(*minID))
+ }
+
+ if limit != nil {
+ queryParts = append(queryParts, apiutil.LimitKey+"="+strconv.Itoa(*limit))
+ }
+ if offset != nil {
+ queryParts = append(queryParts, apiutil.SearchOffsetKey+"="+strconv.Itoa(*offset))
+ }
+
+ queryParts = append(queryParts, apiutil.SearchQueryKey+"="+url.QueryEscape(query))
+
+ if queryType != nil {
+ queryParts = append(queryParts, apiutil.SearchTypeKey+"="+url.QueryEscape(*queryType))
+ }
+
+ if resolve != nil {
+ queryParts = append(queryParts, apiutil.SearchResolveKey+"="+strconv.FormatBool(*resolve))
+ }
+
+ if following != nil {
+ queryParts = append(queryParts, apiutil.SearchFollowingKey+"="+strconv.FormatBool(*following))
+ }
+
+ requestURL.RawQuery = strings.Join(queryParts, "&")
+ ctx.Request = httptest.NewRequest(http.MethodGet, requestURL.String(), nil)
+ ctx.Set(oauth.SessionAuthorizedAccount, requestingAccount)
+ ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(token))
+ ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
+ ctx.Set(oauth.SessionAuthorizedUser, user)
+
+ // Trigger the function being tested.
suite.searchModule.SearchGETHandler(ctx)
+ // Read the result.
result := recorder.Result()
defer result.Body.Close()
+ b, err := io.ReadAll(result.Body)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ errs := gtserror.MultiError{}
+
+ // Check expected code + body.
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
- return nil, fmt.Errorf("expected %d got %d", expectedHTTPStatus, resultCode)
+ errs = append(errs, fmt.Sprintf("expected %d got %d", expectedHTTPStatus, resultCode))
}
- b, err := ioutil.ReadAll(result.Body)
- if err != nil {
- return nil, err
+ // If we got an expected body, return early.
+ if expectedBody != "" && string(b) != expectedBody {
+ errs = append(errs, fmt.Sprintf("expected %s got %s", expectedBody, string(b)))
+ }
+
+ if err := errs.Combine(); err != nil {
+ suite.FailNow("", "%v (body %s)", err, string(b))
}
searchResult := &apimodel.SearchResult{}
if err := json.Unmarshal(b, searchResult); err != nil {
- return nil, err
+ suite.FailNow(err.Error())
}
return searchResult, nil
}
+func (suite *SearchGetTestSuite) bodgeLocalInstance(domain string) {
+ // Set new host.
+ config.SetHost(domain)
+
+ // Copy instance account to not mess up other tests.
+ instanceAccount := &gtsmodel.Account{}
+ *instanceAccount = *suite.testAccounts["instance_account"]
+
+ // Set username of instance account to given domain.
+ instanceAccount.Username = domain
+ if err := suite.db.UpdateAccount(context.Background(), instanceAccount, "username"); err != nil {
+ suite.FailNow(err.Error())
+ }
+}
+
func (suite *SearchGetTestSuite) TestSearchRemoteAccountByURI() {
- query := "https://unknown-instance.com/users/brand_new_person"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "https://unknown-instance.com/users/brand_new_person"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -80,10 +199,36 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByURI() {
}
func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestring() {
- query := "@brand_new_person@unknown-instance.com"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "@brand_new_person@unknown-instance.com"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -97,10 +242,36 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestring() {
}
func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringUppercase() {
- query := "@Some_User@example.org"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "@Some_User@example.org"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -114,10 +285,36 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringUppercase()
}
func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoLeadingAt() {
- query := "brand_new_person@unknown-instance.com"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "brand_new_person@unknown-instance.com"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -131,10 +328,36 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoLeadingAt(
}
func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoResolve() {
- query := "@brand_new_person@unknown-instance.com"
- resolve := false
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@brand_new_person@unknown-instance.com"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -143,10 +366,36 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoResolve()
}
func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialChars() {
- query := "@üser@ëxample.org"
- resolve := false
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@üser@ëxample.org"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -158,10 +407,36 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialChars
}
func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialCharsPunycode() {
- query := "@üser@xn--xample-ova.org"
- resolve := false
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@üser@xn--xample-ova.org"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -173,10 +448,36 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialChars
}
func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestring() {
- query := "@the_mighty_zork"
- resolve := false
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@the_mighty_zork"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -190,10 +491,36 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestring() {
}
func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestringWithDomain() {
- query := "@the_mighty_zork@localhost:8080"
- resolve := false
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@the_mighty_zork@localhost:8080"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -207,10 +534,36 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestringWithDomain()
}
func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByNamestringResolveTrue() {
- query := "@somone_made_up@localhost:8080"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "@somone_made_up@localhost:8080"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -219,27 +572,36 @@ func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByNamestringRe
}
func (suite *SearchGetTestSuite) TestSearchLocalAccountByURI() {
- query := "http://localhost:8080/users/the_mighty_zork"
- resolve := false
-
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
- if err != nil {
- suite.FailNow(err.Error())
- }
-
- if !suite.Len(searchResult.Accounts, 1) {
- suite.FailNow("expected 1 account in search results but got 0")
- }
-
- gotAccount := searchResult.Accounts[0]
- suite.NotNil(gotAccount)
-}
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "http://localhost:8080/users/the_mighty_zork"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
-func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountByURI() {
- query := "http://localhost:8080/users/localhost:8080"
- resolve := false
-
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -253,10 +615,36 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountByURI() {
}
func (suite *SearchGetTestSuite) TestSearchLocalAccountByURL() {
- query := "http://localhost:8080/@the_mighty_zork"
- resolve := false
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "http://localhost:8080/@the_mighty_zork"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -270,10 +658,36 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByURL() {
}
func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByURL() {
- query := "http://localhost:8080/@the_shmighty_shmork"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "http://localhost:8080/@the_shmighty_shmork"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -282,10 +696,36 @@ func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByURL() {
}
func (suite *SearchGetTestSuite) TestSearchStatusByURL() {
- query := "https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042"
+ queryType *string = func() *string { i := "statuses"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -299,10 +739,36 @@ func (suite *SearchGetTestSuite) TestSearchStatusByURL() {
}
func (suite *SearchGetTestSuite) TestSearchBlockedDomainURL() {
- query := "https://replyguys.com/@someone"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "https://replyguys.com/@someone"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -313,10 +779,322 @@ func (suite *SearchGetTestSuite) TestSearchBlockedDomainURL() {
}
func (suite *SearchGetTestSuite) TestSearchBlockedDomainNamestring() {
- query := "@someone@replyguys.com"
- resolve := true
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "@someone@replyguys.com"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(searchResult.Accounts, 0)
+ suite.Len(searchResult.Statuses, 0)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchAAny() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "a"
+ queryType *string = nil // Return anything.
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(searchResult.Accounts, 5)
+ suite.Len(searchResult.Statuses, 4)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchAAnyFollowingOnly() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "a"
+ queryType *string = nil // Return anything.
+ following *bool = func() *bool { i := true; return &i }()
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
- searchResult, err := suite.testSearch(query, resolve, http.StatusOK)
+ suite.Len(searchResult.Accounts, 2)
+ suite.Len(searchResult.Statuses, 4)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchAStatuses() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "a"
+ queryType *string = func() *string { i := "statuses"; return &i }() // Only statuses.
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(searchResult.Accounts, 0)
+ suite.Len(searchResult.Statuses, 4)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchAAccounts() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "a"
+ queryType *string = func() *string { i := "accounts"; return &i }() // Only accounts.
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(searchResult.Accounts, 5)
+ suite.Len(searchResult.Statuses, 0)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchAAccountsLimit1() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = func() *int { i := 1; return &i }()
+ offset *int = nil
+ resolve *bool = func() *bool { i := true; return &i }()
+ query = "a"
+ queryType *string = func() *string { i := "accounts"; return &i }() // Only accounts.
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(searchResult.Accounts, 1)
+ suite.Len(searchResult.Statuses, 0)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountByURI() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "http://localhost:8080/users/localhost:8080"
+ queryType *string = func() *string { i := "accounts"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(searchResult.Accounts, 0)
+ suite.Len(searchResult.Statuses, 0)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchInstanceAccountFull() {
+ // Namestring excludes ':' in usernames, so we
+ // need to fiddle with the instance account a
+ // bit to get it to look like a different domain.
+ newDomain := "example.org"
+ suite.bodgeLocalInstance(newDomain)
+
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@" + newDomain + "@" + newDomain
+ queryType *string = nil
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
if err != nil {
suite.FailNow(err.Error())
}
@@ -326,6 +1104,124 @@ func (suite *SearchGetTestSuite) TestSearchBlockedDomainNamestring() {
suite.Len(searchResult.Hashtags, 0)
}
+func (suite *SearchGetTestSuite) TestSearchInstanceAccountPartial() {
+ // Namestring excludes ':' in usernames, so we
+ // need to fiddle with the instance account a
+ // bit to get it to look like a different domain.
+ newDomain := "example.org"
+ suite.bodgeLocalInstance(newDomain)
+
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@" + newDomain
+ queryType *string = nil
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ searchResult, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Len(searchResult.Accounts, 0)
+ suite.Len(searchResult.Statuses, 0)
+ suite.Len(searchResult.Hashtags, 0)
+}
+
+func (suite *SearchGetTestSuite) TestSearchBadQueryType() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "whatever"
+ queryType *string = func() *string { i := "aaaaaaaaaaa"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusBadRequest
+ expectedBody = `{"error":"Bad Request: search query type aaaaaaaaaaa was not recognized, valid options are ['', 'accounts', 'statuses', 'hashtags']"}`
+ )
+
+ _, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+}
+
+func (suite *SearchGetTestSuite) TestSearchEmptyQuery() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ maxID *string = nil
+ minID *string = nil
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = ""
+ queryType *string = func() *string { i := "aaaaaaaaaaa"; return &i }()
+ following *bool = nil
+ expectedHTTPStatus = http.StatusBadRequest
+ expectedBody = `{"error":"Bad Request: required key q was not set or had empty value"}`
+ )
+
+ _, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ maxID,
+ minID,
+ limit,
+ offset,
+ query,
+ queryType,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+}
+
func TestSearchGetTestSuite(t *testing.T) {
suite.Run(t, &SearchGetTestSuite{})
}