[#69] add end-point for update application name and description
This commit is contained in:
parent
ee723918f9
commit
4a6863eda2
|
|
@ -1,13 +1,11 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gotify/location"
|
"github.com/gotify/location"
|
||||||
|
|
@ -16,31 +14,24 @@ import (
|
||||||
"github.com/h2non/filetype"
|
"github.com/h2non/filetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The TokenDatabase interface for encapsulating database access.
|
// The ApplicationDatabase interface for encapsulating database access.
|
||||||
type TokenDatabase interface {
|
type ApplicationDatabase interface {
|
||||||
CreateApplication(application *model.Application) error
|
CreateApplication(application *model.Application) error
|
||||||
GetApplicationByToken(token string) *model.Application
|
GetApplicationByToken(token string) *model.Application
|
||||||
GetApplicationByID(id uint) *model.Application
|
GetApplicationByID(id uint) *model.Application
|
||||||
GetApplicationsByUser(userID uint) []*model.Application
|
GetApplicationsByUser(userID uint) []*model.Application
|
||||||
DeleteApplicationByID(id uint) error
|
DeleteApplicationByID(id uint) error
|
||||||
UpdateApplication(application *model.Application)
|
UpdateApplication(application *model.Application) error
|
||||||
|
|
||||||
CreateClient(client *model.Client) error
|
|
||||||
GetClientByToken(token string) *model.Client
|
|
||||||
GetClientByID(id uint) *model.Client
|
|
||||||
GetClientsByUser(userID uint) []*model.Client
|
|
||||||
DeleteClientByID(id uint) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The TokenAPI provides handlers for managing clients and applications.
|
// The ApplicationAPI provides handlers for managing applications.
|
||||||
type TokenAPI struct {
|
type ApplicationAPI struct {
|
||||||
DB TokenDatabase
|
DB ApplicationDatabase
|
||||||
ImageDir string
|
ImageDir string
|
||||||
NotifyDeleted func(uint, string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateApplication creates an application and returns the access token.
|
// CreateApplication creates an application and returns the access token.
|
||||||
func (a *TokenAPI) CreateApplication(ctx *gin.Context) {
|
func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) {
|
||||||
app := model.Application{}
|
app := model.Application{}
|
||||||
if err := ctx.Bind(&app); err == nil {
|
if err := ctx.Bind(&app); err == nil {
|
||||||
app.Token = generateNotExistingToken(auth.GenerateApplicationToken, a.applicationExists)
|
app.Token = generateNotExistingToken(auth.GenerateApplicationToken, a.applicationExists)
|
||||||
|
|
@ -50,19 +41,8 @@ func (a *TokenAPI) CreateApplication(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateClient creates a client and returns the access token.
|
|
||||||
func (a *TokenAPI) CreateClient(ctx *gin.Context) {
|
|
||||||
client := model.Client{}
|
|
||||||
if err := ctx.Bind(&client); err == nil {
|
|
||||||
client.Token = generateNotExistingToken(auth.GenerateClientToken, a.clientExists)
|
|
||||||
client.UserID = auth.GetUserID(ctx)
|
|
||||||
a.DB.CreateClient(&client)
|
|
||||||
ctx.JSON(200, client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetApplications returns all applications a user has.
|
// GetApplications returns all applications a user has.
|
||||||
func (a *TokenAPI) GetApplications(ctx *gin.Context) {
|
func (a *ApplicationAPI) GetApplications(ctx *gin.Context) {
|
||||||
userID := auth.GetUserID(ctx)
|
userID := auth.GetUserID(ctx)
|
||||||
apps := a.DB.GetApplicationsByUser(userID)
|
apps := a.DB.GetApplicationsByUser(userID)
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
|
|
@ -71,15 +51,8 @@ func (a *TokenAPI) GetApplications(ctx *gin.Context) {
|
||||||
ctx.JSON(200, apps)
|
ctx.JSON(200, apps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClients returns all clients a user has.
|
|
||||||
func (a *TokenAPI) GetClients(ctx *gin.Context) {
|
|
||||||
userID := auth.GetUserID(ctx)
|
|
||||||
clients := a.DB.GetClientsByUser(userID)
|
|
||||||
ctx.JSON(200, clients)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteApplication deletes an application by its id.
|
// DeleteApplication deletes an application by its id.
|
||||||
func (a *TokenAPI) DeleteApplication(ctx *gin.Context) {
|
func (a *ApplicationAPI) DeleteApplication(ctx *gin.Context) {
|
||||||
withID(ctx, "id", func(id uint) {
|
withID(ctx, "id", func(id uint) {
|
||||||
if app := a.DB.GetApplicationByID(id); app != nil && app.UserID == auth.GetUserID(ctx) {
|
if app := a.DB.GetApplicationByID(id); app != nil && app.UserID == auth.GetUserID(ctx) {
|
||||||
a.DB.DeleteApplicationByID(id)
|
a.DB.DeleteApplicationByID(id)
|
||||||
|
|
@ -92,20 +65,27 @@ func (a *TokenAPI) DeleteApplication(ctx *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteClient deletes a client by its id.
|
// UpdateApplication updates an application info by its id.
|
||||||
func (a *TokenAPI) DeleteClient(ctx *gin.Context) {
|
func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) {
|
||||||
withID(ctx, "id", func(id uint) {
|
withID(ctx, "id", func(id uint) {
|
||||||
if client := a.DB.GetClientByID(id); client != nil && client.UserID == auth.GetUserID(ctx) {
|
if app := a.DB.GetApplicationByID(id); app != nil && app.UserID == auth.GetUserID(ctx) {
|
||||||
a.NotifyDeleted(client.UserID, client.Token)
|
newValues := &model.Application{}
|
||||||
a.DB.DeleteClientByID(id)
|
if err := ctx.Bind(newValues); err == nil {
|
||||||
|
app.Description = newValues.Description
|
||||||
|
app.Name = newValues.Name
|
||||||
|
|
||||||
|
a.DB.UpdateApplication(app)
|
||||||
|
|
||||||
|
ctx.JSON(200, withAbsoluteURL(ctx, app))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.AbortWithError(404, fmt.Errorf("client with id %d doesn't exists", id))
|
ctx.AbortWithError(404, fmt.Errorf("app with id %d doesn't exists", id))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadApplicationImage uploads an image for an application.
|
// UploadApplicationImage uploads an image for an application.
|
||||||
func (a *TokenAPI) UploadApplicationImage(ctx *gin.Context) {
|
func (a *ApplicationAPI) UploadApplicationImage(ctx *gin.Context) {
|
||||||
withID(ctx, "id", func(id uint) {
|
withID(ctx, "id", func(id uint) {
|
||||||
if app := a.DB.GetApplicationByID(id); app != nil && app.UserID == auth.GetUserID(ctx) {
|
if app := a.DB.GetApplicationByID(id); app != nil && app.UserID == auth.GetUserID(ctx) {
|
||||||
file, err := ctx.FormFile("file")
|
file, err := ctx.FormFile("file")
|
||||||
|
|
@ -150,6 +130,10 @@ func (a *TokenAPI) UploadApplicationImage(ctx *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ApplicationAPI) applicationExists(token string) bool {
|
||||||
|
return a.DB.GetApplicationByToken(token) != nil
|
||||||
|
}
|
||||||
|
|
||||||
func exist(path string) bool {
|
func exist(path string) bool {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return false
|
return false
|
||||||
|
|
@ -168,20 +152,3 @@ func withAbsoluteURL(ctx *gin.Context, app *model.Application) *model.Applicatio
|
||||||
app.Image = url.String()
|
app.Image = url.String()
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *TokenAPI) applicationExists(token string) bool {
|
|
||||||
return a.DB.GetApplicationByToken(token) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *TokenAPI) clientExists(token string) bool {
|
|
||||||
return a.DB.GetClientByToken(token) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateNotExistingToken(generateToken func() string, tokenExists func(token string) bool) string {
|
|
||||||
for {
|
|
||||||
token := generateToken()
|
|
||||||
if !tokenExists(token) {
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -26,57 +25,63 @@ import (
|
||||||
var (
|
var (
|
||||||
firstApplicationToken = "APorrUa5b1IIK3y"
|
firstApplicationToken = "APorrUa5b1IIK3y"
|
||||||
secondApplicationToken = "AKo_Pp6ww_9vZal"
|
secondApplicationToken = "AKo_Pp6ww_9vZal"
|
||||||
firstClientToken = "CPorrUa5b1IIK3y"
|
|
||||||
secondClientToken = "CKo_Pp6ww_9vZal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTokenSuite(t *testing.T) {
|
func TestApplicationSuite(t *testing.T) {
|
||||||
suite.Run(t, new(TokenSuite))
|
suite.Run(t, new(ApplicationSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenSuite struct {
|
type ApplicationSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
db *test.Database
|
db *test.Database
|
||||||
a *TokenAPI
|
a *ApplicationAPI
|
||||||
ctx *gin.Context
|
ctx *gin.Context
|
||||||
recorder *httptest.ResponseRecorder
|
recorder *httptest.ResponseRecorder
|
||||||
notified bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) BeforeTest(suiteName, testName string) {
|
func (s *ApplicationSuite) BeforeTest(suiteName, testName string) {
|
||||||
mode.Set(mode.TestDev)
|
mode.Set(mode.TestDev)
|
||||||
rand.Seed(50)
|
rand.Seed(50)
|
||||||
s.recorder = httptest.NewRecorder()
|
s.recorder = httptest.NewRecorder()
|
||||||
s.db = test.NewDB(s.T())
|
s.db = test.NewDB(s.T())
|
||||||
s.ctx, _ = gin.CreateTestContext(s.recorder)
|
s.ctx, _ = gin.CreateTestContext(s.recorder)
|
||||||
withURL(s.ctx, "http", "example.com")
|
withURL(s.ctx, "http", "example.com")
|
||||||
s.notified = false
|
s.a = &ApplicationAPI{DB: s.db}
|
||||||
s.a = &TokenAPI{DB: s.db, NotifyDeleted: s.notify}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) notify(uint, string) {
|
func (s *ApplicationSuite) AfterTest(suiteName, testName string) {
|
||||||
s.notified = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TokenSuite) AfterTest(suiteName, testName string) {
|
|
||||||
s.db.Close()
|
s.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// test application api
|
func (s *ApplicationSuite) Test_CreateApplication_mapAllParameters() {
|
||||||
|
|
||||||
func (s *TokenSuite) Test_CreateApplication_mapAllParameters() {
|
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
|
|
||||||
test.WithUser(s.ctx, 5)
|
test.WithUser(s.ctx, 5)
|
||||||
s.withFormData("name=custom_name&description=description_text")
|
s.withFormData("name=custom_name&description=description_text")
|
||||||
s.a.CreateApplication(s.ctx)
|
s.a.CreateApplication(s.ctx)
|
||||||
|
|
||||||
expected := &model.Application{ID: 1, Token: firstApplicationToken, UserID: 5, Name: "custom_name", Description: "description_text"}
|
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(), 200, s.recorder.Code)
|
||||||
assert.Equal(s.T(), expected, s.db.GetApplicationByID(1))
|
assert.Equal(s.T(), expected, s.db.GetApplicationByID(1))
|
||||||
}
|
}
|
||||||
|
func (s *ApplicationSuite) Test_ensureApplicationHasCorrectJsonRepresentation() {
|
||||||
func (s *TokenSuite) Test_CreateApplication_expectBadRequestOnEmptyName() {
|
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 *ApplicationSuite) Test_CreateApplication_expectBadRequestOnEmptyName() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
|
|
||||||
test.WithUser(s.ctx, 5)
|
test.WithUser(s.ctx, 5)
|
||||||
|
|
@ -87,7 +92,7 @@ func (s *TokenSuite) Test_CreateApplication_expectBadRequestOnEmptyName() {
|
||||||
assert.Empty(s.T(), s.db.GetApplicationsByUser(5))
|
assert.Empty(s.T(), s.db.GetApplicationsByUser(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_DeleteApplication_expectNotFoundOnCurrentUserIsNotOwner() {
|
func (s *ApplicationSuite) Test_DeleteApplication_expectNotFoundOnCurrentUserIsNotOwner() {
|
||||||
s.db.User(2)
|
s.db.User(2)
|
||||||
s.db.User(5).App(5)
|
s.db.User(5).App(5)
|
||||||
|
|
||||||
|
|
@ -101,7 +106,7 @@ func (s *TokenSuite) Test_DeleteApplication_expectNotFoundOnCurrentUserIsNotOwne
|
||||||
s.db.AssertAppExist(5)
|
s.db.AssertAppExist(5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_CreateApplication_onlyRequiredParameters() {
|
func (s *ApplicationSuite) Test_CreateApplication_onlyRequiredParameters() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
|
|
||||||
test.WithUser(s.ctx, 5)
|
test.WithUser(s.ctx, 5)
|
||||||
|
|
@ -112,12 +117,8 @@ func (s *TokenSuite) Test_CreateApplication_onlyRequiredParameters() {
|
||||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
assert.Contains(s.T(), s.db.GetApplicationsByUser(5), expected)
|
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() {
|
func (s *ApplicationSuite) Test_CreateApplication_returnsApplicationWithID() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
|
|
||||||
test.WithUser(s.ctx, 5)
|
test.WithUser(s.ctx, 5)
|
||||||
|
|
@ -125,12 +126,18 @@ func (s *TokenSuite) Test_CreateApplication_returnsApplicationWithID() {
|
||||||
|
|
||||||
s.a.CreateApplication(s.ctx)
|
s.a.CreateApplication(s.ctx)
|
||||||
|
|
||||||
expected := &model.Application{ID: 1, Token: firstApplicationToken, Name: "custom_name", Image: "http://example.com/static/defaultapp.png", UserID: 5}
|
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)
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
test.BodyEquals(s.T(), expected, s.recorder)
|
test.BodyEquals(s.T(), expected, s.recorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_CreateApplication_withExistingToken() {
|
func (s *ApplicationSuite) Test_CreateApplication_withExistingToken() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
s.db.User(6).AppWithToken(1, firstApplicationToken)
|
s.db.User(6).AppWithToken(1, firstApplicationToken)
|
||||||
|
|
||||||
|
|
@ -144,7 +151,7 @@ func (s *TokenSuite) Test_CreateApplication_withExistingToken() {
|
||||||
assert.Contains(s.T(), s.db.GetApplicationsByUser(5), expected)
|
assert.Contains(s.T(), s.db.GetApplicationsByUser(5), expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_GetApplications() {
|
func (s *ApplicationSuite) Test_GetApplications() {
|
||||||
userBuilder := s.db.User(5)
|
userBuilder := s.db.User(5)
|
||||||
first := userBuilder.NewAppWithToken(1, "perfper")
|
first := userBuilder.NewAppWithToken(1, "perfper")
|
||||||
second := userBuilder.NewAppWithToken(2, "asdasd")
|
second := userBuilder.NewAppWithToken(2, "asdasd")
|
||||||
|
|
@ -160,7 +167,7 @@ func (s *TokenSuite) Test_GetApplications() {
|
||||||
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_GetApplications_WithImage() {
|
func (s *ApplicationSuite) Test_GetApplications_WithImage() {
|
||||||
userBuilder := s.db.User(5)
|
userBuilder := s.db.User(5)
|
||||||
first := userBuilder.NewAppWithToken(1, "perfper")
|
first := userBuilder.NewAppWithToken(1, "perfper")
|
||||||
second := userBuilder.NewAppWithToken(2, "asdasd")
|
second := userBuilder.NewAppWithToken(2, "asdasd")
|
||||||
|
|
@ -178,7 +185,7 @@ func (s *TokenSuite) Test_GetApplications_WithImage() {
|
||||||
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
test.BodyEquals(s.T(), []*model.Application{first, second}, s.recorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_DeleteApplication_expectNotFound() {
|
func (s *ApplicationSuite) Test_DeleteApplication_expectNotFound() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
|
|
||||||
test.WithUser(s.ctx, 5)
|
test.WithUser(s.ctx, 5)
|
||||||
|
|
@ -190,7 +197,7 @@ func (s *TokenSuite) Test_DeleteApplication_expectNotFound() {
|
||||||
assert.Equal(s.T(), 404, s.recorder.Code)
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_DeleteApplication() {
|
func (s *ApplicationSuite) Test_DeleteApplication() {
|
||||||
s.db.User(5).App(1)
|
s.db.User(5).App(1)
|
||||||
|
|
||||||
test.WithUser(s.ctx, 5)
|
test.WithUser(s.ctx, 5)
|
||||||
|
|
@ -203,122 +210,7 @@ func (s *TokenSuite) Test_DeleteApplication() {
|
||||||
s.db.AssertAppNotExist(1)
|
s.db.AssertAppNotExist(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// test client api
|
func (s *ApplicationSuite) Test_UploadAppImage_NoImageProvided_expectBadRequest() {
|
||||||
|
|
||||||
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"}}
|
|
||||||
|
|
||||||
assert.False(s.T(), s.notified)
|
|
||||||
|
|
||||||
s.a.DeleteClient(s.ctx)
|
|
||||||
|
|
||||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
|
||||||
s.db.AssertClientNotExist(8)
|
|
||||||
assert.True(s.T(), s.notified)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TokenSuite) Test_UploadAppImage_NoImageProvided_expectBadRequest() {
|
|
||||||
s.db.User(5).App(1)
|
s.db.User(5).App(1)
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
writer := multipart.NewWriter(&b)
|
writer := multipart.NewWriter(&b)
|
||||||
|
|
@ -335,7 +227,7 @@ func (s *TokenSuite) Test_UploadAppImage_NoImageProvided_expectBadRequest() {
|
||||||
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("file with key 'file' must be present"))
|
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() {
|
func (s *ApplicationSuite) Test_UploadAppImage_OtherErrors_expectServerError() {
|
||||||
s.db.User(5).App(1)
|
s.db.User(5).App(1)
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
writer := multipart.NewWriter(&b)
|
writer := multipart.NewWriter(&b)
|
||||||
|
|
@ -352,7 +244,7 @@ func (s *TokenSuite) Test_UploadAppImage_OtherErrors_expectServerError() {
|
||||||
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("multipart: NextPart: EOF"))
|
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("multipart: NextPart: EOF"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_UploadAppImage_WithImageFile_expectSuccess() {
|
func (s *ApplicationSuite) Test_UploadAppImage_WithImageFile_expectSuccess() {
|
||||||
s.db.User(5).App(1)
|
s.db.User(5).App(1)
|
||||||
|
|
||||||
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
||||||
|
|
@ -374,7 +266,7 @@ func (s *TokenSuite) Test_UploadAppImage_WithImageFile_expectSuccess() {
|
||||||
assert.True(s.T(), os.IsNotExist(err))
|
assert.True(s.T(), os.IsNotExist(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_UploadAppImage_WithImageFile_DeleteExstingImageAndGenerateNewName() {
|
func (s *ApplicationSuite) Test_UploadAppImage_WithImageFile_DeleteExstingImageAndGenerateNewName() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
s.db.CreateApplication(&model.Application{UserID: 5, ID: 1, Image: "PorrUa5b1IIK3yKo_Pp6ww_9v.png"})
|
s.db.CreateApplication(&model.Application{UserID: 5, ID: 1, Image: "PorrUa5b1IIK3yKo_Pp6ww_9v.png"})
|
||||||
|
|
||||||
|
|
@ -397,7 +289,7 @@ func (s *TokenSuite) Test_UploadAppImage_WithImageFile_DeleteExstingImageAndGene
|
||||||
assert.Nil(s.T(), os.Remove("Zal6-ySIuL-T3EMLCcFtityHn.png"))
|
assert.Nil(s.T(), os.Remove("Zal6-ySIuL-T3EMLCcFtityHn.png"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_UploadAppImage_WithImageFile_DeleteExistingImage() {
|
func (s *ApplicationSuite) Test_UploadAppImage_WithImageFile_DeleteExistingImage() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
s.db.CreateApplication(&model.Application{UserID: 5, ID: 1, Image: "existing.png"})
|
s.db.CreateApplication(&model.Application{UserID: 5, ID: 1, Image: "existing.png"})
|
||||||
|
|
||||||
|
|
@ -419,7 +311,7 @@ func (s *TokenSuite) Test_UploadAppImage_WithImageFile_DeleteExistingImage() {
|
||||||
os.Remove("PorrUa5b1IIK3yKo_Pp6ww_9v.png")
|
os.Remove("PorrUa5b1IIK3yKo_Pp6ww_9v.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_UploadAppImage_WithTextFile_expectBadRequest() {
|
func (s *ApplicationSuite) Test_UploadAppImage_WithTextFile_expectBadRequest() {
|
||||||
s.db.User(5).App(1)
|
s.db.User(5).App(1)
|
||||||
|
|
||||||
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/text.txt")})
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/text.txt")})
|
||||||
|
|
@ -435,7 +327,7 @@ func (s *TokenSuite) Test_UploadAppImage_WithTextFile_expectBadRequest() {
|
||||||
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("file must be an image"))
|
assert.Equal(s.T(), s.ctx.Errors[0].Err, errors.New("file must be an image"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_UploadAppImage_expectNotFound() {
|
func (s *ApplicationSuite) Test_UploadAppImage_expectNotFound() {
|
||||||
s.db.User(5)
|
s.db.User(5)
|
||||||
|
|
||||||
test.WithUser(s.ctx, 5)
|
test.WithUser(s.ctx, 5)
|
||||||
|
|
@ -447,7 +339,7 @@ func (s *TokenSuite) Test_UploadAppImage_expectNotFound() {
|
||||||
assert.Equal(s.T(), 404, s.recorder.Code)
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) Test_UploadAppImage_WithSaveError_expectServerError() {
|
func (s *ApplicationSuite) Test_UploadAppImage_WithSaveError_expectServerError() {
|
||||||
s.db.User(5).App(1)
|
s.db.User(5).App(1)
|
||||||
|
|
||||||
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
cType, buffer, err := upload(map[string]*os.File{"file": mustOpen("../test/assets/image.png")})
|
||||||
|
|
@ -463,13 +355,105 @@ func (s *TokenSuite) Test_UploadAppImage_WithSaveError_expectServerError() {
|
||||||
assert.Equal(s.T(), 500, s.recorder.Code)
|
assert.Equal(s.T(), 500, s.recorder.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenSuite) withFormData(formData string) {
|
func (s *ApplicationSuite) Test_UpdateApplicationNameAndDescription_expectSuccess() {
|
||||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(formData))
|
s.db.User(5).NewAppWithToken(2, "app-2")
|
||||||
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
|
test.WithUser(s.ctx, 5)
|
||||||
|
s.withFormData("name=new_name&description=new_description_text")
|
||||||
|
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
expected := &model.Application{
|
||||||
|
ID: 2,
|
||||||
|
Token: "app-2",
|
||||||
|
UserID: 5,
|
||||||
|
Name: "new_name",
|
||||||
|
Description: "new_description_text",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
assert.Equal(s.T(), expected, s.db.GetApplicationByID(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func withURL(ctx *gin.Context, scheme, host string) {
|
func (s *ApplicationSuite) Test_UpdateApplicationName_expectSuccess() {
|
||||||
ctx.Set("location", &url.URL{Scheme: scheme, Host: host})
|
s.db.User(5).NewAppWithToken(2, "app-2")
|
||||||
|
|
||||||
|
test.WithUser(s.ctx, 5)
|
||||||
|
s.withFormData("name=new_name")
|
||||||
|
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
expected := &model.Application{
|
||||||
|
ID: 2,
|
||||||
|
Token: "app-2",
|
||||||
|
UserID: 5,
|
||||||
|
Name: "new_name",
|
||||||
|
Description: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
assert.Equal(s.T(), expected, s.db.GetApplicationByID(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApplicationSuite) Test_UpdateApplication_preservesImage() {
|
||||||
|
app := s.db.User(5).NewAppWithToken(2, "app-2")
|
||||||
|
app.Image = "existing.png"
|
||||||
|
assert.Nil(s.T(), s.db.UpdateApplication(app))
|
||||||
|
|
||||||
|
test.WithUser(s.ctx, 5)
|
||||||
|
s.withFormData("name=new_name")
|
||||||
|
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
|
||||||
|
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
assert.Equal(s.T(), "existing.png", s.db.GetApplicationByID(2).Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApplicationSuite) Test_UpdateApplication_setEmptyDescription() {
|
||||||
|
app := s.db.User(5).NewAppWithToken(2, "app-2")
|
||||||
|
app.Description = "my desc"
|
||||||
|
assert.Nil(s.T(), s.db.UpdateApplication(app))
|
||||||
|
|
||||||
|
test.WithUser(s.ctx, 5)
|
||||||
|
s.withFormData("name=new_name&desc=")
|
||||||
|
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
|
||||||
|
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
assert.Equal(s.T(), "", s.db.GetApplicationByID(2).Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApplicationSuite) Test_UpdateApplication_expectNotFound() {
|
||||||
|
test.WithUser(s.ctx, 5)
|
||||||
|
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApplicationSuite) Test_UpdateApplication_WithMissingAttributes_expectBadRequest() {
|
||||||
|
test.WithUser(s.ctx, 5)
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 400, s.recorder.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApplicationSuite) Test_UpdateApplication_WithoutPermission_expectNotFound() {
|
||||||
|
s.db.User(5).NewAppWithToken(2, "app-2")
|
||||||
|
|
||||||
|
test.WithUser(s.ctx, 4)
|
||||||
|
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
|
||||||
|
|
||||||
|
s.a.UpdateApplication(s.ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 404, s.recorder.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ApplicationSuite) 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// A modified version of https://stackoverflow.com/a/20397167/4244993 from Attila O.
|
// A modified version of https://stackoverflow.com/a/20397167/4244993 from Attila O.
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gotify/server/auth"
|
||||||
|
"github.com/gotify/server/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The ClientDatabase interface for encapsulating database access.
|
||||||
|
type ClientDatabase interface {
|
||||||
|
CreateClient(client *model.Client) error
|
||||||
|
GetClientByToken(token string) *model.Client
|
||||||
|
GetClientByID(id uint) *model.Client
|
||||||
|
GetClientsByUser(userID uint) []*model.Client
|
||||||
|
DeleteClientByID(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ClientAPI provides handlers for managing clients and applications.
|
||||||
|
type ClientAPI struct {
|
||||||
|
DB ClientDatabase
|
||||||
|
ImageDir string
|
||||||
|
NotifyDeleted func(uint, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateClient creates a client and returns the access token.
|
||||||
|
func (a *ClientAPI) CreateClient(ctx *gin.Context) {
|
||||||
|
client := model.Client{}
|
||||||
|
if err := ctx.Bind(&client); err == nil {
|
||||||
|
client.Token = generateNotExistingToken(auth.GenerateClientToken, a.clientExists)
|
||||||
|
client.UserID = auth.GetUserID(ctx)
|
||||||
|
a.DB.CreateClient(&client)
|
||||||
|
ctx.JSON(200, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClients returns all clients a user has.
|
||||||
|
func (a *ClientAPI) GetClients(ctx *gin.Context) {
|
||||||
|
userID := auth.GetUserID(ctx)
|
||||||
|
clients := a.DB.GetClientsByUser(userID)
|
||||||
|
ctx.JSON(200, clients)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteClient deletes a client by its id.
|
||||||
|
func (a *ClientAPI) DeleteClient(ctx *gin.Context) {
|
||||||
|
withID(ctx, "id", func(id uint) {
|
||||||
|
if client := a.DB.GetClientByID(id); client != nil && client.UserID == auth.GetUserID(ctx) {
|
||||||
|
a.NotifyDeleted(client.UserID, client.Token)
|
||||||
|
a.DB.DeleteClientByID(id)
|
||||||
|
} else {
|
||||||
|
ctx.AbortWithError(404, fmt.Errorf("client with id %d doesn't exists", id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ClientAPI) clientExists(token string) bool {
|
||||||
|
return a.DB.GetClientByToken(token) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateNotExistingToken(generateToken func() string, tokenExists func(token string) bool) string {
|
||||||
|
for {
|
||||||
|
token := generateToken()
|
||||||
|
if !tokenExists(token) {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"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 (
|
||||||
|
firstClientToken = "CPorrUa5b1IIK3y"
|
||||||
|
secondClientToken = "CKo_Pp6ww_9vZal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ClientSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
db *test.Database
|
||||||
|
a *ClientAPI
|
||||||
|
ctx *gin.Context
|
||||||
|
recorder *httptest.ResponseRecorder
|
||||||
|
notified bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) 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.notified = false
|
||||||
|
s.a = &ClientAPI{DB: s.db, NotifyDeleted: s.notify}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) notify(uint, string) {
|
||||||
|
s.notified = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) AfterTest(suiteName, testName string) {
|
||||||
|
s.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) 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 *ClientSuite) 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 *ClientSuite) 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 *ClientSuite) 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 *ClientSuite) 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 *ClientSuite) 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 *ClientSuite) 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 *ClientSuite) 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 *ClientSuite) 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"}}
|
||||||
|
|
||||||
|
assert.False(s.T(), s.notified)
|
||||||
|
|
||||||
|
s.a.DeleteClient(s.ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||||
|
s.db.AssertClientNotExist(8)
|
||||||
|
assert.True(s.T(), s.notified)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientSuite) 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})
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,6 @@ func (d *GormDatabase) GetApplicationsByUser(userID uint) []*model.Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateApplication updates an application.
|
// UpdateApplication updates an application.
|
||||||
func (d *GormDatabase) UpdateApplication(app *model.Application) {
|
func (d *GormDatabase) UpdateApplication(app *model.Application) error {
|
||||||
d.DB.Save(app)
|
return d.DB.Save(app).Error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
//
|
//
|
||||||
// Schemes: http, https
|
// Schemes: http, https
|
||||||
// Host: localhost
|
// Host: localhost
|
||||||
// Version: 1.0.5
|
// Version: 1.0.6
|
||||||
// License: MIT https://github.com/gotify/server/blob/master/LICENSE
|
// License: MIT https://github.com/gotify/server/blob/master/LICENSE
|
||||||
//
|
//
|
||||||
// Consumes:
|
// Consumes:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
"name": "MIT",
|
"name": "MIT",
|
||||||
"url": "https://github.com/gotify/server/blob/master/LICENSE"
|
"url": "https://github.com/gotify/server/blob/master/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "1.0.5"
|
"version": "1.0.6"
|
||||||
},
|
},
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"token"
|
"application"
|
||||||
],
|
],
|
||||||
"summary": "Return all applications.",
|
"summary": "Return all applications.",
|
||||||
"operationId": "getApps",
|
"operationId": "getApps",
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"token"
|
"application"
|
||||||
],
|
],
|
||||||
"summary": "Create an application.",
|
"summary": "Create an application.",
|
||||||
"operationId": "createApp",
|
"operationId": "createApp",
|
||||||
|
|
@ -126,6 +126,74 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/application/{id}": {
|
"/application/{id}": {
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"clientTokenHeader": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clientTokenQuery": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"basicAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Update info for an application",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"application"
|
||||||
|
],
|
||||||
|
"operationId": "updateApplication",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "the application to update",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Application"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "the application id",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Ok",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Application"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
|
|
@ -145,7 +213,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"token"
|
"application"
|
||||||
],
|
],
|
||||||
"summary": "Delete an application.",
|
"summary": "Delete an application.",
|
||||||
"operationId": "deleteApp",
|
"operationId": "deleteApp",
|
||||||
|
|
@ -198,7 +266,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"token"
|
"application"
|
||||||
],
|
],
|
||||||
"operationId": "uploadAppImage",
|
"operationId": "uploadAppImage",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
|
@ -374,7 +442,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"token"
|
"client"
|
||||||
],
|
],
|
||||||
"summary": "Return all clients.",
|
"summary": "Return all clients.",
|
||||||
"operationId": "getClients",
|
"operationId": "getClients",
|
||||||
|
|
@ -421,7 +489,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"token"
|
"client"
|
||||||
],
|
],
|
||||||
"summary": "Create a client.",
|
"summary": "Create a client.",
|
||||||
"operationId": "createClient",
|
"operationId": "createClient",
|
||||||
|
|
@ -478,7 +546,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"token"
|
"client"
|
||||||
],
|
],
|
||||||
"summary": "Delete a client.",
|
"summary": "Delete a client.",
|
||||||
"operationId": "deleteClient",
|
"operationId": "deleteClient",
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,17 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
streamHandler := stream.New(200*time.Second, 15*time.Second)
|
streamHandler := stream.New(200*time.Second, 15*time.Second)
|
||||||
authentication := auth.Auth{DB: db}
|
authentication := auth.Auth{DB: db}
|
||||||
messageHandler := api.MessageAPI{Notifier: streamHandler, DB: db}
|
messageHandler := api.MessageAPI{Notifier: streamHandler, DB: db}
|
||||||
tokenHandler := api.TokenAPI{DB: db, ImageDir: conf.UploadedImagesDir, NotifyDeleted: streamHandler.NotifyDeletedClient}
|
clientHandler := api.ClientAPI{
|
||||||
|
DB: db,
|
||||||
|
ImageDir: conf.UploadedImagesDir,
|
||||||
|
NotifyDeleted: streamHandler.NotifyDeletedClient,
|
||||||
|
}
|
||||||
|
applicationHandler := api.ApplicationAPI{
|
||||||
|
DB: db,
|
||||||
|
ImageDir: conf.UploadedImagesDir,
|
||||||
|
}
|
||||||
userHandler := api.UserAPI{DB: db, PasswordStrength: conf.PassStrength, NotifyDeleted: streamHandler.NotifyDeletedUser}
|
userHandler := api.UserAPI{DB: db, PasswordStrength: conf.PassStrength, NotifyDeleted: streamHandler.NotifyDeletedUser}
|
||||||
|
|
||||||
g := gin.New()
|
g := gin.New()
|
||||||
|
|
||||||
g.Use(gin.Logger(), gin.Recovery(), error.Handler(), location.Default())
|
g.Use(gin.Logger(), gin.Recovery(), error.Handler(), location.Default())
|
||||||
|
|
@ -103,7 +112,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
clientAuth.Use(authentication.RequireClient())
|
clientAuth.Use(authentication.RequireClient())
|
||||||
app := clientAuth.Group("/application")
|
app := clientAuth.Group("/application")
|
||||||
{
|
{
|
||||||
// swagger:operation GET /application token getApps
|
// swagger:operation GET /application application getApps
|
||||||
//
|
//
|
||||||
// Return all applications.
|
// Return all applications.
|
||||||
//
|
//
|
||||||
|
|
@ -131,9 +140,9 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
// description: Forbidden
|
// description: Forbidden
|
||||||
// schema:
|
// schema:
|
||||||
// $ref: "#/definitions/Error"
|
// $ref: "#/definitions/Error"
|
||||||
app.GET("", tokenHandler.GetApplications)
|
app.GET("", applicationHandler.GetApplications)
|
||||||
|
|
||||||
// swagger:operation POST /application token createApp
|
// swagger:operation POST /application application createApp
|
||||||
//
|
//
|
||||||
// Create an application.
|
// Create an application.
|
||||||
//
|
//
|
||||||
|
|
@ -166,9 +175,9 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
// description: Forbidden
|
// description: Forbidden
|
||||||
// schema:
|
// schema:
|
||||||
// $ref: "#/definitions/Error"
|
// $ref: "#/definitions/Error"
|
||||||
app.POST("", tokenHandler.CreateApplication)
|
app.POST("", applicationHandler.CreateApplication)
|
||||||
|
|
||||||
// swagger:operation POST /application/{id}/image token uploadAppImage
|
// swagger:operation POST /application/{id}/image application uploadAppImage
|
||||||
//
|
//
|
||||||
// Upload an image for an application
|
// Upload an image for an application
|
||||||
//
|
//
|
||||||
|
|
@ -205,9 +214,53 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
// description: Forbidden
|
// description: Forbidden
|
||||||
// schema:
|
// schema:
|
||||||
// $ref: "#/definitions/Error"
|
// $ref: "#/definitions/Error"
|
||||||
app.POST("/:id/image", tokenHandler.UploadApplicationImage)
|
app.POST("/:id/image", applicationHandler.UploadApplicationImage)
|
||||||
|
|
||||||
// swagger:operation DELETE /application/{id} token deleteApp
|
// swagger:operation PUT /application/{id} application updateApplication
|
||||||
|
//
|
||||||
|
// Update info for an application
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// security:
|
||||||
|
// - clientTokenHeader: []
|
||||||
|
// - clientTokenQuery: []
|
||||||
|
// - basicAuth: []
|
||||||
|
// parameters:
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// description: the application to update
|
||||||
|
// required: true
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Application"
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: the application id
|
||||||
|
// required: true
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: Ok
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Application"
|
||||||
|
// 400:
|
||||||
|
// description: Bad Request
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Error"
|
||||||
|
// 401:
|
||||||
|
// description: Unauthorized
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Error"
|
||||||
|
// 403:
|
||||||
|
// description: Forbidden
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Error"
|
||||||
|
app.PUT("/:id", applicationHandler.UpdateApplication)
|
||||||
|
|
||||||
|
// swagger:operation DELETE /application/{id} application deleteApp
|
||||||
//
|
//
|
||||||
// Delete an application.
|
// Delete an application.
|
||||||
//
|
//
|
||||||
|
|
@ -237,7 +290,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
// description: Forbidden
|
// description: Forbidden
|
||||||
// schema:
|
// schema:
|
||||||
// $ref: "#/definitions/Error"
|
// $ref: "#/definitions/Error"
|
||||||
app.DELETE("/:id", tokenHandler.DeleteApplication)
|
app.DELETE("/:id", applicationHandler.DeleteApplication)
|
||||||
|
|
||||||
tokenMessage := app.Group("/:id/message")
|
tokenMessage := app.Group("/:id/message")
|
||||||
{
|
{
|
||||||
|
|
@ -321,7 +374,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
|
|
||||||
client := clientAuth.Group("/client")
|
client := clientAuth.Group("/client")
|
||||||
{
|
{
|
||||||
// swagger:operation GET /client token getClients
|
// swagger:operation GET /client client getClients
|
||||||
//
|
//
|
||||||
// Return all clients.
|
// Return all clients.
|
||||||
//
|
//
|
||||||
|
|
@ -349,9 +402,9 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
// description: Forbidden
|
// description: Forbidden
|
||||||
// schema:
|
// schema:
|
||||||
// $ref: "#/definitions/Error"
|
// $ref: "#/definitions/Error"
|
||||||
client.GET("", tokenHandler.GetClients)
|
client.GET("", clientHandler.GetClients)
|
||||||
|
|
||||||
// swagger:operation POST /client token createClient
|
// swagger:operation POST /client client createClient
|
||||||
//
|
//
|
||||||
// Create a client.
|
// Create a client.
|
||||||
//
|
//
|
||||||
|
|
@ -384,9 +437,9 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
// description: Forbidden
|
// description: Forbidden
|
||||||
// schema:
|
// schema:
|
||||||
// $ref: "#/definitions/Error"
|
// $ref: "#/definitions/Error"
|
||||||
client.POST("", tokenHandler.CreateClient)
|
client.POST("", clientHandler.CreateClient)
|
||||||
|
|
||||||
// swagger:operation DELETE /client/{id} token deleteClient
|
// swagger:operation DELETE /client/{id} client deleteClient
|
||||||
//
|
//
|
||||||
// Delete a client.
|
// Delete a client.
|
||||||
//
|
//
|
||||||
|
|
@ -416,7 +469,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
|
||||||
// description: Forbidden
|
// description: Forbidden
|
||||||
// schema:
|
// schema:
|
||||||
// $ref: "#/definitions/Error"
|
// $ref: "#/definitions/Error"
|
||||||
client.DELETE("/:id", tokenHandler.DeleteClient)
|
client.DELETE("/:id", clientHandler.DeleteClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
message := clientAuth.Group("/message")
|
message := clientAuth.Group("/message")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue