summaryrefslogtreecommitdiff
path: root/internal/api/client/accounts
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/client/accounts')
-rw-r--r--internal/api/client/accounts/accountdelete_test.go6
-rw-r--r--internal/api/client/accounts/accounts.go86
-rw-r--r--internal/api/client/accounts/accountupdate_test.go2
-rw-r--r--internal/api/client/accounts/lookup.go93
-rw-r--r--internal/api/client/accounts/search.go166
-rw-r--r--internal/api/client/accounts/search_test.go430
6 files changed, 728 insertions, 55 deletions
diff --git a/internal/api/client/accounts/accountdelete_test.go b/internal/api/client/accounts/accountdelete_test.go
index fe328487b..d8889b680 100644
--- a/internal/api/client/accounts/accountdelete_test.go
+++ b/internal/api/client/accounts/accountdelete_test.go
@@ -44,7 +44,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandler() {
}
bodyBytes := requestBody.Bytes()
recorder := httptest.NewRecorder()
- ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType())
+ ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeletePath, w.FormDataContentType())
// call the handler
suite.accountsModule.AccountDeletePOSTHandler(ctx)
@@ -66,7 +66,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerWrongPassword()
}
bodyBytes := requestBody.Bytes()
recorder := httptest.NewRecorder()
- ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType())
+ ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeletePath, w.FormDataContentType())
// call the handler
suite.accountsModule.AccountDeletePOSTHandler(ctx)
@@ -86,7 +86,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerNoPassword() {
}
bodyBytes := requestBody.Bytes()
recorder := httptest.NewRecorder()
- ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType())
+ ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeletePath, w.FormDataContentType())
// call the handler
suite.accountsModule.AccountDeletePOSTHandler(ctx)
diff --git a/internal/api/client/accounts/accounts.go b/internal/api/client/accounts/accounts.go
index 298104a8d..9bb13231d 100644
--- a/internal/api/client/accounts/accounts.go
+++ b/internal/api/client/accounts/accounts.go
@@ -25,53 +25,33 @@ import (
)
const (
- // LimitKey is for setting the return amount limit for eg., requesting an account's statuses
- LimitKey = "limit"
- // ExcludeRepliesKey is for specifying whether to exclude replies in a list of returned statuses by an account.
- ExcludeRepliesKey = "exclude_replies"
- // ExcludeReblogsKey is for specifying whether to exclude reblogs in a list of returned statuses by an account.
ExcludeReblogsKey = "exclude_reblogs"
- // PinnedKey is for specifying whether to include pinned statuses in a list of returned statuses by an account.
- PinnedKey = "pinned"
- // MaxIDKey is for specifying the maximum ID of the status to retrieve.
- MaxIDKey = "max_id"
- // MinIDKey is for specifying the minimum ID of the status to retrieve.
- MinIDKey = "min_id"
- // OnlyMediaKey is for specifying that only statuses with media should be returned in a list of returned statuses by an account.
- OnlyMediaKey = "only_media"
- // OnlyPublicKey is for specifying that only statuses with visibility public should be returned in a list of returned statuses by account.
- OnlyPublicKey = "only_public"
-
- // IDKey is the key to use for retrieving account ID in requests
- IDKey = "id"
- // BasePath is the base API path for this module, excluding the 'api' prefix
- BasePath = "/v1/accounts"
- // BasePathWithID is the base path for this module with the ID key
+ ExcludeRepliesKey = "exclude_replies"
+ LimitKey = "limit"
+ MaxIDKey = "max_id"
+ MinIDKey = "min_id"
+ OnlyMediaKey = "only_media"
+ OnlyPublicKey = "only_public"
+ PinnedKey = "pinned"
+
+ BasePath = "/v1/accounts"
+ IDKey = "id"
BasePathWithID = BasePath + "/:" + IDKey
- // VerifyPath is for verifying account credentials
- VerifyPath = BasePath + "/verify_credentials"
- // UpdateCredentialsPath is for updating account credentials
- UpdateCredentialsPath = BasePath + "/update_credentials"
- // GetStatusesPath is for showing an account's statuses
- GetStatusesPath = BasePathWithID + "/statuses"
- // GetFollowersPath is for showing an account's followers
- GetFollowersPath = BasePathWithID + "/followers"
- // GetFollowingPath is for showing account's that an account follows.
- GetFollowingPath = BasePathWithID + "/following"
- // GetRelationshipsPath is for showing an account's relationship with other accounts
- GetRelationshipsPath = BasePath + "/relationships"
- // FollowPath is for POSTing new follows to, and updating existing follows
- FollowPath = BasePathWithID + "/follow"
- // UnfollowPath is for POSTing an unfollow
- UnfollowPath = BasePathWithID + "/unfollow"
- // BlockPath is for creating a block of an account
- BlockPath = BasePathWithID + "/block"
- // UnblockPath is for removing a block of an account
- UnblockPath = BasePathWithID + "/unblock"
- // DeleteAccountPath is for deleting one's account via the API
- DeleteAccountPath = BasePath + "/delete"
- // ListsPath is for seeing which lists an account is.
- ListsPath = BasePathWithID + "/lists"
+
+ BlockPath = BasePathWithID + "/block"
+ DeletePath = BasePath + "/delete"
+ FollowersPath = BasePathWithID + "/followers"
+ FollowingPath = BasePathWithID + "/following"
+ FollowPath = BasePathWithID + "/follow"
+ ListsPath = BasePathWithID + "/lists"
+ LookupPath = BasePath + "/lookup"
+ RelationshipsPath = BasePath + "/relationships"
+ SearchPath = BasePath + "/search"
+ StatusesPath = BasePathWithID + "/statuses"
+ UnblockPath = BasePathWithID + "/unblock"
+ UnfollowPath = BasePathWithID + "/unfollow"
+ UpdatePath = BasePath + "/update_credentials"
+ VerifyPath = BasePath + "/verify_credentials"
)
type Module struct {
@@ -92,23 +72,23 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
attachHandler(http.MethodGet, BasePathWithID, m.AccountGETHandler)
// delete account
- attachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler)
+ attachHandler(http.MethodPost, DeletePath, m.AccountDeletePOSTHandler)
// verify account
attachHandler(http.MethodGet, VerifyPath, m.AccountVerifyGETHandler)
// modify account
- attachHandler(http.MethodPatch, UpdateCredentialsPath, m.AccountUpdateCredentialsPATCHHandler)
+ attachHandler(http.MethodPatch, UpdatePath, m.AccountUpdateCredentialsPATCHHandler)
// get account's statuses
- attachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler)
+ attachHandler(http.MethodGet, StatusesPath, m.AccountStatusesGETHandler)
// get following or followers
- attachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler)
- attachHandler(http.MethodGet, GetFollowingPath, m.AccountFollowingGETHandler)
+ attachHandler(http.MethodGet, FollowersPath, m.AccountFollowersGETHandler)
+ attachHandler(http.MethodGet, FollowingPath, m.AccountFollowingGETHandler)
// get relationship with account
- attachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler)
+ attachHandler(http.MethodGet, RelationshipsPath, m.AccountRelationshipsGETHandler)
// follow or unfollow account
attachHandler(http.MethodPost, FollowPath, m.AccountFollowPOSTHandler)
@@ -120,4 +100,8 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
// account lists
attachHandler(http.MethodGet, ListsPath, m.AccountListsGETHandler)
+
+ // search for accounts
+ attachHandler(http.MethodGet, SearchPath, m.AccountSearchGETHandler)
+ attachHandler(http.MethodGet, LookupPath, m.AccountLookupGETHandler)
}
diff --git a/internal/api/client/accounts/accountupdate_test.go b/internal/api/client/accounts/accountupdate_test.go
index f6bff4825..01d12ab27 100644
--- a/internal/api/client/accounts/accountupdate_test.go
+++ b/internal/api/client/accounts/accountupdate_test.go
@@ -76,7 +76,7 @@ func (suite *AccountUpdateTestSuite) updateAccount(
) (*apimodel.Account, error) {
// Initialize http test context.
recorder := httptest.NewRecorder()
- ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, contentType)
+ ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdatePath, contentType)
// Trigger the handler.
suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)
diff --git a/internal/api/client/accounts/lookup.go b/internal/api/client/accounts/lookup.go
new file mode 100644
index 000000000..4b31ea6cc
--- /dev/null
+++ b/internal/api/client/accounts/lookup.go
@@ -0,0 +1,93 @@
+// 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 accounts
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+// AccountLookupGETHandler swagger:operation GET /api/v1/accounts/lookup accountLookupGet
+//
+// Quickly lookup a username to see if it is available, skipping WebFinger resolution.
+//
+// ---
+// tags:
+// - accounts
+//
+// produces:
+// - application/json
+//
+// parameters:
+// -
+// name: acct
+// type: string
+// description: The username or Webfinger address to lookup.
+// in: query
+// required: true
+//
+// security:
+// - OAuth2 Bearer:
+// - read:accounts
+//
+// responses:
+// '200':
+// name: lookup result
+// description: Result of the lookup.
+// schema:
+// "$ref": "#/definitions/account"
+// '400':
+// description: bad request
+// '401':
+// description: unauthorized
+// '404':
+// description: not found
+// '406':
+// description: not acceptable
+// '500':
+// description: internal server error
+func (m *Module) AccountLookupGETHandler(c *gin.Context) {
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ query, errWithCode := apiutil.ParseSearchLookup(c.Query(apiutil.SearchLookupKey))
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ account, errWithCode := m.processor.Search().Lookup(c.Request.Context(), authed.Account, query)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ c.JSON(http.StatusOK, account)
+}
diff --git a/internal/api/client/accounts/search.go b/internal/api/client/accounts/search.go
new file mode 100644
index 000000000..c10fb2960
--- /dev/null
+++ b/internal/api/client/accounts/search.go
@@ -0,0 +1,166 @@
+// 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 accounts
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+// AccountSearchGETHandler swagger:operation GET /api/v1/accounts/search accountSearchGet
+//
+// Search for accounts by username and/or display name.
+//
+// ---
+// tags:
+// - accounts
+//
+// produces:
+// - application/json
+//
+// parameters:
+// -
+// name: limit
+// type: integer
+// description: Number of results to try to return.
+// default: 40
+// maximum: 80
+// minimum: 1
+// in: query
+// -
+// name: offset
+// type: integer
+// description: >-
+// Page number of results to return (starts at 0).
+// This parameter is currently not used, offsets
+// over 0 will always return 0 results.
+// default: 0
+// maximum: 10
+// minimum: 0
+// in: query
+// -
+// 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.
+// - any arbitrary string -- search for accounts containing the given string in their username or display name. Can return multiple results.
+// in: query
+// required: true
+// -
+// name: resolve
+// type: boolean
+// description: >-
+// If 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: >-
+// 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
+//
+// security:
+// - OAuth2 Bearer:
+// - read:accounts
+//
+// responses:
+// '200':
+// name: search results
+// description: Results of the search.
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/account"
+// '400':
+// description: bad request
+// '401':
+// description: unauthorized
+// '404':
+// description: not found
+// '406':
+// description: not acceptable
+// '500':
+// description: internal server error
+func (m *Module) AccountSearchGETHandler(c *gin.Context) {
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 40, 80, 1)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ offset, errWithCode := apiutil.ParseSearchOffset(c.Query(apiutil.SearchOffsetKey), 0, 10, 0)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ query, errWithCode := apiutil.ParseSearchQuery(c.Query(apiutil.SearchQueryKey))
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ resolve, errWithCode := apiutil.ParseSearchResolve(c.Query(apiutil.SearchResolveKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ following, errWithCode := apiutil.ParseSearchFollowing(c.Query(apiutil.SearchFollowingKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ results, errWithCode := m.processor.Search().Accounts(
+ c.Request.Context(),
+ authed.Account,
+ query,
+ limit,
+ offset,
+ resolve,
+ following,
+ )
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ c.JSON(http.StatusOK, results)
+}
diff --git a/internal/api/client/accounts/search_test.go b/internal/api/client/accounts/search_test.go
new file mode 100644
index 000000000..7d778f090
--- /dev/null
+++ b/internal/api/client/accounts/search_test.go
@@ -0,0 +1,430 @@
+// 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 accounts_test
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/testrig"
+)
+
+type AccountSearchTestSuite struct {
+ AccountStandardTestSuite
+}
+
+func (suite *AccountSearchTestSuite) getSearch(
+ requestingAccount *gtsmodel.Account,
+ token *gtsmodel.Token,
+ user *gtsmodel.User,
+ limit *int,
+ offset *int,
+ query string,
+ resolve *bool,
+ following *bool,
+ expectedHTTPStatus int,
+ expectedBody string,
+) ([]*apimodel.Account, error) {
+ var (
+ recorder = httptest.NewRecorder()
+ ctx, _ = testrig.CreateGinTestContext(recorder, nil)
+ requestURL = testrig.URLMustParse("/api" + accounts.BasePath + "/search")
+ queryParts []string
+ )
+
+ // Put the request together.
+ 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 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.accountsModule.AccountSearchGETHandler(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 {
+ errs = append(errs, fmt.Sprintf("expected %d got %d", expectedHTTPStatus, resultCode))
+ }
+
+ // 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))
+ }
+
+ accounts := []*apimodel.Account{}
+ if err := json.Unmarshal(b, &accounts); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ return accounts, nil
+}
+
+func (suite *AccountSearchTestSuite) TestSearchZorkOK() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "zork"
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 1 {
+ suite.FailNow("", "expected length %d got %d", 1, l)
+ }
+}
+
+func (suite *AccountSearchTestSuite) TestSearchZorkExactOK() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@the_mighty_zork"
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 1 {
+ suite.FailNow("", "expected length %d got %d", 1, l)
+ }
+}
+
+func (suite *AccountSearchTestSuite) TestSearchZorkWithDomainOK() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "@the_mighty_zork@localhost:8080"
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 1 {
+ suite.FailNow("", "expected length %d got %d", 1, l)
+ }
+}
+
+func (suite *AccountSearchTestSuite) TestSearchFossSatanNotFollowing() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "foss_satan"
+ following *bool = func() *bool { i := false; return &i }()
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 1 {
+ suite.FailNow("", "expected length %d got %d", 1, l)
+ }
+}
+
+func (suite *AccountSearchTestSuite) TestSearchFossSatanFollowing() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "foss_satan"
+ following *bool = func() *bool { i := true; return &i }()
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 0 {
+ suite.FailNow("", "expected length %d got %d", 0, l)
+ }
+}
+
+func (suite *AccountSearchTestSuite) TestSearchBonkersQuery() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "aaaaa@aaaaaaaaa@aaaaa **** this won't@ return anything!@!!"
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 0 {
+ suite.FailNow("", "expected length %d got %d", 0, l)
+ }
+}
+
+func (suite *AccountSearchTestSuite) TestSearchAFollowing() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "a"
+ following *bool = nil
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 5 {
+ suite.FailNow("", "expected length %d got %d", 5, l)
+ }
+
+ usernames := make([]string, 0, 5)
+ for _, account := range accounts {
+ usernames = append(usernames, account.Username)
+ }
+
+ suite.EqualValues([]string{"her_fuckin_maj", "foss_satan", "1happyturtle", "the_mighty_zork", "admin"}, usernames)
+}
+
+func (suite *AccountSearchTestSuite) TestSearchANotFollowing() {
+ var (
+ requestingAccount = suite.testAccounts["local_account_1"]
+ token = suite.testTokens["local_account_1"]
+ user = suite.testUsers["local_account_1"]
+ limit *int = nil
+ offset *int = nil
+ resolve *bool = nil
+ query = "a"
+ following *bool = func() *bool { i := true; return &i }()
+ expectedHTTPStatus = http.StatusOK
+ expectedBody = ""
+ )
+
+ accounts, err := suite.getSearch(
+ requestingAccount,
+ token,
+ user,
+ limit,
+ offset,
+ query,
+ resolve,
+ following,
+ expectedHTTPStatus,
+ expectedBody,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ if l := len(accounts); l != 2 {
+ suite.FailNow("", "expected length %d got %d", 2, l)
+ }
+
+ usernames := make([]string, 0, 2)
+ for _, account := range accounts {
+ usernames = append(usernames, account.Username)
+ }
+
+ suite.EqualValues([]string{"1happyturtle", "admin"}, usernames)
+}
+
+func TestAccountSearchTestSuite(t *testing.T) {
+ suite.Run(t, new(AccountSearchTestSuite))
+}