diff options
Diffstat (limited to 'internal/api/client')
| -rw-r--r-- | internal/api/client/admin/admin.go | 29 | ||||
| -rw-r--r-- | internal/api/client/admin/admin_test.go | 2 | ||||
| -rw-r--r-- | internal/api/client/admin/reportget.go | 103 | ||||
| -rw-r--r-- | internal/api/client/admin/reportresolve.go | 125 | ||||
| -rw-r--r-- | internal/api/client/admin/reportresolve_test.go | 168 | ||||
| -rw-r--r-- | internal/api/client/admin/reportsget.go | 184 | ||||
| -rw-r--r-- | internal/api/client/admin/reportsget_test.go | 905 | 
7 files changed, 1515 insertions, 1 deletions
diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index 31670bac8..f7e54d271 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -46,6 +46,12 @@ const (  	AccountsActionPath = AccountsPathWithID + "/action"  	MediaCleanupPath   = BasePath + "/media_cleanup"  	MediaRefetchPath   = BasePath + "/media_refetch" +	// ReportsPath is for serving admin view of user reports. +	ReportsPath = BasePath + "/reports" +	// ReportsPathWithID is for viewing/acting on one report. +	ReportsPathWithID = ReportsPath + "/:" + IDKey +	// ReportsResolvePath is for marking one report as resolved. +	ReportsResolvePath = ReportsPathWithID + "/resolve"  	// ExportQueryKey is for requesting a public export of some data.  	ExportQueryKey = "export" @@ -65,6 +71,15 @@ const (  	LimitKey = "limit"  	// DomainQueryKey is for specifying a domain during admin actions.  	DomainQueryKey = "domain" +	// ResolvedKey is for filtering reports by their resolved status +	ResolvedKey = "resolved" +	// AccountIDKey is for selecting account in API paths. +	AccountIDKey = "account_id" +	// TargetAccountIDKey is for selecting target account in API paths. +	TargetAccountIDKey = "target_account_id" +	MaxIDKey           = "max_id" +	SinceIDKey         = "since_id" +	MinIDKey           = "min_id"  )  type Module struct { @@ -78,17 +93,29 @@ func New(processor processing.Processor) *Module {  }  func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	// emoji stuff  	attachHandler(http.MethodPost, EmojiPath, m.EmojiCreatePOSTHandler)  	attachHandler(http.MethodGet, EmojiPath, m.EmojisGETHandler)  	attachHandler(http.MethodDelete, EmojiPathWithID, m.EmojiDELETEHandler)  	attachHandler(http.MethodGet, EmojiPathWithID, m.EmojiGETHandler)  	attachHandler(http.MethodPatch, EmojiPathWithID, m.EmojiPATCHHandler) +	attachHandler(http.MethodGet, EmojiCategoriesPath, m.EmojiCategoriesGETHandler) + +	// domain block stuff  	attachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler)  	attachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler)  	attachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler)  	attachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler) + +	// accounts stuff  	attachHandler(http.MethodPost, AccountsActionPath, m.AccountActionPOSTHandler) + +	// media stuff  	attachHandler(http.MethodPost, MediaCleanupPath, m.MediaCleanupPOSTHandler)  	attachHandler(http.MethodPost, MediaRefetchPath, m.MediaRefetchPOSTHandler) -	attachHandler(http.MethodGet, EmojiCategoriesPath, m.EmojiCategoriesGETHandler) + +	// reports stuff +	attachHandler(http.MethodGet, ReportsPath, m.ReportsGETHandler) +	attachHandler(http.MethodGet, ReportsPathWithID, m.ReportGETHandler) +	attachHandler(http.MethodPost, ReportsResolvePath, m.ReportResolvePOSTHandler)  } diff --git a/internal/api/client/admin/admin_test.go b/internal/api/client/admin/admin_test.go index a7c402c49..000ca1927 100644 --- a/internal/api/client/admin/admin_test.go +++ b/internal/api/client/admin/admin_test.go @@ -62,6 +62,7 @@ type AdminStandardTestSuite struct {  	testStatuses        map[string]*gtsmodel.Status  	testEmojis          map[string]*gtsmodel.Emoji  	testEmojiCategories map[string]*gtsmodel.EmojiCategory +	testReports         map[string]*gtsmodel.Report  	// module being tested  	adminModule *admin.Module @@ -77,6 +78,7 @@ func (suite *AdminStandardTestSuite) SetupSuite() {  	suite.testStatuses = testrig.NewTestStatuses()  	suite.testEmojis = testrig.NewTestEmojis()  	suite.testEmojiCategories = testrig.NewTestEmojiCategories() +	suite.testReports = testrig.NewTestReports()  }  func (suite *AdminStandardTestSuite) SetupTest() { diff --git a/internal/api/client/admin/reportget.go b/internal/api/client/admin/reportget.go new file mode 100644 index 000000000..5f2fbbb91 --- /dev/null +++ b/internal/api/client/admin/reportget.go @@ -0,0 +1,103 @@ +/* +   GoToSocial +   Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + +   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 admin + +import ( +	"errors" +	"fmt" +	"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" +) + +// ReportGETHandler swagger:operation GET /api/v1/admin/reports/{id} adminReportGet +// +// View user moderation report with the given id. +// +//	--- +//	tags: +//	- admin +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: The id of the report. +//		in: path +//		required: true +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			name: report +//			description: The requested report. +//			schema: +//				"$ref": "#/definitions/adminReport" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ReportGETHandler(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.InstanceGet) +		return +	} + +	if !*authed.User.Admin { +		err := fmt.Errorf("user %s not an admin", authed.User.ID) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	reportID := c.Param(IDKey) +	if reportID == "" { +		err := errors.New("no report id specified") +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	report, errWithCode := m.processor.AdminReportGet(c.Request.Context(), authed, reportID) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		return +	} + +	c.JSON(http.StatusOK, report) +} diff --git a/internal/api/client/admin/reportresolve.go b/internal/api/client/admin/reportresolve.go new file mode 100644 index 000000000..e34bd6814 --- /dev/null +++ b/internal/api/client/admin/reportresolve.go @@ -0,0 +1,125 @@ +/* +   GoToSocial +   Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + +   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 admin + +import ( +	"errors" +	"fmt" +	"net/http" + +	"github.com/gin-gonic/gin" +	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/oauth" +) + +// ReportResolvePOSTHandler swagger:operation POST /api/v1/admin/reports/{id}/resolve adminReportResolve +// +// Mark a report as resolved. +// +//	--- +//	tags: +//	- admin +// +//	consumes: +//	- application/json +//	- application/xml +//	- multipart/form-data +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: The id of the report. +//		in: path +//		required: true +//	- +//		name: action_taken_comment +//		in: formData +//		description: >- +//			Optional admin comment on the action taken in response to this report. +//			Useful for providing an explanation about what action was taken (if any) +//			before the report was marked as resolved. This will be visible to the user +//			that created the report! +//		type: string +//		example: The reported account was suspended. +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			name: report +//			description: The resolved report. +//			schema: +//				"$ref": "#/definitions/adminReport" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ReportResolvePOSTHandler(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.InstanceGet) +		return +	} + +	if !*authed.User.Admin { +		err := fmt.Errorf("user %s not an admin", authed.User.ID) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	reportID := c.Param(IDKey) +	if reportID == "" { +		err := errors.New("no report id specified") +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	form := &apimodel.AdminReportResolveRequest{} +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	report, errWithCode := m.processor.AdminReportResolve(c.Request.Context(), authed, reportID, form.ActionTakenComment) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		return +	} + +	c.JSON(http.StatusOK, report) +} diff --git a/internal/api/client/admin/reportresolve_test.go b/internal/api/client/admin/reportresolve_test.go new file mode 100644 index 000000000..7b340183e --- /dev/null +++ b/internal/api/client/admin/reportresolve_test.go @@ -0,0 +1,168 @@ +/* +   GoToSocial +   Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + +   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 admin_test + +import ( +	"encoding/json" +	"fmt" +	"io/ioutil" +	"net/http" +	"net/http/httptest" +	"net/url" +	"testing" +	"time" + +	"github.com/stretchr/testify/suite" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/admin" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/config" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/internal/util" +	"github.com/superseriousbusiness/gotosocial/testrig" +) + +type ReportResolveTestSuite struct { +	AdminStandardTestSuite +} + +func (suite *ReportResolveTestSuite) resolveReport( +	account *gtsmodel.Account, +	token *gtsmodel.Token, +	user *gtsmodel.User, +	targetReportID string, +	expectedHTTPStatus int, +	expectedBody string, +	actionTakenComment *string, +) (*apimodel.AdminReport, error) { +	// instantiate recorder + test context +	recorder := httptest.NewRecorder() +	ctx, _ := testrig.CreateGinTestContext(recorder, nil) +	ctx.Set(oauth.SessionAuthorizedAccount, account) +	ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(token)) +	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) +	ctx.Set(oauth.SessionAuthorizedUser, user) + +	// create the request URI +	requestPath := admin.ReportsPath + "/" + targetReportID + "/resolve" +	baseURI := config.GetProtocol() + "://" + config.GetHost() +	requestURI := baseURI + "/api/" + requestPath + +	// create the request +	ctx.Request = httptest.NewRequest(http.MethodPost, requestURI, nil) +	ctx.AddParam(admin.IDKey, targetReportID) +	ctx.Request.Header.Set("accept", "application/json") +	if actionTakenComment != nil { +		ctx.Request.Form = url.Values{"action_taken_comment": {*actionTakenComment}} +	} + +	// trigger the handler +	suite.adminModule.ReportResolvePOSTHandler(ctx) + +	// read the response +	result := recorder.Result() +	defer result.Body.Close() + +	b, err := ioutil.ReadAll(result.Body) +	if err != nil { +		return nil, err +	} + +	errs := gtserror.MultiError{} + +	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 != "" { +		if string(b) != expectedBody { +			errs = append(errs, fmt.Sprintf("expected %s got %s", expectedBody, string(b))) +		} +		return nil, errs.Combine() +	} + +	resp := &apimodel.AdminReport{} +	if err := json.Unmarshal(b, &resp); err != nil { +		return nil, err +	} + +	return resp, nil +} + +func (suite *ReportResolveTestSuite) TestReportResolve1() { +	testAccount := suite.testAccounts["admin_account"] +	testToken := suite.testTokens["admin_account"] +	testUser := suite.testUsers["admin_account"] +	testReportID := suite.testReports["local_account_2_report_remote_account_1"].ID +	var actionTakenComment *string = nil + +	report, err := suite.resolveReport(testAccount, testToken, testUser, testReportID, http.StatusOK, "", actionTakenComment) +	suite.NoError(err) +	suite.NotEmpty(report) + +	// report should be resolved +	suite.True(report.ActionTaken) +	actionTime, err := util.ParseISO8601(*report.ActionTakenAt) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.WithinDuration(time.Now(), actionTime, 1*time.Minute) +	updatedTime, err := util.ParseISO8601(report.UpdatedAt) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.WithinDuration(time.Now(), updatedTime, 1*time.Minute) +	suite.Equal(report.ActionTakenByAccount.ID, testAccount.ID) +	suite.EqualValues(report.ActionTakenComment, actionTakenComment) +	suite.EqualValues(report.AssignedAccount.ID, testAccount.ID) +} + +func (suite *ReportResolveTestSuite) TestReportResolve2() { +	testAccount := suite.testAccounts["admin_account"] +	testToken := suite.testTokens["admin_account"] +	testUser := suite.testUsers["admin_account"] +	testReportID := suite.testReports["local_account_2_report_remote_account_1"].ID +	var actionTakenComment *string = testrig.StringPtr("no action was taken, this is a frivolous report you boob") + +	report, err := suite.resolveReport(testAccount, testToken, testUser, testReportID, http.StatusOK, "", actionTakenComment) +	suite.NoError(err) +	suite.NotEmpty(report) + +	// report should be resolved +	suite.True(report.ActionTaken) +	actionTime, err := util.ParseISO8601(*report.ActionTakenAt) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.WithinDuration(time.Now(), actionTime, 1*time.Minute) +	updatedTime, err := util.ParseISO8601(report.UpdatedAt) +	if err != nil { +		suite.FailNow(err.Error()) +	} +	suite.WithinDuration(time.Now(), updatedTime, 1*time.Minute) +	suite.Equal(report.ActionTakenByAccount.ID, testAccount.ID) +	suite.EqualValues(report.ActionTakenComment, actionTakenComment) +	suite.EqualValues(report.AssignedAccount.ID, testAccount.ID) +} + +func TestReportResolveTestSuite(t *testing.T) { +	suite.Run(t, &ReportResolveTestSuite{}) +} diff --git a/internal/api/client/admin/reportsget.go b/internal/api/client/admin/reportsget.go new file mode 100644 index 000000000..51108e461 --- /dev/null +++ b/internal/api/client/admin/reportsget.go @@ -0,0 +1,184 @@ +/* +   GoToSocial +   Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + +   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 admin + +import ( +	"fmt" +	"net/http" +	"strconv" + +	"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" +) + +// ReportsGETHandler swagger:operation GET /api/v1/admin/reports adminReports +// +// View user moderation reports. +// +// The reports will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer). +// +// The next and previous queries can be parsed from the returned Link header. +// +// Example: +// +// ``` +// <https://example.org/api/v1/admin/reports?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/reports?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev" +// ```` +// +//	--- +//	tags: +//	- admin +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: resolved +//		type: boolean +//		description: >- +//			If set to true, only resolved reports will be returned. +//			If false, only unresolved reports will be returned. +//			If unset, reports will not be filtered on their resolved status. +//		in: query +//	- +//		name: account_id +//		type: string +//		description: Return only reports created by the given account id. +//		in: query +//	- +//		name: target_account_id +//		type: string +//		description: Return only reports that target the given account id. +//		in: query +//	- +//		name: max_id +//		type: string +//		description: >- +//			Return only reports *OLDER* than the given max ID. +//			The report with the specified ID will not be included in the response. +//		in: query +//	- +//		name: since_id +//		type: string +//		description: >- +//			Return only reports *NEWER* than the given since ID. +//			The report with the specified ID will not be included in the response. +//			This parameter is functionally equivalent to min_id. +//		in: query +//	- +//		name: min_id +//		type: string +//		description: >- +//			Return only reports *NEWER* than the given min ID. +//			The report with the specified ID will not be included in the response. +//			This parameter is functionally equivalent to since_id. +//		in: query +//	- +//		name: limit +//		type: integer +//		description: >- +//			Number of reports to return. +//			If less than 1, will be clamped to 1. +//			If more than 100, will be clamped to 100. +//		default: 20 +//		in: query +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			name: reports +//			description: Array of reports. +//			schema: +//				type: array +//				items: +//					"$ref": "#/definitions/adminReport" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ReportsGETHandler(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.InstanceGet) +		return +	} + +	if !*authed.User.Admin { +		err := fmt.Errorf("user %s not an admin", authed.User.ID) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	var resolved *bool +	if resolvedString := c.Query(ResolvedKey); resolvedString != "" { +		i, err := strconv.ParseBool(resolvedString) +		if err != nil { +			err := fmt.Errorf("error parsing %s: %s", ResolvedKey, err) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			return +		} +		resolved = &i +	} + +	limit := 20 +	if limitString := c.Query(LimitKey); limitString != "" { +		i, err := strconv.Atoi(limitString) +		if err != nil { +			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			return +		} + +		// normalize +		if i <= 0 { +			i = 1 +		} else if i >= 100 { +			i = 100 +		} +		limit = i +	} + +	resp, errWithCode := m.processor.AdminReportsGet(c.Request.Context(), authed, resolved, c.Query(AccountIDKey), c.Query(TargetAccountIDKey), c.Query(MaxIDKey), c.Query(SinceIDKey), c.Query(MinIDKey), limit) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		return +	} + +	if resp.LinkHeader != "" { +		c.Header("Link", resp.LinkHeader) +	} +	c.JSON(http.StatusOK, resp.Items) +} diff --git a/internal/api/client/admin/reportsget_test.go b/internal/api/client/admin/reportsget_test.go new file mode 100644 index 000000000..9bcc3dcd0 --- /dev/null +++ b/internal/api/client/admin/reportsget_test.go @@ -0,0 +1,905 @@ +/* +   GoToSocial +   Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org + +   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 admin_test + +import ( +	"encoding/json" +	"fmt" +	"io/ioutil" +	"net/http" +	"net/http/httptest" +	"strconv" +	"testing" + +	"github.com/stretchr/testify/suite" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/admin" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/config" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/testrig" +) + +type ReportsGetTestSuite struct { +	AdminStandardTestSuite +} + +func (suite *ReportsGetTestSuite) getReports( +	account *gtsmodel.Account, +	token *gtsmodel.Token, +	user *gtsmodel.User, +	expectedHTTPStatus int, +	expectedBody string, +	resolved *bool, +	accountID string, +	targetAccountID string, +	maxID string, +	sinceID string, +	minID string, +	limit int, +) ([]*apimodel.AdminReport, string, error) { +	// instantiate recorder + test context +	recorder := httptest.NewRecorder() +	ctx, _ := testrig.CreateGinTestContext(recorder, nil) +	ctx.Set(oauth.SessionAuthorizedAccount, account) +	ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(token)) +	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) +	ctx.Set(oauth.SessionAuthorizedUser, user) + +	// create the request URI +	requestPath := admin.ReportsPath + "?" + admin.LimitKey + "=" + strconv.Itoa(limit) +	if resolved != nil { +		requestPath = requestPath + "&" + admin.ResolvedKey + "=" + strconv.FormatBool(*resolved) +	} +	if accountID != "" { +		requestPath = requestPath + "&" + admin.AccountIDKey + "=" + accountID +	} +	if targetAccountID != "" { +		requestPath = requestPath + "&" + admin.TargetAccountIDKey + "=" + targetAccountID +	} +	if maxID != "" { +		requestPath = requestPath + "&" + admin.MaxIDKey + "=" + maxID +	} +	if sinceID != "" { +		requestPath = requestPath + "&" + admin.SinceIDKey + "=" + sinceID +	} +	if minID != "" { +		requestPath = requestPath + "&" + admin.MinIDKey + "=" + minID +	} +	baseURI := config.GetProtocol() + "://" + config.GetHost() +	requestURI := baseURI + "/api/" + requestPath + +	// create the request +	ctx.Request = httptest.NewRequest(http.MethodGet, requestURI, nil) +	ctx.Request.Header.Set("accept", "application/json") + +	// trigger the handler +	suite.adminModule.ReportsGETHandler(ctx) + +	// read the response +	result := recorder.Result() +	defer result.Body.Close() + +	b, err := ioutil.ReadAll(result.Body) +	if err != nil { +		return nil, "", err +	} + +	errs := gtserror.MultiError{} + +	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 != "" { +		if string(b) != expectedBody { +			errs = append(errs, fmt.Sprintf("expected %s got %s", expectedBody, string(b))) +		} +		return nil, "", errs.Combine() +	} + +	resp := []*apimodel.AdminReport{} +	if err := json.Unmarshal(b, &resp); err != nil { +		return nil, "", err +	} + +	return resp, result.Header.Get("Link"), nil +} + +func (suite *ReportsGetTestSuite) TestReportsGet1() { +	testAccount := suite.testAccounts["admin_account"] +	testToken := suite.testTokens["admin_account"] +	testUser := suite.testUsers["admin_account"] + +	reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, "", nil, "", "", "", "", "", 20) +	suite.NoError(err) +	suite.NotEmpty(reports) + +	b, err := json.MarshalIndent(&reports, "", "  ") +	suite.NoError(err) + +	suite.Equal(`[ +  { +    "id": "01GP3DFY9XQ1TJMZT5BGAZPXX7", +    "action_taken": true, +    "action_taken_at": "2022-05-15T15:01:56.000Z", +    "category": "other", +    "comment": "this is a turtle, not a person, therefore should not be a poster", +    "forwarded": true, +    "created_at": "2022-05-15T14:20:12.000Z", +    "updated_at": "2022-05-15T14:20:12.000Z", +    "account": { +      "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +      "username": "foss_satan", +      "domain": "fossbros-anonymous.io", +      "created_at": "2021-09-26T10:52:36.000Z", +      "email": "", +      "ip": null, +      "ips": [], +      "locale": "", +      "invite_request": null, +      "role": "user", +      "confirmed": false, +      "approved": false, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +        "username": "foss_satan", +        "acct": "foss_satan@fossbros-anonymous.io", +        "display_name": "big gerald", +        "locked": false, +        "bot": false, +        "created_at": "2021-09-26T10:52:36.000Z", +        "note": "i post about like, i dunno, stuff, or whatever!!!!", +        "url": "http://fossbros-anonymous.io/@foss_satan", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 0, +        "following_count": 0, +        "statuses_count": 1, +        "last_status_at": "2021-09-20T10:40:37.000Z", +        "emojis": [], +        "fields": [] +      } +    }, +    "target_account": { +      "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +      "username": "1happyturtle", +      "domain": null, +      "created_at": "2022-06-04T13:12:00.000Z", +      "email": "tortle.dude@example.org", +      "ip": "118.44.18.196", +      "ips": [], +      "locale": "en", +      "invite_request": "", +      "role": "user", +      "confirmed": true, +      "approved": true, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +        "username": "1happyturtle", +        "acct": "1happyturtle", +        "display_name": "happy little turtle :3", +        "locked": true, +        "bot": false, +        "created_at": "2022-06-04T13:12:00.000Z", +        "note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e", +        "url": "http://localhost:8080/@1happyturtle", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 1, +        "following_count": 1, +        "statuses_count": 7, +        "last_status_at": "2021-10-20T10:40:37.000Z", +        "emojis": [], +        "fields": [], +        "role": "user" +      }, +      "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" +    }, +    "assigned_account": { +      "id": "01F8MH17FWEB39HZJ76B6VXSKF", +      "username": "admin", +      "domain": null, +      "created_at": "2022-05-17T13:10:59.000Z", +      "email": "admin@example.org", +      "ip": "89.122.255.1", +      "ips": [], +      "locale": "en", +      "invite_request": "", +      "role": "admin", +      "confirmed": true, +      "approved": true, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH17FWEB39HZJ76B6VXSKF", +        "username": "admin", +        "acct": "admin", +        "display_name": "", +        "locked": false, +        "bot": false, +        "created_at": "2022-05-17T13:10:59.000Z", +        "note": "", +        "url": "http://localhost:8080/@admin", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 1, +        "following_count": 1, +        "statuses_count": 4, +        "last_status_at": "2021-10-20T10:41:37.000Z", +        "emojis": [], +        "fields": [], +        "enable_rss": true, +        "role": "admin" +      }, +      "created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F" +    }, +    "action_taken_by_account": { +      "id": "01F8MH17FWEB39HZJ76B6VXSKF", +      "username": "admin", +      "domain": null, +      "created_at": "2022-05-17T13:10:59.000Z", +      "email": "admin@example.org", +      "ip": "89.122.255.1", +      "ips": [], +      "locale": "en", +      "invite_request": "", +      "role": "admin", +      "confirmed": true, +      "approved": true, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH17FWEB39HZJ76B6VXSKF", +        "username": "admin", +        "acct": "admin", +        "display_name": "", +        "locked": false, +        "bot": false, +        "created_at": "2022-05-17T13:10:59.000Z", +        "note": "", +        "url": "http://localhost:8080/@admin", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 1, +        "following_count": 1, +        "statuses_count": 4, +        "last_status_at": "2021-10-20T10:41:37.000Z", +        "emojis": [], +        "fields": [], +        "enable_rss": true, +        "role": "admin" +      }, +      "created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F" +    }, +    "statuses": [], +    "rule_ids": [], +    "action_taken_comment": "user was warned not to be a turtle anymore" +  }, +  { +    "id": "01GP3AWY4CRDVRNZKW0TEAMB5R", +    "action_taken": false, +    "action_taken_at": null, +    "category": "other", +    "comment": "dark souls sucks, please yeet this nerd", +    "forwarded": true, +    "created_at": "2022-05-14T10:20:03.000Z", +    "updated_at": "2022-05-14T10:20:03.000Z", +    "account": { +      "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +      "username": "1happyturtle", +      "domain": null, +      "created_at": "2022-06-04T13:12:00.000Z", +      "email": "tortle.dude@example.org", +      "ip": "118.44.18.196", +      "ips": [], +      "locale": "en", +      "invite_request": "", +      "role": "user", +      "confirmed": true, +      "approved": true, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +        "username": "1happyturtle", +        "acct": "1happyturtle", +        "display_name": "happy little turtle :3", +        "locked": true, +        "bot": false, +        "created_at": "2022-06-04T13:12:00.000Z", +        "note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e", +        "url": "http://localhost:8080/@1happyturtle", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 1, +        "following_count": 1, +        "statuses_count": 7, +        "last_status_at": "2021-10-20T10:40:37.000Z", +        "emojis": [], +        "fields": [], +        "role": "user" +      }, +      "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" +    }, +    "target_account": { +      "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +      "username": "foss_satan", +      "domain": "fossbros-anonymous.io", +      "created_at": "2021-09-26T10:52:36.000Z", +      "email": "", +      "ip": null, +      "ips": [], +      "locale": "", +      "invite_request": null, +      "role": "user", +      "confirmed": false, +      "approved": false, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +        "username": "foss_satan", +        "acct": "foss_satan@fossbros-anonymous.io", +        "display_name": "big gerald", +        "locked": false, +        "bot": false, +        "created_at": "2021-09-26T10:52:36.000Z", +        "note": "i post about like, i dunno, stuff, or whatever!!!!", +        "url": "http://fossbros-anonymous.io/@foss_satan", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 0, +        "following_count": 0, +        "statuses_count": 1, +        "last_status_at": "2021-09-20T10:40:37.000Z", +        "emojis": [], +        "fields": [] +      } +    }, +    "assigned_account": null, +    "action_taken_by_account": null, +    "statuses": [ +      { +        "id": "01FVW7JHQFSFK166WWKR8CBA6M", +        "created_at": "2021-09-20T10:40:37.000Z", +        "in_reply_to_id": null, +        "in_reply_to_account_id": null, +        "sensitive": false, +        "spoiler_text": "", +        "visibility": "unlisted", +        "language": "en", +        "uri": "http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M", +        "url": "http://fossbros-anonymous.io/@foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M", +        "replies_count": 0, +        "reblogs_count": 0, +        "favourites_count": 0, +        "favourited": false, +        "reblogged": false, +        "muted": false, +        "bookmarked": false, +        "pinned": false, +        "content": "dark souls status bot: \"thoughts of dog\"", +        "reblog": null, +        "account": { +          "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +          "username": "foss_satan", +          "acct": "foss_satan@fossbros-anonymous.io", +          "display_name": "big gerald", +          "locked": false, +          "bot": false, +          "created_at": "2021-09-26T10:52:36.000Z", +          "note": "i post about like, i dunno, stuff, or whatever!!!!", +          "url": "http://fossbros-anonymous.io/@foss_satan", +          "avatar": "", +          "avatar_static": "", +          "header": "http://localhost:8080/assets/default_header.png", +          "header_static": "http://localhost:8080/assets/default_header.png", +          "followers_count": 0, +          "following_count": 0, +          "statuses_count": 1, +          "last_status_at": "2021-09-20T10:40:37.000Z", +          "emojis": [], +          "fields": [] +        }, +        "media_attachments": [ +          { +            "id": "01FVW7RXPQ8YJHTEXYPE7Q8ZY0", +            "type": "image", +            "url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "text_url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "preview_url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/small/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "remote_url": "http://fossbros-anonymous.io/attachments/original/13bbc3f8-2b5e-46ea-9531-40b4974d9912.jpg", +            "preview_remote_url": "http://fossbros-anonymous.io/attachments/small/a499f55b-2d1e-4acd-98d2-1ac2ba6d79b9.jpg", +            "meta": { +              "original": { +                "width": 472, +                "height": 291, +                "size": "472x291", +                "aspect": 1.6219932 +              }, +              "small": { +                "width": 472, +                "height": 291, +                "size": "472x291", +                "aspect": 1.6219932 +              }, +              "focus": { +                "x": 0, +                "y": 0 +              } +            }, +            "description": "tweet from thoughts of dog: i drank. all the water. in my bowl. earlier. but just now. i returned. to the same bowl. and it was. full again.. the bowl. is haunted", +            "blurhash": "LARysgM_IU_3~pD%M_Rj_39FIAt6" +          } +        ], +        "mentions": [], +        "tags": [], +        "emojis": [], +        "card": null, +        "poll": null +      } +    ], +    "rule_ids": [], +    "action_taken_comment": null +  } +]`, string(b)) + +	suite.Equal(`<http://localhost:8080/api/v1/admin/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R>; rel="next", <http://localhost:8080/api/v1/admin/reports?limit=20&min_id=01GP3DFY9XQ1TJMZT5BGAZPXX7>; rel="prev"`, link) +} + +func (suite *ReportsGetTestSuite) TestReportsGet2() { +	testAccount := suite.testAccounts["admin_account"] +	testToken := suite.testTokens["admin_account"] +	testUser := suite.testUsers["admin_account"] +	account := suite.testAccounts["local_account_2"] + +	reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, "", nil, account.ID, "", "", "", "", 20) +	suite.NoError(err) +	suite.NotEmpty(reports) + +	b, err := json.MarshalIndent(&reports, "", "  ") +	suite.NoError(err) + +	suite.Equal(`[ +  { +    "id": "01GP3AWY4CRDVRNZKW0TEAMB5R", +    "action_taken": false, +    "action_taken_at": null, +    "category": "other", +    "comment": "dark souls sucks, please yeet this nerd", +    "forwarded": true, +    "created_at": "2022-05-14T10:20:03.000Z", +    "updated_at": "2022-05-14T10:20:03.000Z", +    "account": { +      "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +      "username": "1happyturtle", +      "domain": null, +      "created_at": "2022-06-04T13:12:00.000Z", +      "email": "tortle.dude@example.org", +      "ip": "118.44.18.196", +      "ips": [], +      "locale": "en", +      "invite_request": "", +      "role": "user", +      "confirmed": true, +      "approved": true, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +        "username": "1happyturtle", +        "acct": "1happyturtle", +        "display_name": "happy little turtle :3", +        "locked": true, +        "bot": false, +        "created_at": "2022-06-04T13:12:00.000Z", +        "note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e", +        "url": "http://localhost:8080/@1happyturtle", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 1, +        "following_count": 1, +        "statuses_count": 7, +        "last_status_at": "2021-10-20T10:40:37.000Z", +        "emojis": [], +        "fields": [], +        "role": "user" +      }, +      "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" +    }, +    "target_account": { +      "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +      "username": "foss_satan", +      "domain": "fossbros-anonymous.io", +      "created_at": "2021-09-26T10:52:36.000Z", +      "email": "", +      "ip": null, +      "ips": [], +      "locale": "", +      "invite_request": null, +      "role": "user", +      "confirmed": false, +      "approved": false, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +        "username": "foss_satan", +        "acct": "foss_satan@fossbros-anonymous.io", +        "display_name": "big gerald", +        "locked": false, +        "bot": false, +        "created_at": "2021-09-26T10:52:36.000Z", +        "note": "i post about like, i dunno, stuff, or whatever!!!!", +        "url": "http://fossbros-anonymous.io/@foss_satan", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 0, +        "following_count": 0, +        "statuses_count": 1, +        "last_status_at": "2021-09-20T10:40:37.000Z", +        "emojis": [], +        "fields": [] +      } +    }, +    "assigned_account": null, +    "action_taken_by_account": null, +    "statuses": [ +      { +        "id": "01FVW7JHQFSFK166WWKR8CBA6M", +        "created_at": "2021-09-20T10:40:37.000Z", +        "in_reply_to_id": null, +        "in_reply_to_account_id": null, +        "sensitive": false, +        "spoiler_text": "", +        "visibility": "unlisted", +        "language": "en", +        "uri": "http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M", +        "url": "http://fossbros-anonymous.io/@foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M", +        "replies_count": 0, +        "reblogs_count": 0, +        "favourites_count": 0, +        "favourited": false, +        "reblogged": false, +        "muted": false, +        "bookmarked": false, +        "pinned": false, +        "content": "dark souls status bot: \"thoughts of dog\"", +        "reblog": null, +        "account": { +          "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +          "username": "foss_satan", +          "acct": "foss_satan@fossbros-anonymous.io", +          "display_name": "big gerald", +          "locked": false, +          "bot": false, +          "created_at": "2021-09-26T10:52:36.000Z", +          "note": "i post about like, i dunno, stuff, or whatever!!!!", +          "url": "http://fossbros-anonymous.io/@foss_satan", +          "avatar": "", +          "avatar_static": "", +          "header": "http://localhost:8080/assets/default_header.png", +          "header_static": "http://localhost:8080/assets/default_header.png", +          "followers_count": 0, +          "following_count": 0, +          "statuses_count": 1, +          "last_status_at": "2021-09-20T10:40:37.000Z", +          "emojis": [], +          "fields": [] +        }, +        "media_attachments": [ +          { +            "id": "01FVW7RXPQ8YJHTEXYPE7Q8ZY0", +            "type": "image", +            "url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "text_url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "preview_url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/small/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "remote_url": "http://fossbros-anonymous.io/attachments/original/13bbc3f8-2b5e-46ea-9531-40b4974d9912.jpg", +            "preview_remote_url": "http://fossbros-anonymous.io/attachments/small/a499f55b-2d1e-4acd-98d2-1ac2ba6d79b9.jpg", +            "meta": { +              "original": { +                "width": 472, +                "height": 291, +                "size": "472x291", +                "aspect": 1.6219932 +              }, +              "small": { +                "width": 472, +                "height": 291, +                "size": "472x291", +                "aspect": 1.6219932 +              }, +              "focus": { +                "x": 0, +                "y": 0 +              } +            }, +            "description": "tweet from thoughts of dog: i drank. all the water. in my bowl. earlier. but just now. i returned. to the same bowl. and it was. full again.. the bowl. is haunted", +            "blurhash": "LARysgM_IU_3~pD%M_Rj_39FIAt6" +          } +        ], +        "mentions": [], +        "tags": [], +        "emojis": [], +        "card": null, +        "poll": null +      } +    ], +    "rule_ids": [], +    "action_taken_comment": null +  } +]`, string(b)) + +	suite.Equal(`<http://localhost:8080/api/v1/admin/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R&account_id=01F8MH5NBDF2MV7CTC4Q5128HF>; rel="next", <http://localhost:8080/api/v1/admin/reports?limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R&account_id=01F8MH5NBDF2MV7CTC4Q5128HF>; rel="prev"`, link) +} + +func (suite *ReportsGetTestSuite) TestReportsGet3() { +	testAccount := suite.testAccounts["admin_account"] +	testToken := suite.testTokens["admin_account"] +	testUser := suite.testUsers["admin_account"] +	targetAccount := suite.testAccounts["remote_account_1"] + +	reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, "", nil, "", targetAccount.ID, "", "", "", 20) +	suite.NoError(err) +	suite.NotEmpty(reports) + +	b, err := json.MarshalIndent(&reports, "", "  ") +	suite.NoError(err) + +	suite.Equal(`[ +  { +    "id": "01GP3AWY4CRDVRNZKW0TEAMB5R", +    "action_taken": false, +    "action_taken_at": null, +    "category": "other", +    "comment": "dark souls sucks, please yeet this nerd", +    "forwarded": true, +    "created_at": "2022-05-14T10:20:03.000Z", +    "updated_at": "2022-05-14T10:20:03.000Z", +    "account": { +      "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +      "username": "1happyturtle", +      "domain": null, +      "created_at": "2022-06-04T13:12:00.000Z", +      "email": "tortle.dude@example.org", +      "ip": "118.44.18.196", +      "ips": [], +      "locale": "en", +      "invite_request": "", +      "role": "user", +      "confirmed": true, +      "approved": true, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5NBDF2MV7CTC4Q5128HF", +        "username": "1happyturtle", +        "acct": "1happyturtle", +        "display_name": "happy little turtle :3", +        "locked": true, +        "bot": false, +        "created_at": "2022-06-04T13:12:00.000Z", +        "note": "\u003cp\u003ei post about things that concern me\u003c/p\u003e", +        "url": "http://localhost:8080/@1happyturtle", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 1, +        "following_count": 1, +        "statuses_count": 7, +        "last_status_at": "2021-10-20T10:40:37.000Z", +        "emojis": [], +        "fields": [], +        "role": "user" +      }, +      "created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG" +    }, +    "target_account": { +      "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +      "username": "foss_satan", +      "domain": "fossbros-anonymous.io", +      "created_at": "2021-09-26T10:52:36.000Z", +      "email": "", +      "ip": null, +      "ips": [], +      "locale": "", +      "invite_request": null, +      "role": "user", +      "confirmed": false, +      "approved": false, +      "disabled": false, +      "silenced": false, +      "suspended": false, +      "account": { +        "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +        "username": "foss_satan", +        "acct": "foss_satan@fossbros-anonymous.io", +        "display_name": "big gerald", +        "locked": false, +        "bot": false, +        "created_at": "2021-09-26T10:52:36.000Z", +        "note": "i post about like, i dunno, stuff, or whatever!!!!", +        "url": "http://fossbros-anonymous.io/@foss_satan", +        "avatar": "", +        "avatar_static": "", +        "header": "http://localhost:8080/assets/default_header.png", +        "header_static": "http://localhost:8080/assets/default_header.png", +        "followers_count": 0, +        "following_count": 0, +        "statuses_count": 1, +        "last_status_at": "2021-09-20T10:40:37.000Z", +        "emojis": [], +        "fields": [] +      } +    }, +    "assigned_account": null, +    "action_taken_by_account": null, +    "statuses": [ +      { +        "id": "01FVW7JHQFSFK166WWKR8CBA6M", +        "created_at": "2021-09-20T10:40:37.000Z", +        "in_reply_to_id": null, +        "in_reply_to_account_id": null, +        "sensitive": false, +        "spoiler_text": "", +        "visibility": "unlisted", +        "language": "en", +        "uri": "http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M", +        "url": "http://fossbros-anonymous.io/@foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M", +        "replies_count": 0, +        "reblogs_count": 0, +        "favourites_count": 0, +        "favourited": false, +        "reblogged": false, +        "muted": false, +        "bookmarked": false, +        "pinned": false, +        "content": "dark souls status bot: \"thoughts of dog\"", +        "reblog": null, +        "account": { +          "id": "01F8MH5ZK5VRH73AKHQM6Y9VNX", +          "username": "foss_satan", +          "acct": "foss_satan@fossbros-anonymous.io", +          "display_name": "big gerald", +          "locked": false, +          "bot": false, +          "created_at": "2021-09-26T10:52:36.000Z", +          "note": "i post about like, i dunno, stuff, or whatever!!!!", +          "url": "http://fossbros-anonymous.io/@foss_satan", +          "avatar": "", +          "avatar_static": "", +          "header": "http://localhost:8080/assets/default_header.png", +          "header_static": "http://localhost:8080/assets/default_header.png", +          "followers_count": 0, +          "following_count": 0, +          "statuses_count": 1, +          "last_status_at": "2021-09-20T10:40:37.000Z", +          "emojis": [], +          "fields": [] +        }, +        "media_attachments": [ +          { +            "id": "01FVW7RXPQ8YJHTEXYPE7Q8ZY0", +            "type": "image", +            "url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "text_url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "preview_url": "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/small/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg", +            "remote_url": "http://fossbros-anonymous.io/attachments/original/13bbc3f8-2b5e-46ea-9531-40b4974d9912.jpg", +            "preview_remote_url": "http://fossbros-anonymous.io/attachments/small/a499f55b-2d1e-4acd-98d2-1ac2ba6d79b9.jpg", +            "meta": { +              "original": { +                "width": 472, +                "height": 291, +                "size": "472x291", +                "aspect": 1.6219932 +              }, +              "small": { +                "width": 472, +                "height": 291, +                "size": "472x291", +                "aspect": 1.6219932 +              }, +              "focus": { +                "x": 0, +                "y": 0 +              } +            }, +            "description": "tweet from thoughts of dog: i drank. all the water. in my bowl. earlier. but just now. i returned. to the same bowl. and it was. full again.. the bowl. is haunted", +            "blurhash": "LARysgM_IU_3~pD%M_Rj_39FIAt6" +          } +        ], +        "mentions": [], +        "tags": [], +        "emojis": [], +        "card": null, +        "poll": null +      } +    ], +    "rule_ids": [], +    "action_taken_comment": null +  } +]`, string(b)) + +	suite.Equal(`<http://localhost:8080/api/v1/admin/reports?limit=20&max_id=01GP3AWY4CRDVRNZKW0TEAMB5R&target_account_id=01F8MH5ZK5VRH73AKHQM6Y9VNX>; rel="next", <http://localhost:8080/api/v1/admin/reports?limit=20&min_id=01GP3AWY4CRDVRNZKW0TEAMB5R&target_account_id=01F8MH5ZK5VRH73AKHQM6Y9VNX>; rel="prev"`, link) +} + +func (suite *ReportsGetTestSuite) TestReportsGet4() { +	testAccount := suite.testAccounts["admin_account"] +	testToken := suite.testTokens["admin_account"] +	testUser := suite.testUsers["admin_account"] +	resolved := testrig.FalseBool() +	targetAccount := suite.testAccounts["local_account_2"] + +	reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, "", resolved, "", targetAccount.ID, "", "", "", 20) +	suite.NoError(err) +	suite.Empty(reports) + +	b, err := json.MarshalIndent(&reports, "", "  ") +	suite.NoError(err) + +	suite.Equal(`[]`, string(b)) +	suite.Empty(link) +} + +func (suite *ReportsGetTestSuite) TestReportsGet6() { +	testAccount := suite.testAccounts["local_account_1"] +	testToken := suite.testTokens["local_account_1"] +	testUser := suite.testUsers["local_account_1"] + +	reports, _, err := suite.getReports(testAccount, testToken, testUser, http.StatusForbidden, `{"error":"Forbidden: user 01F8MGVGPHQ2D3P3X0454H54Z5 not an admin"}`, nil, "", "", "", "", "", 20) +	suite.NoError(err) +	suite.Empty(reports) +} + +func TestReportsGetTestSuite(t *testing.T) { +	suite.Run(t, &ReportsGetTestSuite{}) +}  | 
