506 lines
14 KiB
Go
506 lines
14 KiB
Go
package api
|
|
|
|
import (
|
|
"math/rand"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"strings"
|
|
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
|
|
"io/ioutil"
|
|
|
|
"github.com/bouk/monkey"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gotify/server/mode"
|
|
"github.com/gotify/server/model"
|
|
"github.com/gotify/server/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
var (
|
|
firstApplicationToken = "APorrUa5b1IIK3y"
|
|
secondApplicationToken = "AKo_Pp6ww_9vZal"
|
|
firstClientToken = "CPorrUa5b1IIK3y"
|
|
secondClientToken = "CKo_Pp6ww_9vZal"
|
|
)
|
|
|
|
func TestTokenSuite(t *testing.T) {
|
|
suite.Run(t, new(TokenSuite))
|
|
}
|
|
|
|
type TokenSuite struct {
|
|
suite.Suite
|
|
db *test.Database
|
|
a *TokenAPI
|
|
ctx *gin.Context
|
|
recorder *httptest.ResponseRecorder
|
|
}
|
|
|
|
func (s *TokenSuite) BeforeTest(suiteName, testName string) {
|
|
mode.Set(mode.TestDev)
|
|
rand.Seed(50)
|
|
s.recorder = httptest.NewRecorder()
|
|
s.db = test.NewDB(s.T())
|
|
s.ctx, _ = gin.CreateTestContext(s.recorder)
|
|
withURL(s.ctx, "http", "example.com")
|
|
s.a = &TokenAPI{DB: s.db}
|
|
}
|
|
|
|
func (s *TokenSuite) AfterTest(suiteName, testName string) {
|
|
s.db.Close()
|
|
}
|
|
|
|
// test application api
|
|
|
|
func (s *TokenSuite) Test_CreateApplication_mapAllParameters() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=custom_name&description=description_text")
|
|
s.a.CreateApplication(s.ctx)
|
|
|
|
expected := &model.Application{ID: 1, Token: firstApplicationToken, UserID: 5, Name: "custom_name", Description: "description_text"}
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
assert.Equal(s.T(), expected, s.db.GetApplicationByID(1))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateApplication_expectBadRequestOnEmptyName() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=&description=description_text")
|
|
s.a.CreateApplication(s.ctx)
|
|
|
|
assert.Equal(s.T(), 400, s.recorder.Code)
|
|
assert.Empty(s.T(), s.db.GetApplicationsByUser(5))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_DeleteApplication_expectNotFoundOnCurrentUserIsNotOwner() {
|
|
s.db.User(2)
|
|
s.db.User(5).App(5)
|
|
|
|
test.WithUser(s.ctx, 2)
|
|
s.ctx.Request = httptest.NewRequest("DELETE", "/token/5", nil)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "5"}}
|
|
|
|
s.a.DeleteApplication(s.ctx)
|
|
|
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
|
s.db.AssertAppExist(5)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateApplication_onlyRequiredParameters() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=custom_name")
|
|
s.a.CreateApplication(s.ctx)
|
|
|
|
expected := &model.Application{ID: 1, Token: firstApplicationToken, Name: "custom_name", UserID: 5}
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
assert.Contains(s.T(), s.db.GetApplicationsByUser(5), expected)
|
|
}
|
|
func (s *TokenSuite) Test_ensureApplicationHasCorrectJsonRepresentation() {
|
|
actual := &model.Application{ID: 1, UserID: 2, Token: "Aasdasfgeeg", Name: "myapp", Description: "mydesc", Image: "asd"}
|
|
test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd"}`)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateApplication_returnsApplicationWithID() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=custom_name")
|
|
|
|
s.a.CreateApplication(s.ctx)
|
|
|
|
expected := &model.Application{ID: 1, Token: firstApplicationToken, Name: "custom_name", Image: "http://example.com/static/defaultapp.png", UserID: 5}
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
test.BodyEquals(s.T(), expected, s.recorder)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateApplication_withExistingToken() {
|
|
s.db.User(5)
|
|
s.db.User(6).AppWithToken(1, firstApplicationToken)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=custom_name")
|
|
|
|
s.a.CreateApplication(s.ctx)
|
|
|
|
expected := &model.Application{ID: 2, Token: secondApplicationToken, Name: "custom_name", UserID: 5}
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
assert.Contains(s.T(), s.db.GetApplicationsByUser(5), expected)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_GetApplications() {
|
|
userBuilder := s.db.User(5)
|
|
first := userBuilder.NewAppWithToken(1, "perfper")
|
|
second := userBuilder.NewAppWithToken(2, "asdasd")
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("GET", "/tokens", nil)
|
|
|
|
s.a.GetApplications(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
first.Image = "http://example.com/static/defaultapp.png"
|
|
second.Image = "http://example.com/static/defaultapp.png"
|
|
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_GetApplications_WithImage() {
|
|
userBuilder := s.db.User(5)
|
|
first := userBuilder.NewAppWithToken(1, "perfper")
|
|
second := userBuilder.NewAppWithToken(2, "asdasd")
|
|
first.Image = "abcd.jpg"
|
|
s.db.UpdateApplication(first)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("GET", "/tokens", nil)
|
|
|
|
s.a.GetApplications(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
first.Image = "http://example.com/image/abcd.jpg"
|
|
second.Image = "http://example.com/static/defaultapp.png"
|
|
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_DeleteApplication_expectNotFound() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("DELETE", "/token/"+firstApplicationToken, nil)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "4"}}
|
|
|
|
s.a.DeleteApplication(s.ctx)
|
|
|
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_DeleteApplication() {
|
|
s.db.User(5).App(1)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("DELETE", "/token/"+firstApplicationToken, nil)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
|
|
s.a.DeleteApplication(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
s.db.AssertAppNotExist(1)
|
|
}
|
|
|
|
// test client api
|
|
|
|
func (s *TokenSuite) Test_ensureClientHasCorrectJsonRepresentation() {
|
|
actual := &model.Client{ID: 1, UserID: 2, Token: "Casdasfgeeg", Name: "myclient"}
|
|
test.JSONEquals(s.T(), actual, `{"id":1,"token":"Casdasfgeeg","name":"myclient"}`)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateClient_mapAllParameters() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=custom_name&description=description_text")
|
|
|
|
s.a.CreateClient(s.ctx)
|
|
|
|
expected := &model.Client{ID: 1, Token: firstClientToken, UserID: 5, Name: "custom_name"}
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
assert.Contains(s.T(), s.db.GetClientsByUser(5), expected)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateClient_expectBadRequestOnEmptyName() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=&description=description_text")
|
|
|
|
s.a.CreateClient(s.ctx)
|
|
|
|
assert.Equal(s.T(), 400, s.recorder.Code)
|
|
assert.Empty(s.T(), s.db.GetClientsByUser(5))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_DeleteClient_expectNotFoundOnCurrentUserIsNotOwner() {
|
|
s.db.User(5).Client(7)
|
|
s.db.User(2)
|
|
|
|
test.WithUser(s.ctx, 2)
|
|
s.ctx.Request = httptest.NewRequest("DELETE", "/token/7", nil)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "7"}}
|
|
|
|
s.a.DeleteClient(s.ctx)
|
|
|
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
|
s.db.AssertClientExist(7)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateClient_returnsClientWithID() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=custom_name")
|
|
|
|
s.a.CreateClient(s.ctx)
|
|
|
|
expected := &model.Client{ID: 1, Token: firstClientToken, Name: "custom_name", UserID: 5}
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
test.BodyEquals(s.T(), expected, s.recorder)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_CreateClient_withExistingToken() {
|
|
s.db.User(5).ClientWithToken(1, firstClientToken)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.withFormData("name=custom_name")
|
|
|
|
s.a.CreateClient(s.ctx)
|
|
|
|
expected := &model.Client{ID: 2, Token: secondClientToken, Name: "custom_name", UserID: 5}
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
test.BodyEquals(s.T(), expected, s.recorder)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_GetClients() {
|
|
userBuilder := s.db.User(5)
|
|
first := userBuilder.NewClientWithToken(1, "perfper")
|
|
second := userBuilder.NewClientWithToken(2, "asdasd")
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("GET", "/tokens", nil)
|
|
|
|
s.a.GetClients(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
test.BodyEquals(s.T(), []*model.Client{first, second}, s.recorder)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_DeleteClient_expectNotFound() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("DELETE", "/token/"+firstClientToken, nil)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "8"}}
|
|
|
|
s.a.DeleteClient(s.ctx)
|
|
|
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
|
}
|
|
|
|
//
|
|
func (s *TokenSuite) Test_DeleteClient() {
|
|
s.db.User(5).Client(8)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("DELETE", "/token/"+firstClientToken, nil)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "8"}}
|
|
|
|
s.a.DeleteClient(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
s.db.AssertClientNotExist(8)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_NoImageProvided_expectBadRequest() {
|
|
s.db.User(5).App(1)
|
|
var b bytes.Buffer
|
|
writer := multipart.NewWriter(&b)
|
|
writer.Close()
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &b)
|
|
s.ctx.Request.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 400, s.recorder.Code)
|
|
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("file with key 'file' must be present"))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_OtherErrors_expectServerError() {
|
|
s.db.User(5).App(1)
|
|
var b bytes.Buffer
|
|
writer := multipart.NewWriter(&b)
|
|
defer writer.Close()
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &b)
|
|
s.ctx.Request.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 500, s.recorder.Code)
|
|
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("multipart: NextPart: EOF"))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_WithImageFile_expectSuccess() {
|
|
s.db.User(5).App(1)
|
|
|
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
|
assert.Nil(s.T(), err)
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &buffer)
|
|
s.ctx.Request.Header.Set("Content-Type", cType)
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
_, err = os.Stat("PorrUa5b1IIK3yKo_Pp6ww_9v.png")
|
|
assert.Nil(s.T(), err)
|
|
|
|
s.a.DeleteApplication(s.ctx)
|
|
|
|
_, err = os.Stat("PorrUa5b1IIK3yKo_Pp6ww_9v.png")
|
|
assert.True(s.T(), os.IsNotExist(err))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_WithImageFile_DeleteExstingImageAndGenerateNewName() {
|
|
s.db.User(5)
|
|
s.db.CreateApplication(&model.Application{UserID: 5, ID: 1, Image: "PorrUa5b1IIK3yKo_Pp6ww_9v.png"})
|
|
|
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
|
assert.Nil(s.T(), err)
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &buffer)
|
|
s.ctx.Request.Header.Set("Content-Type", cType)
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
fakeImage(s.T(), "PorrUa5b1IIK3yKo_Pp6ww_9v.png")
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
|
|
_, err = os.Stat("PorrUa5b1IIK3yKo_Pp6ww_9v.png")
|
|
assert.True(s.T(), os.IsNotExist(err))
|
|
_, err = os.Stat("Zal6-ySIuL-T3EMLCcFtityHn.png")
|
|
assert.Nil(s.T(), err)
|
|
assert.Nil(s.T(), os.Remove("Zal6-ySIuL-T3EMLCcFtityHn.png"))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_WithImageFile_DeleteExistingImage() {
|
|
s.db.User(5)
|
|
s.db.CreateApplication(&model.Application{UserID: 5, ID: 1, Image: "existing.png"})
|
|
|
|
fakeImage(s.T(), "existing.png")
|
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
|
assert.Nil(s.T(), err)
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &buffer)
|
|
s.ctx.Request.Header.Set("Content-Type", cType)
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
|
|
_, err = os.Stat("existing.png")
|
|
assert.True(s.T(), os.IsNotExist(err))
|
|
|
|
os.Remove("PorrUa5b1IIK3yKo_Pp6ww_9v.png")
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_WithTextFile_expectBadRequest() {
|
|
s.db.User(5).App(1)
|
|
|
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/text.txt")})
|
|
assert.Nil(s.T(), err)
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &buffer)
|
|
s.ctx.Request.Header.Set("Content-Type", cType)
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 400, s.recorder.Code)
|
|
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("file must be an image"))
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_expectNotFound() {
|
|
s.db.User(5)
|
|
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", nil)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "4"}}
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
|
}
|
|
|
|
func (s *TokenSuite) Test_UploadAppImage_WithSaveError_expectServerError() {
|
|
s.db.User(5).App(1)
|
|
|
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
|
assert.Nil(s.T(), err)
|
|
s.ctx.Request = httptest.NewRequest("POST", "/irrelevant", &buffer)
|
|
s.ctx.Request.Header.Set("Content-Type", cType)
|
|
test.WithUser(s.ctx, 5)
|
|
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
|
|
// try emulate a save error.
|
|
patch := monkey.PatchInstanceMethod(reflect.TypeOf(s.ctx), "SaveUploadedFile", func(ctx *gin.Context, file *multipart.FileHeader, dst string) error {
|
|
return errors.New("could not do something")
|
|
})
|
|
defer patch.Unpatch()
|
|
|
|
s.a.UploadApplicationImage(s.ctx)
|
|
|
|
assert.Equal(s.T(), 500, s.recorder.Code)
|
|
}
|
|
|
|
func (s *TokenSuite) withFormData(formData string) {
|
|
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(formData))
|
|
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
}
|
|
|
|
func withURL(ctx *gin.Context, scheme, host string) {
|
|
ctx.Set("location", &url.URL{Scheme: scheme, Host: host})
|
|
}
|
|
|
|
// A modified version of https://stackoverflow.com/a/20397167/4244993 from Attila O.
|
|
func upload(values map[string]*os.File) (contentType string, buffer bytes.Buffer, err error) {
|
|
w := multipart.NewWriter(&buffer)
|
|
for key, r := range values {
|
|
var fw io.Writer
|
|
if fw, err = w.CreateFormFile(key, r.Name()); err != nil {
|
|
return
|
|
}
|
|
|
|
if _, err = io.Copy(fw, r); err != nil {
|
|
return
|
|
}
|
|
}
|
|
contentType = w.FormDataContentType()
|
|
w.Close()
|
|
return
|
|
}
|
|
|
|
func mustOpen(f string) *os.File {
|
|
r, err := os.Open(f)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func fakeImage(t *testing.T, path string) {
|
|
data, err := ioutil.ReadFile("../test/assets/image.png")
|
|
assert.Nil(t, err)
|
|
// Write data to dst
|
|
err = ioutil.WriteFile(path, data, 0644)
|
|
assert.Nil(t, err)
|
|
}
|