package api import ( "fmt" "io/ioutil" "net/http/httptest" "strings" "testing" "github.com/bouk/monkey" "github.com/gin-gonic/gin" apimock "github.com/gotify/server/api/mock" "github.com/gotify/server/auth" "github.com/gotify/server/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "github.com/gotify/server/mode" ) var ( adminUser = &model.User{ID: 1, Name: "jmattheis", Pass: []byte{1, 2}, Admin: true} adminUserJSON = `{"id":1,"name":"jmattheis","admin":true}` normalUser = &model.User{ID: 2, Name: "nicories", Pass: []byte{2, 3}, Admin: false} normalUserJSON = `{"id":2,"name":"nicories","admin":false}` ) func TestUserSuite(t *testing.T) { suite.Run(t, new(UserSuite)) } type UserSuite struct { suite.Suite db *apimock.MockUserDatabase a *UserAPI ctx *gin.Context recorder *httptest.ResponseRecorder } func (s *UserSuite) BeforeTest(suiteName, testName string) { mode.Set(mode.TestDev) s.recorder = httptest.NewRecorder() s.ctx, _ = gin.CreateTestContext(s.recorder) s.db = &apimock.MockUserDatabase{} s.a = &UserAPI{DB: s.db} } func (s *UserSuite) Test_GetUsers() { s.db.On("GetUsers").Return([]*model.User{adminUser, normalUser}) s.a.GetUsers(s.ctx) s.expectJSON(fmt.Sprintf("[%s, %s]", adminUserJSON, normalUserJSON)) } func (s *UserSuite) Test_GetCurrentUser() { patch := monkey.Patch(auth.GetUserID, func(*gin.Context) uint { return 1 }) defer patch.Unpatch() s.db.On("GetUserByID", uint(1)).Return(adminUser) s.a.GetCurrentUser(s.ctx) s.expectJSON(adminUserJSON) } func (s *UserSuite) Test_GetUserByID() { s.db.On("GetUserByID", uint(2)).Return(normalUser) s.ctx.Params = gin.Params{{Key: "id", Value: "2"}} s.a.GetUserByID(s.ctx) s.expectJSON(normalUserJSON) } func (s *UserSuite) Test_GetUserByID_InvalidID() { s.db.On("GetUserByID", uint(2)).Return(normalUser) s.ctx.Params = gin.Params{{Key: "id", Value: "abc"}} s.a.GetUserByID(s.ctx) assert.Equal(s.T(), 400, s.recorder.Code) } func (s *UserSuite) Test_GetUserByID_UnknownUser() { s.db.On("GetUserByID", mock.Anything).Return(nil) s.ctx.Params = gin.Params{{Key: "id", Value: "3"}} s.a.GetUserByID(s.ctx) assert.Equal(s.T(), 404, s.recorder.Code) } func (s *UserSuite) Test_DeleteUserByID_InvalidID() { s.ctx.Params = gin.Params{{Key: "id", Value: "abc"}} s.a.DeleteUserByID(s.ctx) assert.Equal(s.T(), 400, s.recorder.Code) } func (s *UserSuite) Test_DeleteUserByID_UnknownUser() { s.db.On("GetUserByID", mock.Anything).Return(nil) s.ctx.Params = gin.Params{{Key: "id", Value: "3"}} s.a.DeleteUserByID(s.ctx) assert.Equal(s.T(), 404, s.recorder.Code) } func (s *UserSuite) Test_DeleteUserByID() { s.db.On("GetUserByID", uint(2)).Return(normalUser) s.db.On("DeleteUserByID", uint(2)).Return(nil) s.ctx.Params = gin.Params{{Key: "id", Value: "2"}} s.a.DeleteUserByID(s.ctx) s.db.AssertCalled(s.T(), "DeleteUserByID", uint(2)) assert.Equal(s.T(), 200, s.recorder.Code) } func (s *UserSuite) Test_CreateUser() { pwByte := []byte{1, 2, 3} patch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { if pw == "mylittlepony" { return pwByte } return []byte{5, 67} }) defer patch.Unpatch() s.db.On("GetUserByName", "tom").Return(nil) s.db.On("CreateUser", mock.Anything).Return(nil) s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "mylittlepony", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.CreateUser(s.ctx) s.db.AssertCalled(s.T(), "CreateUser", &model.User{Name: "tom", Pass: pwByte, Admin: true}) s.expectJSON(`{"id":0, "name":"tom", "admin":true}`) } func (s *UserSuite) Test_CreateUser_NoPassword() { s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.CreateUser(s.ctx) assert.Equal(s.T(), 400, s.recorder.Code) } func (s *UserSuite) Test_CreateUser_NoName() { s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "", "pass": "asd", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.CreateUser(s.ctx) assert.Equal(s.T(), 400, s.recorder.Code) } func (s *UserSuite) Test_CreateUser_NameAlreadyExists() { pwByte := []byte{1, 2, 3} monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte }) s.db.On("GetUserByName", "tom").Return(&model.User{ID: 3, Name: "tom"}) s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "mylittlepony", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.CreateUser(s.ctx) assert.Equal(s.T(), 400, s.recorder.Code) } func (s *UserSuite) Test_UpdateUserByID_InvalidID() { s.ctx.Params = gin.Params{{Key: "id", Value: "abc"}} s.ctx.Request = httptest.NewRequest("POST", "/user/abc", strings.NewReader(`{"name": "tom", "pass": "", "admin": false}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.UpdateUserByID(s.ctx) assert.Equal(s.T(), 400, s.recorder.Code) } func (s *UserSuite) Test_UpdateUserByID_UnknownUser() { s.db.On("GetUserByID", uint(2)).Return(nil) s.ctx.Params = gin.Params{{Key: "id", Value: "2"}} s.ctx.Request = httptest.NewRequest("POST", "/user/2", strings.NewReader(`{"name": "tom", "pass": "", "admin": false}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.UpdateUserByID(s.ctx) assert.Equal(s.T(), 404, s.recorder.Code) } func (s *UserSuite) Test_UpdateUserByID_UpdateNotPassword() { s.db.On("GetUserByID", uint(2)).Return(&model.User{Name: "nico", Pass: []byte{5}, Admin: false}) expected := &model.User{ID: 2, Name: "tom", Pass: []byte{5}, Admin: true} s.db.On("UpdateUser", expected).Return(nil) s.ctx.Params = gin.Params{{Key: "id", Value: "2"}} s.ctx.Request = httptest.NewRequest("POST", "/user/2", strings.NewReader(`{"name": "tom", "pass": "", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.UpdateUserByID(s.ctx) assert.Equal(s.T(), 200, s.recorder.Code) s.db.AssertCalled(s.T(), "UpdateUser", expected) } func (s *UserSuite) Test_UpdateUserByID_UpdatePassword() { pwByte := []byte{1, 2, 3} patch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte }) defer patch.Unpatch() s.db.On("GetUserByID", uint(2)).Return(normalUser) expected := &model.User{ID: 2, Name: "tom", Pass: pwByte, Admin: true} s.db.On("UpdateUser", expected).Return(nil) s.ctx.Params = gin.Params{{Key: "id", Value: "2"}} s.ctx.Request = httptest.NewRequest("POST", "/user/2", strings.NewReader(`{"name": "tom", "pass": "secret", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.UpdateUserByID(s.ctx) assert.Equal(s.T(), 200, s.recorder.Code) s.db.AssertCalled(s.T(), "UpdateUser", expected) } func (s *UserSuite) Test_UpdatePassword() { pwByte := []byte{1, 2, 3} createPasswordPatch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte }) defer createPasswordPatch.Unpatch() patchUser := monkey.Patch(auth.GetUserID, func(*gin.Context) uint { return 1 }) defer patchUser.Unpatch() s.ctx.Request = httptest.NewRequest("POST", "/user/current/password", strings.NewReader(`{"pass": "secret"}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.db.On("GetUserByID", uint(1)).Return(&model.User{ID: 1, Name: "jmattheis", Pass: []byte{1}}) s.db.On("UpdateUser", mock.Anything).Return(nil) s.a.ChangePassword(s.ctx) s.db.AssertCalled(s.T(), "UpdateUser", &model.User{ID: 1, Name: "jmattheis", Pass: pwByte}) } func (s *UserSuite) Test_UpdatePassword_EmptyPassword() { patchUser := monkey.Patch(auth.GetUserID, func(*gin.Context) uint { return 1 }) defer patchUser.Unpatch() s.ctx.Request = httptest.NewRequest("POST", "/user/current/password", strings.NewReader(`{"pass":""}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.db.On("UpdateUser", mock.Anything).Return(nil) s.db.On("GetUserByID", uint(1)).Return(&model.User{ID: 1, Name: "jmattheis", Pass: []byte{1}}) s.a.ChangePassword(s.ctx) s.db.AssertNotCalled(s.T(), "UpdateUser", mock.Anything) assert.Equal(s.T(), 400, s.recorder.Code) } func (s *UserSuite) expectJSON(json string) { assert.Equal(s.T(), 200, s.recorder.Code) bytes, _ := ioutil.ReadAll(s.recorder.Body) assert.JSONEq(s.T(), json, string(bytes)) }