Add message api

This commit is contained in:
Jannis Mattheis 2018-01-30 21:42:06 +01:00 committed by Jannis Mattheis
parent 8e8705c6e5
commit 9178d2d08b
4 changed files with 473 additions and 6 deletions

83
api/message.go Normal file
View File

@ -0,0 +1,83 @@
package api
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/jmattheis/memo/auth"
"github.com/jmattheis/memo/model"
"strconv"
"time"
)
// The MessageDatabase interface for encapsulating database access.
type MessageDatabase interface {
GetMessagesByUserAndApplication(userID uint, tokenID string) []*model.Message
GetApplicationByID(id string) *model.Application
GetMessagesByUser(userID uint) []*model.Message
DeleteMessageByID(id uint) error
GetMessageByID(id uint) *model.Message
DeleteMessagesByUser(userID uint) error
DeleteMessagesByApplication(applicationID string) error
CreateMessage(message *model.Message) error
}
// The MessageAPI provides handlers for managing messages.
type MessageAPI struct {
DB MessageDatabase
}
// GetMessages returns all messages from a user.
func (a *MessageAPI) GetMessages(ctx *gin.Context) {
userID := auth.GetUserID(ctx)
messages := a.DB.GetMessagesByUser(userID)
ctx.JSON(200, messages)
}
// GetMessagesWithApplication returns all messages from a specific application.
func (a *MessageAPI) GetMessagesWithApplication(ctx *gin.Context) {
appID := ctx.Param("appid")
userID := auth.GetUserID(ctx)
messages := a.DB.GetMessagesByUserAndApplication(userID, appID)
ctx.JSON(200, messages)
}
// DeleteMessages delete all messages from a user.
func (a *MessageAPI) DeleteMessages(ctx *gin.Context) {
userID := auth.GetUserID(ctx)
a.DB.DeleteMessagesByUser(userID)
}
// DeleteMessageWithApplication deletes all messages from a specific application.
func (a *MessageAPI) DeleteMessageWithApplication(ctx *gin.Context) {
appID := ctx.Param("appid")
if application := a.DB.GetApplicationByID(appID); application != nil && application.UserID == auth.GetUserID(ctx) {
a.DB.DeleteMessagesByApplication(appID)
} else {
ctx.AbortWithError(404, errors.New("application does not exists"))
}
}
// DeleteMessage deletes a message with an id.
func (a *MessageAPI) DeleteMessage(ctx *gin.Context) {
id := ctx.Param("id")
if parsedUInt, err := strconv.ParseUint(id, 10, 32); err == nil {
if msg := a.DB.GetMessageByID(uint(parsedUInt)); msg != nil && a.DB.GetApplicationByID(msg.TokenID).UserID == auth.GetUserID(ctx) {
a.DB.DeleteMessageByID(uint(parsedUInt))
} else {
ctx.AbortWithError(404, errors.New("message does not exists"))
}
} else {
ctx.AbortWithError(400, errors.New("message does not exist"))
}
}
// CreateMessage creates a message, authentication via application-token is required.
func (a *MessageAPI) CreateMessage(ctx *gin.Context) {
message := model.Message{}
if err := ctx.Bind(&message); err == nil {
message.TokenID = auth.GetTokenID(ctx)
message.Date = time.Now()
a.DB.CreateMessage(&message)
ctx.JSON(200, message)
}
}

254
api/message_test.go Normal file
View File

@ -0,0 +1,254 @@
package api
import (
"github.com/bouk/monkey"
"github.com/gin-gonic/gin"
apimock "github.com/jmattheis/memo/api/mock"
"github.com/jmattheis/memo/auth"
"github.com/jmattheis/memo/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"io/ioutil"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestMessageSuite(t *testing.T) {
suite.Run(t, new(MessageSuite))
}
type MessageSuite struct {
suite.Suite
db *apimock.MockMessageDatabase
a *MessageAPI
ctx *gin.Context
recorder *httptest.ResponseRecorder
}
func (s *MessageSuite) BeforeTest(suiteName, testName string) {
gin.SetMode(gin.TestMode)
s.recorder = httptest.NewRecorder()
s.ctx, _ = gin.CreateTestContext(s.recorder)
s.db = &apimock.MockMessageDatabase{}
s.a = &MessageAPI{DB: s.db}
}
func (s *MessageSuite) Test_GetMessages() {
auth.RegisterAuthentication(s.ctx, nil, 5, "")
t, _ := time.Parse("2006/01/02", "2017/01/02")
s.db.On("GetMessagesByUser", uint(5)).Return([]*model.Message{{ID: 1, TokenID: "asd", Message: "OH HELLO THERE", Date: t, Title: "wup", Priority: 2}, {ID: 2, TokenID: "cloud", Message: "hi", Title: "hi", Date: t, Priority: 4}})
s.a.GetMessages(s.ctx)
assert.Equal(s.T(), 200, s.recorder.Code)
bytes, _ := ioutil.ReadAll(s.recorder.Body)
assert.JSONEq(s.T(), `[{"id":1,"tokenid":"asd","message":"OH HELLO THERE","title":"wup","priority":2,"date":"2017-01-02T00:00:00Z"},{"id":2,"tokenid":"cloud","message":"hi","title":"hi","priority":4,"date":"2017-01-02T00:00:00Z"}]`, string(bytes))
}
func (s *MessageSuite) Test_GetMessagesWithToken() {
auth.RegisterAuthentication(s.ctx, nil, 4, "")
t, _ := time.Parse("2006/01/02", "2021/01/02")
s.db.On("GetMessagesByUserAndApplication", uint(4), "mytoken").Return([]*model.Message{{ID: 2, TokenID: "mytoken", Message: "hi", Title: "hi", Date: t, Priority: 4}})
s.ctx.Params = gin.Params{{Key: "appid", Value: "mytoken"}}
s.a.GetMessagesWithApplication(s.ctx)
assert.Equal(s.T(), 200, s.recorder.Code)
bytes, _ := ioutil.ReadAll(s.recorder.Body)
assert.JSONEq(s.T(), `[{"id":2,"tokenid":"mytoken","message":"hi","title":"hi","priority":4,"date":"2021-01-02T00:00:00Z"}]`, string(bytes))
}
func (s *MessageSuite) Test_DeleteMessage_invalidID() {
s.ctx.Params = gin.Params{{Key: "id", Value: "string"}}
s.a.DeleteMessage(s.ctx)
assert.Equal(s.T(), 400, s.recorder.Code)
}
func (s *MessageSuite) Test_DeleteMessage_notExistingID() {
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
s.db.On("GetMessageByID", uint(1)).Return(nil)
s.a.DeleteMessage(s.ctx)
assert.Equal(s.T(), 404, s.recorder.Code)
}
func (s *MessageSuite) Test_DeleteMessage_existingIDButNotOwner() {
auth.RegisterAuthentication(s.ctx, nil, 6, "")
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
s.db.On("GetMessageByID", uint(1)).Return(&model.Message{ID: 1, TokenID: "token"})
s.db.On("GetApplicationByID", "token").Return(&model.Application{ID: "token", UserID: 2})
s.a.DeleteMessage(s.ctx)
assert.Equal(s.T(), 404, s.recorder.Code)
}
func (s *MessageSuite) Test_DeleteMessage() {
auth.RegisterAuthentication(s.ctx, nil, 2, "")
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
s.db.On("GetMessageByID", uint(1)).Return(&model.Message{ID: 1, TokenID: "token"})
s.db.On("GetApplicationByID", "token").Return(&model.Application{ID: "token", UserID: 2})
s.db.On("DeleteMessageByID", uint(1)).Return(nil)
s.a.DeleteMessage(s.ctx)
s.db.AssertCalled(s.T(), "DeleteMessageByID", uint(1))
assert.Equal(s.T(), 200, s.recorder.Code)
}
func (s *MessageSuite) Test_DeleteMessageWithToken() {
auth.RegisterAuthentication(s.ctx, nil, 2, "")
s.ctx.Params = gin.Params{{Key: "appid", Value: "mytoken"}}
s.db.On("GetApplicationByID", "mytoken").Return(&model.Application{ID: "mytoken", UserID: 2})
s.db.On("DeleteMessagesByApplication", "mytoken").Return(nil)
s.a.DeleteMessageWithApplication(s.ctx)
s.db.AssertCalled(s.T(), "DeleteMessagesByApplication", "mytoken")
assert.Equal(s.T(), 200, s.recorder.Code)
}
func (s *MessageSuite) Test_DeleteMessageWithToken_notExistingToken() {
auth.RegisterAuthentication(s.ctx, nil, 2, "")
s.ctx.Params = gin.Params{{Key: "appid", Value: "asdasdasd"}}
s.db.On("GetApplicationByID", "asdasdasd").Return(nil)
s.db.On("DeleteMessagesByApplication", "asdasdasd").Return(nil)
s.a.DeleteMessageWithApplication(s.ctx)
s.db.AssertNotCalled(s.T(), "DeleteMessagesByApplication", "mytoken")
assert.Equal(s.T(), 404, s.recorder.Code)
}
func (s *MessageSuite) Test_DeleteMessageWithToken_notOwner() {
auth.RegisterAuthentication(s.ctx, nil, 4, "")
s.ctx.Params = gin.Params{{Key: "appid", Value: "mytoken"}}
s.db.On("GetApplicationByID", "mytoken").Return(&model.Application{ID: "mytoken", UserID: 2})
s.db.On("DeleteMessagesByApplication", "mytoken").Return(nil)
s.a.DeleteMessageWithApplication(s.ctx)
s.db.AssertNotCalled(s.T(), "DeleteMessagesByApplication", "mytoken")
assert.Equal(s.T(), 404, s.recorder.Code)
}
func (s *MessageSuite) Test_DeleteMessages() {
auth.RegisterAuthentication(s.ctx, nil, 4, "")
s.db.On("DeleteMessagesByUser", uint(4)).Return(nil)
s.a.DeleteMessages(s.ctx)
s.db.AssertCalled(s.T(), "DeleteMessagesByUser", uint(4))
assert.Equal(s.T(), 200, s.recorder.Code)
}
func (s *MessageSuite) Test_CreateMessage_onJson_allParams() {
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
t, _ := time.Parse("2006/01/02", "2017/01/02")
monkey.Patch(time.Now, func() time.Time { return t })
expected := &model.Message{ID: 0, TokenID: "app-token", Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle", "message": "mymessage", "priority": 1}`))
s.ctx.Request.Header.Set("Content-Type", "application/json")
s.db.On("CreateMessage", expected).Return(nil)
s.a.CreateMessage(s.ctx)
s.db.AssertCalled(s.T(), "CreateMessage", expected)
assert.Equal(s.T(), 200, s.recorder.Code)
}
func (s *MessageSuite) Test_CreateMessage_onlyRequired() {
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
t, _ := time.Parse("2006/01/02", "2017/01/02")
monkey.Patch(time.Now, func() time.Time { return t })
expected := &model.Message{ID: 0, TokenID: "app-token", Title: "mytitle", Message: "mymessage", Date: t}
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle", "message": "mymessage"}`))
s.ctx.Request.Header.Set("Content-Type", "application/json")
s.db.On("CreateMessage", expected).Return(nil)
s.a.CreateMessage(s.ctx)
s.db.AssertCalled(s.T(), "CreateMessage", expected)
assert.Equal(s.T(), 200, s.recorder.Code)
}
func (s *MessageSuite) Test_CreateMessage_failWhenNoMessage() {
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle"}`))
s.ctx.Request.Header.Set("Content-Type", "application/json")
s.a.CreateMessage(s.ctx)
s.db.AssertNotCalled(s.T(), "CreateMessage", mock.Anything)
assert.Equal(s.T(), 400, s.recorder.Code)
}
func (s *MessageSuite) Test_CreateMessage_failWhenNoTitle() {
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"message": "mymessage"}`))
s.ctx.Request.Header.Set("Content-Type", "application/json")
s.a.CreateMessage(s.ctx)
s.db.AssertNotCalled(s.T(), "CreateMessage", mock.Anything)
assert.Equal(s.T(), 400, s.recorder.Code)
}
func (s *MessageSuite) Test_CreateMessage_failWhenPriorityNotNumber() {
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle", "message": "mymessage", "priority": "asd"}`))
s.ctx.Request.Header.Set("Content-Type", "application/json")
s.a.CreateMessage(s.ctx)
s.db.AssertNotCalled(s.T(), "CreateMessage", mock.Anything)
assert.Equal(s.T(), 400, s.recorder.Code)
}
func (s *MessageSuite) Test_CreateMessage_onQueryData() {
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
t, _ := time.Parse("2006/01/02", "2017/01/02")
monkey.Patch(time.Now, func() time.Time { return t })
expected := &model.Message{ID: 0, TokenID: "app-token", Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
s.ctx.Request = httptest.NewRequest("POST", "/token?title=mytitle&message=mymessage&priority=1", nil)
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
s.db.On("CreateMessage", expected).Return(nil)
s.a.CreateMessage(s.ctx)
s.db.AssertCalled(s.T(), "CreateMessage", expected)
assert.Equal(s.T(), 200, s.recorder.Code)
}
func (s *MessageSuite) Test_CreateMessage_onFormData() {
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
t, _ := time.Parse("2006/01/02", "2017/01/02")
monkey.Patch(time.Now, func() time.Time { return t })
expected := &model.Message{ID: 0, TokenID: "app-token", Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader("title=mytitle&message=mymessage&priority=1"))
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
s.db.On("CreateMessage", expected).Return(nil)
s.a.CreateMessage(s.ctx)
s.db.AssertCalled(s.T(), "CreateMessage", expected)
assert.Equal(s.T(), 200, s.recorder.Code)
}

View File

@ -0,0 +1,130 @@
// Code generated by mockery v1.0.0
package mock
import mock "github.com/stretchr/testify/mock"
import model "github.com/jmattheis/memo/model"
// MockMessageDatabase is an autogenerated mock type for the MessageDatabase type
type MockMessageDatabase struct {
mock.Mock
}
// CreateMessage provides a mock function with given fields: message
func (_m *MockMessageDatabase) CreateMessage(message *model.Message) error {
ret := _m.Called(message)
var r0 error
if rf, ok := ret.Get(0).(func(*model.Message) error); ok {
r0 = rf(message)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteMessageByID provides a mock function with given fields: id
func (_m *MockMessageDatabase) DeleteMessageByID(id uint) error {
ret := _m.Called(id)
var r0 error
if rf, ok := ret.Get(0).(func(uint) error); ok {
r0 = rf(id)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteMessagesByApplication provides a mock function with given fields: applicationID
func (_m *MockMessageDatabase) DeleteMessagesByApplication(applicationID string) error {
ret := _m.Called(applicationID)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(applicationID)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteMessagesByUser provides a mock function with given fields: userID
func (_m *MockMessageDatabase) DeleteMessagesByUser(userID uint) error {
ret := _m.Called(userID)
var r0 error
if rf, ok := ret.Get(0).(func(uint) error); ok {
r0 = rf(userID)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetApplicationByID provides a mock function with given fields: id
func (_m *MockMessageDatabase) GetApplicationByID(id string) *model.Application {
ret := _m.Called(id)
var r0 *model.Application
if rf, ok := ret.Get(0).(func(string) *model.Application); ok {
r0 = rf(id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Application)
}
}
return r0
}
// GetMessageByID provides a mock function with given fields: id
func (_m *MockMessageDatabase) GetMessageByID(id uint) *model.Message {
ret := _m.Called(id)
var r0 *model.Message
if rf, ok := ret.Get(0).(func(uint) *model.Message); ok {
r0 = rf(id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Message)
}
}
return r0
}
// GetMessagesByUser provides a mock function with given fields: userID
func (_m *MockMessageDatabase) GetMessagesByUser(userID uint) []*model.Message {
ret := _m.Called(userID)
var r0 []*model.Message
if rf, ok := ret.Get(0).(func(uint) []*model.Message); ok {
r0 = rf(userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Message)
}
}
return r0
}
// GetMessagesByUserAndApplication provides a mock function with given fields: userID, tokenID
func (_m *MockMessageDatabase) GetMessagesByUserAndApplication(userID uint, tokenID string) []*model.Message {
ret := _m.Called(userID, tokenID)
var r0 []*model.Message
if rf, ok := ret.Get(0).(func(uint, string) []*model.Message); ok {
r0 = rf(userID, tokenID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Message)
}
}
return r0
}

View File

@ -4,10 +4,10 @@ import "time"
// The Message holds information about a message which was sent by an Application.
type Message struct {
ID uint `gorm:"AUTO_INCREMENT;primary_key;index"`
TokenID string
Message string
Title string
Priority int
Date time.Time
ID uint `gorm:"AUTO_INCREMENT;primary_key;index" json:"id"`
TokenID string `json:"tokenid"`
Message string `form:"message" query:"message" json:"message" binding:"required"`
Title string `form:"title" query:"title" json:"title" binding:"required"`
Priority int `form:"priority" query:"priority" json:"priority"`
Date time.Time `json:"date"`
}