mirror of
https://github.com/mattermost/focalboard.git
synced 2024-12-24 13:43:12 +02:00
[GH-436] Add integration tests for missing User API endpoints (#810)
* server/client: support register and login * server/client: support user related apis * integrationtests: Add SetupTestHelperWithoutToken * Add api integration tests for (User APIs) * rename GetUserMe method to GetMe * check GetMe data is match the registered data after login * Add integration test for workspace upload file api * make ci happy
This commit is contained in:
parent
d698932c32
commit
15b5d9746f
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
@ -23,6 +24,7 @@ const (
|
||||
HeaderRequestedWith = "X-Requested-With"
|
||||
HeaderRequestedWithXML = "XMLHttpRequest"
|
||||
SingleUser = "single-user"
|
||||
UploadFormFileKey = "file"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -1245,6 +1247,15 @@ type FileUploadResponse struct {
|
||||
FileID string `json:"fileId"`
|
||||
}
|
||||
|
||||
func FileUploadResponseFromJSON(data io.Reader) (*FileUploadResponse, error) {
|
||||
var fileUploadResponse FileUploadResponse
|
||||
|
||||
if err := json.NewDecoder(data).Decode(&fileUploadResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileUploadResponse, nil
|
||||
}
|
||||
|
||||
func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:operation POST /api/v1/workspaces/{workspaceID}/{rootID}/files uploadFile
|
||||
//
|
||||
@ -1293,10 +1304,9 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
file, handle, err := r.FormFile("file")
|
||||
file, handle, err := r.FormFile(UploadFormFileKey)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -62,6 +63,14 @@ type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func LoginResponseFromJSON(data io.Reader) (*LoginResponse, error) {
|
||||
var resp LoginResponse
|
||||
if err := json.NewDecoder(data).Decode(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// RegisterRequest is a user registration request
|
||||
// swagger:model
|
||||
type RegisterRequest struct {
|
||||
|
@ -1,13 +1,16 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/focalboard/server/api"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
)
|
||||
|
||||
@ -68,15 +71,18 @@ type Client struct {
|
||||
APIURL string
|
||||
HTTPClient *http.Client
|
||||
HTTPHeader map[string]string
|
||||
// Token if token is empty indicate client is not login yet
|
||||
Token string
|
||||
}
|
||||
|
||||
func NewClient(url, sessionToken string) *Client {
|
||||
url = strings.TrimRight(url, "/")
|
||||
|
||||
headers := map[string]string{
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Authorization": "Bearer " + sessionToken,
|
||||
}
|
||||
return &Client{url, url + APIURLSuffix, &http.Client{}, headers}
|
||||
|
||||
return &Client{url, url + APIURLSuffix, &http.Client{}, headers, sessionToken}
|
||||
}
|
||||
|
||||
func (c *Client) DoAPIGet(url, etag string) (*http.Response, error) {
|
||||
@ -103,18 +109,28 @@ func (c *Client) DoAPIRequest(method, url, data, etag string) (*http.Response, e
|
||||
return c.doAPIRequestReader(method, url, strings.NewReader(data), etag)
|
||||
}
|
||||
|
||||
func (c *Client) doAPIRequestReader(method, url string, data io.Reader, _ /* etag */ string) (*http.Response, error) {
|
||||
type requestOption func(r *http.Request)
|
||||
|
||||
func (c *Client) doAPIRequestReader(method, url string, data io.Reader, _ /* etag */ string, opts ...requestOption) (*http.Response, error) {
|
||||
rq, err := http.NewRequest(method, url, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(rq)
|
||||
}
|
||||
|
||||
if c.HTTPHeader != nil && len(c.HTTPHeader) > 0 {
|
||||
for k, v := range c.HTTPHeader {
|
||||
rq.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Token != "" {
|
||||
rq.Header.Set("Authorization", "Bearer "+c.Token)
|
||||
}
|
||||
|
||||
rp, err := c.HTTPClient.Do(rq)
|
||||
if err != nil || rp == nil {
|
||||
return nil, err
|
||||
@ -224,3 +240,124 @@ func (c *Client) PostSharing(sharing model.Sharing) (bool, *Response) {
|
||||
|
||||
return true, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) GetRegisterRoute() string {
|
||||
return "/register"
|
||||
}
|
||||
|
||||
func (c *Client) Register(request *api.RegisterRequest) (bool, *Response) {
|
||||
r, err := c.DoAPIPost(c.GetRegisterRoute(), toJSON(&request))
|
||||
if err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return true, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) GetLoginRoute() string {
|
||||
return "/login"
|
||||
}
|
||||
|
||||
func (c *Client) Login(request *api.LoginRequest) (*api.LoginResponse, *Response) {
|
||||
r, err := c.DoAPIPost(c.GetLoginRoute(), toJSON(&request))
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
data, err := api.LoginResponseFromJSON(r.Body)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
if data.Token != "" {
|
||||
c.Token = data.Token
|
||||
}
|
||||
|
||||
return data, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) GetMeRoute() string {
|
||||
return "/users/me"
|
||||
}
|
||||
|
||||
func (c *Client) GetMe() (*model.User, *Response) {
|
||||
r, err := c.DoAPIGet(c.GetMeRoute(), "")
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
me, err := model.UserFromJSON(r.Body)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
return me, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) GetUserRoute(id string) string {
|
||||
return fmt.Sprintf("/users/%s", id)
|
||||
}
|
||||
|
||||
func (c *Client) GetUser(id string) (*model.User, *Response) {
|
||||
r, err := c.DoAPIGet(c.GetUserRoute(id), "")
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
user, err := model.UserFromJSON(r.Body)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
return user, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) GetUserChangePasswordRoute(id string) string {
|
||||
return fmt.Sprintf("/users/%s/changepassword", id)
|
||||
}
|
||||
|
||||
func (c *Client) UserChangePassword(id string, data *api.ChangePasswordRequest) (bool, *Response) {
|
||||
r, err := c.DoAPIPost(c.GetUserChangePasswordRoute(id), toJSON(&data))
|
||||
if err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return true, BuildResponse(r)
|
||||
}
|
||||
|
||||
func (c *Client) GetWorkspaceUploadFileRoute(workspaceID, rootID string) string {
|
||||
return fmt.Sprintf("/workspaces/%s/%s/files", workspaceID, rootID)
|
||||
}
|
||||
|
||||
func (c *Client) WorkspaceUploadFile(workspaceID, rootID string, data io.Reader) (*api.FileUploadResponse, *Response) {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
part, err := writer.CreateFormFile(api.UploadFormFileKey, "file")
|
||||
if err != nil {
|
||||
return nil, &Response{Error: err}
|
||||
}
|
||||
if _, err = io.Copy(part, data); err != nil {
|
||||
return nil, &Response{Error: err}
|
||||
}
|
||||
writer.Close()
|
||||
|
||||
opt := func(r *http.Request) {
|
||||
r.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
}
|
||||
|
||||
r, err := c.doAPIRequestReader(http.MethodPost, c.APIURL+c.GetWorkspaceUploadFileRoute(workspaceID, rootID), body, "", opt)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
fileUploadResponse, err := api.FileUploadResponseFromJSON(r.Body)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
return fileUploadResponse, BuildResponse(r)
|
||||
}
|
||||
|
@ -50,21 +50,21 @@ func getTestConfig() *config.Configuration {
|
||||
}`
|
||||
|
||||
return &config.Configuration{
|
||||
ServerRoot: "http://localhost:8888",
|
||||
Port: 8888,
|
||||
DBType: dbType,
|
||||
DBConfigString: connectionString,
|
||||
DBTablePrefix: "test_",
|
||||
WebPath: "./pack",
|
||||
FilesDriver: "local",
|
||||
FilesPath: "./files",
|
||||
LoggingCfgJSON: logging,
|
||||
ServerRoot: "http://localhost:8888",
|
||||
Port: 8888,
|
||||
DBType: dbType,
|
||||
DBConfigString: connectionString,
|
||||
DBTablePrefix: "test_",
|
||||
WebPath: "./pack",
|
||||
FilesDriver: "local",
|
||||
FilesPath: "./files",
|
||||
LoggingCfgJSON: logging,
|
||||
SessionExpireTime: int64(30 * time.Second),
|
||||
AuthMode: "native",
|
||||
}
|
||||
}
|
||||
|
||||
func SetupTestHelper() *TestHelper {
|
||||
sessionToken := "TESTTOKEN"
|
||||
th := &TestHelper{}
|
||||
func newTestServer(singleUserToken string) *server.Server {
|
||||
logger := mlog.NewLogger()
|
||||
if err := logger.Configure("", getTestConfig().LoggingCfgJSON); err != nil {
|
||||
panic(err)
|
||||
@ -74,13 +74,26 @@ func SetupTestHelper() *TestHelper {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
srv, err := server.New(cfg, sessionToken, db, logger, "")
|
||||
srv, err := server.New(cfg, singleUserToken, db, logger, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
th.Server = srv
|
||||
th.Client = client.NewClient(srv.Config().ServerRoot, sessionToken)
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
func SetupTestHelper() *TestHelper {
|
||||
sessionToken := "TESTTOKEN"
|
||||
th := &TestHelper{}
|
||||
th.Server = newTestServer(sessionToken)
|
||||
th.Client = client.NewClient(th.Server.Config().ServerRoot, sessionToken)
|
||||
return th
|
||||
}
|
||||
|
||||
func SetupTestHelperWithoutToken() *TestHelper {
|
||||
th := &TestHelper{}
|
||||
th.Server = newTestServer("")
|
||||
th.Client = client.NewClient(th.Server.Config().ServerRoot, "")
|
||||
return th
|
||||
}
|
||||
|
||||
@ -124,4 +137,6 @@ func (th *TestHelper) TearDown() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
os.RemoveAll(th.Server.Config().FilesPath)
|
||||
}
|
||||
|
239
server/integrationtests/user_test.go
Normal file
239
server/integrationtests/user_test.go
Normal file
@ -0,0 +1,239 @@
|
||||
package integrationtests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/focalboard/server/api"
|
||||
"github.com/mattermost/focalboard/server/utils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
fakeUsername = "fakeUsername"
|
||||
fakeEmail = "mock@test.com"
|
||||
)
|
||||
|
||||
func TestUserRegister(t *testing.T) {
|
||||
th := SetupTestHelperWithoutToken().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// register
|
||||
registerRequest := &api.RegisterRequest{
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: utils.CreateGUID(),
|
||||
}
|
||||
success, resp := th.Client.Register(registerRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.True(t, success)
|
||||
|
||||
// register again will failed
|
||||
success, resp = th.Client.Register(registerRequest)
|
||||
require.Error(t, resp.Error)
|
||||
require.False(t, success)
|
||||
}
|
||||
|
||||
func TestUserLogin(t *testing.T) {
|
||||
th := SetupTestHelperWithoutToken().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("with nonexist user", func(t *testing.T) {
|
||||
loginRequest := &api.LoginRequest{
|
||||
Type: "normal",
|
||||
Username: "nonexistuser",
|
||||
Email: "",
|
||||
Password: utils.CreateGUID(),
|
||||
}
|
||||
data, resp := th.Client.Login(loginRequest)
|
||||
require.Error(t, resp.Error)
|
||||
require.Nil(t, data)
|
||||
})
|
||||
|
||||
t.Run("with registered user", func(t *testing.T) {
|
||||
password := utils.CreateGUID()
|
||||
// register
|
||||
registerRequest := &api.RegisterRequest{
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
success, resp := th.Client.Register(registerRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.True(t, success)
|
||||
|
||||
// login
|
||||
loginRequest := &api.LoginRequest{
|
||||
Type: "normal",
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
data, resp := th.Client.Login(loginRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, data)
|
||||
require.NotNil(t, data.Token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMe(t *testing.T) {
|
||||
th := SetupTestHelperWithoutToken().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("not login yet", func(t *testing.T) {
|
||||
me, resp := th.Client.GetMe()
|
||||
require.Error(t, resp.Error)
|
||||
require.Nil(t, me)
|
||||
})
|
||||
|
||||
t.Run("logged in", func(t *testing.T) {
|
||||
// register
|
||||
password := utils.CreateGUID()
|
||||
registerRequest := &api.RegisterRequest{
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
success, resp := th.Client.Register(registerRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.True(t, success)
|
||||
// login
|
||||
loginRequest := &api.LoginRequest{
|
||||
Type: "normal",
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
data, resp := th.Client.Login(loginRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, data)
|
||||
require.NotNil(t, data.Token)
|
||||
|
||||
// get user me
|
||||
me, resp := th.Client.GetMe()
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, me)
|
||||
require.Equal(t, registerRequest.Email, me.Email)
|
||||
require.Equal(t, registerRequest.Username, me.Username)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
th := SetupTestHelperWithoutToken().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// register
|
||||
password := utils.CreateGUID()
|
||||
registerRequest := &api.RegisterRequest{
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
success, resp := th.Client.Register(registerRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.True(t, success)
|
||||
// login
|
||||
loginRequest := &api.LoginRequest{
|
||||
Type: "normal",
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
data, resp := th.Client.Login(loginRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, data)
|
||||
require.NotNil(t, data.Token)
|
||||
|
||||
me, resp := th.Client.GetMe()
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, me)
|
||||
|
||||
t.Run("me's id", func(t *testing.T) {
|
||||
user, resp := th.Client.GetUser(me.ID)
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, user)
|
||||
require.Equal(t, me.ID, user.ID)
|
||||
require.Equal(t, me.Username, user.Username)
|
||||
})
|
||||
|
||||
t.Run("nonexist user", func(t *testing.T) {
|
||||
user, resp := th.Client.GetUser("nonexistid")
|
||||
require.Error(t, resp.Error)
|
||||
require.Nil(t, user)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserChangePassword(t *testing.T) {
|
||||
th := SetupTestHelperWithoutToken().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// register
|
||||
password := utils.CreateGUID()
|
||||
registerRequest := &api.RegisterRequest{
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
success, resp := th.Client.Register(registerRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.True(t, success)
|
||||
// login
|
||||
loginRequest := &api.LoginRequest{
|
||||
Type: "normal",
|
||||
Username: fakeUsername,
|
||||
Email: fakeEmail,
|
||||
Password: password,
|
||||
}
|
||||
data, resp := th.Client.Login(loginRequest)
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, data)
|
||||
require.NotNil(t, data.Token)
|
||||
|
||||
originalMe, resp := th.Client.GetMe()
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, originalMe)
|
||||
|
||||
// change password
|
||||
success, resp = th.Client.UserChangePassword(originalMe.ID, &api.ChangePasswordRequest{
|
||||
OldPassword: password,
|
||||
NewPassword: utils.CreateGUID(),
|
||||
})
|
||||
require.NoError(t, resp.Error)
|
||||
require.True(t, success)
|
||||
}
|
||||
|
||||
func randomBytes(t *testing.T, n int) []byte {
|
||||
bb := make([]byte, n)
|
||||
_, err := rand.Read(bb)
|
||||
require.NoError(t, err)
|
||||
return bb
|
||||
}
|
||||
|
||||
func TestWorkspaceUploadFile(t *testing.T) {
|
||||
t.Run("no permission", func(t *testing.T) { // native auth, but not login
|
||||
th := SetupTestHelperWithoutToken().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
workspaceID := "0"
|
||||
rootID := utils.CreateGUID()
|
||||
data := randomBytes(t, 1024)
|
||||
result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data))
|
||||
require.Error(t, resp.Error)
|
||||
require.Nil(t, result)
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) { // single token auth
|
||||
th := SetupTestHelper().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
workspaceID := "0"
|
||||
rootID := utils.CreateGUID()
|
||||
data := randomBytes(t, 1024)
|
||||
result, resp := th.Client.WorkspaceUploadFile(workspaceID, rootID, bytes.NewReader(data))
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, result)
|
||||
require.NotEmpty(t, result.FileID)
|
||||
// TODO get the uploaded file
|
||||
})
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// User is a user
|
||||
// swagger:model
|
||||
type User struct {
|
||||
@ -53,3 +58,11 @@ type Session struct {
|
||||
CreateAt int64 `json:"create_at,omitempty"`
|
||||
UpdateAt int64 `json:"update_at,omitempty"`
|
||||
}
|
||||
|
||||
func UserFromJSON(data io.Reader) (*User, error) {
|
||||
var user User
|
||||
if err := json.NewDecoder(data).Decode(&user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user