diff options
Diffstat (limited to 'internal/api/client/statuses')
| -rw-r--r-- | internal/api/client/statuses/statuscreate.go | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statuscreate_test.go | 132 | 
2 files changed, 135 insertions, 11 deletions
| diff --git a/internal/api/client/statuses/statuscreate.go b/internal/api/client/statuses/statuscreate.go index d187e823f..bfb1c486d 100644 --- a/internal/api/client/statuses/statuscreate.go +++ b/internal/api/client/statuses/statuscreate.go @@ -179,11 +179,15 @@ import (  //		x-go-name: ScheduledAt  //		description: |-  //			ISO 8601 Datetime at which to schedule a status. -//			Providing this parameter will cause ScheduledStatus to be returned instead of Status. +// +//			Providing this parameter with a *future* time will cause ScheduledStatus to be returned instead of Status.  //			Must be at least 5 minutes in the future. +//			This feature isn't implemented yet.  // -//			This feature isn't implemented yet; attemping to set it will return 501 Not Implemented. +//			Providing this parameter with a *past* time will cause the status to be backdated, +//			and will not push it to the user's followers. This is intended for importing old statuses.  //		type: string +//		format: date-time  //		in: formData  //	-  //		name: language @@ -384,12 +388,6 @@ func parseStatusCreateForm(c *gin.Context) (*apimodel.StatusCreateRequest, gtser  		return nil, gtserror.NewErrorNotAcceptable(errors.New(text), text)  	} -	// Check not scheduled status. -	if form.ScheduledAt != "" { -		const text = "scheduled_at is not yet implemented" -		return nil, gtserror.NewErrorNotImplemented(errors.New(text), text) -	} -  	// Check if the deprecated "federated" field was  	// set in lieu of "local_only", and use it if so.  	if form.LocalOnly == nil && form.Federated != nil { // nolint:staticcheck diff --git a/internal/api/client/statuses/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go index 227e7d83e..53e517a6e 100644 --- a/internal/api/client/statuses/statuscreate_test.go +++ b/internal/api/client/statuses/statuscreate_test.go @@ -20,14 +20,18 @@ package statuses_test  import (  	"bytes"  	"context" +	"encoding/json"  	"fmt" +	"io"  	"net/http"  	"net/http/httptest"  	"testing" +	"time"  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -41,10 +45,11 @@ const (  	statusMarkdown         = "# Title\n\n## Smaller title\n\nThis is a post written in [markdown](https://www.markdownguide.org/)\n\n<img src=\"https://d33wubrfki0l68.cloudfront.net/f1f475a6fda1c2c4be4cac04033db5c3293032b4/513a4/assets/images/markdown-mark-white.svg\"/>"  ) -func (suite *StatusCreateTestSuite) postStatus( +// Post a status. +func (suite *StatusCreateTestSuite) postStatusCore(  	formData map[string][]string,  	jsonData string, -) (string, *httptest.ResponseRecorder) { +) *httptest.ResponseRecorder {  	recorder := httptest.NewRecorder()  	ctx, _ := testrig.CreateGinTestContext(recorder, nil)  	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) @@ -77,9 +82,42 @@ func (suite *StatusCreateTestSuite) postStatus(  	// Trigger handler.  	suite.statusModule.StatusCreatePOSTHandler(ctx) + +	return recorder +} + +// Post a status and return the result as deterministic JSON. +func (suite *StatusCreateTestSuite) postStatus( +	formData map[string][]string, +	jsonData string, +) (string, *httptest.ResponseRecorder) { +	recorder := suite.postStatusCore(formData, jsonData)  	return suite.parseStatusResponse(recorder)  } +// Post a status and return the result as a non-deterministic API structure. +func (suite *StatusCreateTestSuite) postStatusStruct( +	formData map[string][]string, +	jsonData string, +) (*apimodel.Status, *httptest.ResponseRecorder) { +	recorder := suite.postStatusCore(formData, jsonData) + +	result := recorder.Result() +	defer result.Body.Close() + +	data, err := io.ReadAll(result.Body) +	if err != nil { +		suite.FailNow(err.Error()) +	} + +	apiStatus := apimodel.Status{} +	if err := json.Unmarshal(data, &apiStatus); err != nil { +		suite.FailNow(err.Error()) +	} + +	return &apiStatus, recorder +} +  // Post a new status with some custom visibility settings  func (suite *StatusCreateTestSuite) TestPostNewStatus() {  	out, recorder := suite.postStatus(map[string][]string{ @@ -383,10 +421,98 @@ func (suite *StatusCreateTestSuite) TestPostNewScheduledStatus() {  	// We should have a helpful error message.  	suite.Equal(`{ -  "error": "Not Implemented: scheduled_at is not yet implemented" +  "error": "Not Implemented: scheduled statuses are not yet supported"  }`, out)  } +func (suite *StatusCreateTestSuite) TestPostNewBackfilledStatus() { +	// A time in the past. +	scheduledAtStr := "2020-10-04T15:32:02.018Z" +	scheduledAt, err := time.Parse(time.RFC3339Nano, scheduledAtStr) +	if err != nil { +		suite.FailNow(err.Error()) +	} + +	status, recorder := suite.postStatusStruct(map[string][]string{ +		"status":       {"this is a recycled status from the past!"}, +		"scheduled_at": {scheduledAtStr}, +	}, "") + +	// Creating a status in the past should succeed. +	suite.Equal(http.StatusOK, recorder.Code) + +	// The status should be backdated. +	createdAt, err := time.Parse(time.RFC3339Nano, status.CreatedAt) +	if err != nil { +		suite.FailNow(err.Error()) +		return +	} +	suite.Equal(scheduledAt, createdAt.UTC()) + +	// The status's ULID should be backdated. +	timeFromULID, err := id.TimeFromULID(status.ID) +	if err != nil { +		suite.FailNow(err.Error()) +		return +	} +	suite.Equal(scheduledAt, timeFromULID.UTC()) +} + +func (suite *StatusCreateTestSuite) TestPostNewBackfilledStatusWithSelfMention() { +	_, recorder := suite.postStatus(map[string][]string{ +		"status":       {"@the_mighty_zork this is a recycled mention from the past!"}, +		"scheduled_at": {"2020-10-04T15:32:02.018Z"}, +	}, "") + +	// Mentioning yourself is allowed in backfilled statuses. +	suite.Equal(http.StatusOK, recorder.Code) +} + +func (suite *StatusCreateTestSuite) TestPostNewBackfilledStatusWithMention() { +	_, recorder := suite.postStatus(map[string][]string{ +		"status":       {"@admin this is a recycled mention from the past!"}, +		"scheduled_at": {"2020-10-04T15:32:02.018Z"}, +	}, "") + +	// Mentioning others is forbidden in backfilled statuses. +	suite.Equal(http.StatusForbidden, recorder.Code) +} + +func (suite *StatusCreateTestSuite) TestPostNewBackfilledStatusWithSelfReply() { +	_, recorder := suite.postStatus(map[string][]string{ +		"status":         {"this is a recycled reply from the past!"}, +		"scheduled_at":   {"2020-10-04T15:32:02.018Z"}, +		"in_reply_to_id": {suite.testStatuses["local_account_1_status_1"].ID}, +	}, "") + +	// Replying to yourself is allowed in backfilled statuses. +	suite.Equal(http.StatusOK, recorder.Code) +} + +func (suite *StatusCreateTestSuite) TestPostNewBackfilledStatusWithReply() { +	_, recorder := suite.postStatus(map[string][]string{ +		"status":         {"this is a recycled reply from the past!"}, +		"scheduled_at":   {"2020-10-04T15:32:02.018Z"}, +		"in_reply_to_id": {suite.testStatuses["admin_account_status_1"].ID}, +	}, "") + +	// Replying to others is forbidden in backfilled statuses. +	suite.Equal(http.StatusForbidden, recorder.Code) +} + +func (suite *StatusCreateTestSuite) TestPostNewBackfilledStatusWithPoll() { +	_, recorder := suite.postStatus(map[string][]string{ +		"status":           {"this is a recycled poll from the past!"}, +		"scheduled_at":     {"2020-10-04T15:32:02.018Z"}, +		"poll[options][]":  {"first option", "second option"}, +		"poll[expires_in]": {"3600"}, +		"poll[multiple]":   {"true"}, +	}, "") + +	// Polls are forbidden in backfilled statuses. +	suite.Equal(http.StatusForbidden, recorder.Code) +} +  func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {  	out, recorder := suite.postStatus(map[string][]string{  		"status":       {statusMarkdown}, | 
