diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..8b277888c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: Check-in tests + +on: + push: + branches: + - 'main' + - 'releases-**' + pull_request: + workflow_dispatch: + +env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + EXCLUDE_ENTERPRISE: true + +jobs: + webapp-test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: "focalboard" + - name: npm ci + run: | + cd focalboard/webapp && npm ci + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.21 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20.11.0 + + - name: Lint & test webapp + run: cd focalboard; make webapp-ci + + - name: set up golangci-lint + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.59.0 + + - name: Lint & test server + run: cd focalboard; make server-ci \ No newline at end of file diff --git a/Makefile b/Makefile index a585bb4cf..71dab8583 100644 --- a/Makefile +++ b/Makefile @@ -154,6 +154,14 @@ endif @echo plugin built at: dist/$(BUNDLE_NAME) +info: ## Display build information + @echo "Build Number: $(BUILD_NUMBER)" + @echo "Build Date: $(BUILD_DATE)" + @echo "Build Hash: $(BUILD_HASH)" + @echo "Plugin ID: $(PLUGIN_ID)" + @echo "Plugin Version: $(PLUGIN_VERSION)" + @echo "Bundle Name: $(BUNDLE_NAME)" + ## Builds and bundles the plugin. .PHONY: dist dist: apply server webapp bundle @@ -357,6 +365,8 @@ generate: ## Install and run code generators. cd server; go install github.com/golang/mock/mockgen@v1.6.0 cd server; go generate ./... +server-ci: server-lint + server-lint: ## Run linters on server code. @if ! [ -x "$$(command -v golangci-lint)" ]; then \ echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install-golangci-lint for installation instructions."; \ @@ -373,7 +383,7 @@ modd-precheck: webapp-ci: ## Webapp CI: linting & testing. cd webapp; npm run check cd webapp; npm run test - cd webapp: npm run check-types + cd webapp; npm run check-types webapp-test: ## jest tests for webapp cd webapp; npm run test @@ -382,7 +392,33 @@ watch-plugin: modd-precheck ## Run and upload the plugin to a development server env FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd -f modd-watchplugin.conf live-watch-plugin: modd-precheck ## Run and update locally the plugin in the development server - cd mattermost-plugin; make live-watch + make live-watch + +server-test: server-test-mysql server-test-postgres + +server-test-mysql: export FOCALBOARD_UNIT_TESTING=1 +server-test-mysql: export FOCALBOARD_STORE_TEST_DB_TYPE=mysql +server-test-mysql: export FOCALBOARD_STORE_TEST_DOCKER_PORT=44446 + +server-test-mysql: ## Run server tests using mysql + @echo Starting docker container for mysql + docker-compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans + docker-compose -f ./docker-testing/docker-compose-mysql.yml run start_dependencies + cd server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=server-mysql-profile.coverage -count=1 -timeout=30m ./... + cd server; go tool cover -func server-mysql-profile.coverage + docker-compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans + +server-test-postgres: export FOCALBOARD_UNIT_TESTING=1 +server-test-postgres: export FOCALBOARD_STORE_TEST_DB_TYPE=postgres +server-test-postgres: export FOCALBOARD_STORE_TEST_DOCKER_PORT=44447 + +server-test-postgres: ## Run server tests using postgres + @echo Starting docker container for postgres + docker-compose -f ./docker-testing/docker-compose-postgres.yml down -v --remove-orphans + docker-compose -f ./docker-testing/docker-compose-postgres.yml run start_dependencies + cd server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=server-postgres-profile.coverage -count=1 -timeout=30m ./... + cd server; go tool cover -func server-postgres-profile.coverage + docker-compose -f ./docker-testing/docker-compose-postgres.yml down -v --remove-orphans swagger: ## Generate swagger API spec and clients based on it. mkdir -p server/swagger/docs diff --git a/docker-testing/docker-compose-mysql.yml b/docker-testing/docker-compose-mysql.yml new file mode 100644 index 000000000..111aacea2 --- /dev/null +++ b/docker-testing/docker-compose-mysql.yml @@ -0,0 +1,25 @@ +version: '2.4' +services: + mysql: + image: "mysql/mysql-server:8.0.32" + restart: always + environment: + MYSQL_ROOT_HOST: "%" + MYSQL_ROOT_PASSWORD: mostest + MYSQL_PASSWORD: mostest + MYSQL_USER: mmuser + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + interval: 5s + timeout: 10s + retries: 3 + tmpfs: /var/lib/mysql + ports: + - 44446:3306 + + start_dependencies: + image: mattermost/mattermost-wait-for-dep:latest + depends_on: + - mysql + command: mysql + diff --git a/docker-testing/docker-compose-postgres.yml b/docker-testing/docker-compose-postgres.yml new file mode 100644 index 000000000..529f3bb6b --- /dev/null +++ b/docker-testing/docker-compose-postgres.yml @@ -0,0 +1,23 @@ +version: '2.4' +services: + postgres: + image: "postgres:10" + restart: always + environment: + POSTGRES_USER: mmuser + POSTGRES_PASSWORD: mostest + healthcheck: + test: [ "CMD", "pg_isready", "-h", "localhost" ] + interval: 5s + timeout: 10s + retries: 3 + tmpfs: /var/lib/postgresql/data + ports: + - 44447:5432 + + start_dependencies: + image: mattermost/mattermost-wait-for-dep:latest + depends_on: + - postgres + command: postgres:5432 + diff --git a/server/admin-scripts/reset-password.sh b/server/admin-scripts/reset-password.sh deleted file mode 100755 index 6c614ed1b..000000000 --- a/server/admin-scripts/reset-password.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -if [[ $# < 2 ]] ; then - echo 'reset-password.sh ' - exit 1 -fi - -curl --unix-socket /var/tmp/focalboard_local.socket http://localhost/api/v2/admin/users/$1/password -X POST -H 'Content-Type: application/json' -d '{ "password": "'$2'" }' diff --git a/server/api/admin.go b/server/api/admin.go deleted file mode 100644 index 12832460b..000000000 --- a/server/api/admin.go +++ /dev/null @@ -1,56 +0,0 @@ -package api - -import ( - "encoding/json" - "io" - "net/http" - "strings" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/audit" - - "github.com/mattermost/mattermost/server/public/shared/mlog" -) - -type AdminSetPasswordData struct { - Password string `json:"password"` -} - -func (a *API) handleAdminSetPassword(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - username := vars["username"] - - requestBody, err := io.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r, err) - return - } - - var requestData AdminSetPasswordData - err = json.Unmarshal(requestBody, &requestData) - if err != nil { - a.errorResponse(w, r, err) - return - } - - auditRec := a.makeAuditRecord(r, "adminSetPassword", audit.Fail) - defer a.audit.LogRecord(audit.LevelAuth, auditRec) - auditRec.AddMeta("username", username) - - if !strings.Contains(requestData.Password, "") { - a.errorResponse(w, r, model.NewErrBadRequest("password is required")) - return - } - - err = a.app.UpdateUserPassword(username, requestData.Password) - if err != nil { - a.errorResponse(w, r, err) - return - } - - a.logger.Debug("AdminSetPassword, username: %s", mlog.String("username", username)) - - jsonStringResponse(w, http.StatusOK, "{}") - auditRec.Success() -} diff --git a/server/api/api.go b/server/api/api.go index 91bfb4133..1ef3d4d81 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -34,14 +34,11 @@ var ( // REST APIs type API struct { - app *app.App - authService string - permissions permissions.PermissionsService - singleUserToken string - MattermostAuth bool - logger mlog.LoggerIFace - audit *audit.Audit - isPlugin bool + app *app.App + authService string + permissions permissions.PermissionsService + logger mlog.LoggerIFace + audit *audit.Audit } func NewAPI( @@ -51,16 +48,13 @@ func NewAPI( permissions permissions.PermissionsService, logger mlog.LoggerIFace, audit *audit.Audit, - isPlugin bool, ) *API { return &API{ - app: app, - singleUserToken: singleUserToken, - authService: authService, - permissions: permissions, - logger: logger, - audit: audit, - isPlugin: isPlugin, + app: app, + authService: authService, + permissions: permissions, + logger: logger, + audit: audit, } } @@ -77,7 +71,6 @@ func (a *API) RegisterRoutes(r *mux.Router) { // V2 routes (ToDo: migrate these to V3 when ready to ship V3) a.registerUsersRoutes(apiv2) - a.registerAuthRoutes(apiv2) a.registerMembersRoutes(apiv2) a.registerCategoriesRoutes(apiv2) a.registerSharingRoutes(apiv2) @@ -104,10 +97,6 @@ func (a *API) RegisterRoutes(r *mux.Router) { a.registerSystemRoutes(r) } -func (a *API) RegisterAdminRoutes(r *mux.Router) { - r.HandleFunc("/api/v2/admin/users/{username}/password", a.adminRequired(a.handleAdminSetPassword)).Methods("POST") -} - func getUserID(r *http.Request) string { ctx := r.Context() session, ok := ctx.Value(sessionContextKey).(*model.Session) @@ -169,9 +158,6 @@ func (a *API) hasValidReadTokenForBoard(r *http.Request, boardID string) bool { } func (a *API) userIsGuest(userID string) (bool, error) { - if a.singleUserToken != "" { - return false, nil - } return a.app.UserIsGuest(userID) } diff --git a/server/api/api_test.go b/server/api/api_test.go index 6426ebc23..7652dfbef 100644 --- a/server/api/api_test.go +++ b/server/api/api_test.go @@ -27,7 +27,6 @@ func TestErrorResponse(t *testing.T) { // bad request {"ErrBadRequest", model.NewErrBadRequest("bad field"), http.StatusBadRequest, "bad field"}, {"ErrViewsLimitReached", model.ErrViewsLimitReached, http.StatusBadRequest, "limit reached"}, - {"ErrAuthParam", model.NewErrAuthParam("password is required"), http.StatusBadRequest, "password is required"}, {"ErrInvalidCategory", model.NewErrInvalidCategory("open"), http.StatusBadRequest, "open"}, {"ErrBoardMemberIsLastAdmin", model.ErrBoardMemberIsLastAdmin, http.StatusBadRequest, "no admins"}, {"ErrBoardIDMismatch", model.ErrBoardIDMismatch, http.StatusBadRequest, "Board IDs do not match"}, diff --git a/server/api/archive.go b/server/api/archive.go index a8bb55435..acbd6e750 100644 --- a/server/api/archive.go +++ b/server/api/archive.go @@ -204,51 +204,5 @@ func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode")) - return - } - - vars := mux.Vars(r) - teamID := vars["teamID"] - - ctx := r.Context() - session, _ := ctx.Value(sessionContextKey).(*model.Session) - userID := session.UserID - - auditRec := a.makeAuditRecord(r, "archiveExportTeam", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("TeamID", teamID) - - isGuest, err := a.userIsGuest(userID) - if err != nil { - a.errorResponse(w, r, err) - return - } - - boards, err := a.app.GetBoardsForUserAndTeam(userID, teamID, !isGuest) - if err != nil { - a.errorResponse(w, r, err) - return - } - ids := []string{} - for _, board := range boards { - ids = append(ids, board.ID) - } - - opts := model.ExportArchiveOptions{ - TeamID: teamID, - BoardIDs: ids, - } - - filename := fmt.Sprintf("archive-%s%s", time.Now().Format("2006-01-02"), archiveExtension) - w.Header().Set("Content-Type", "application/octet-stream") - w.Header().Set("Content-Disposition", "attachment; filename="+filename) - w.Header().Set("Content-Transfer-Encoding", "binary") - - if err := a.app.ExportArchive(w, opts); err != nil { - a.errorResponse(w, r, err) - } - - auditRec.Success() + a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode")) } diff --git a/server/api/auth.go b/server/api/auth.go index 203de88b8..44f4a2dd9 100644 --- a/server/api/auth.go +++ b/server/api/auth.go @@ -2,357 +2,19 @@ package api import ( "context" - "encoding/json" - "io" - "net" "net/http" - "strings" - "github.com/gorilla/mux" "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/audit" - "github.com/mattermost/mattermost-plugin-boards/server/services/auth" "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "github.com/mattermost/mattermost/server/public/shared/mlog" ) -func (a *API) registerAuthRoutes(r *mux.Router) { - // personal-server specific routes. These are not needed in plugin mode. - if !a.isPlugin { - r.HandleFunc("/login", a.handleLogin).Methods("POST") - r.HandleFunc("/logout", a.sessionRequired(a.handleLogout)).Methods("POST") - r.HandleFunc("/register", a.handleRegister).Methods("POST") - r.HandleFunc("/teams/{teamID}/regenerate_signup_token", a.sessionRequired(a.handlePostTeamRegenerateSignupToken)).Methods("POST") - r.HandleFunc("/users/{userID}/changepassword", a.sessionRequired(a.handleChangePassword)).Methods("POST") - } -} - -func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /login login - // - // Login user - // - // --- - // produces: - // - application/json - // parameters: - // - name: body - // in: body - // description: Login request - // required: true - // schema: - // "$ref": "#/definitions/LoginRequest" - // responses: - // '200': - // description: success - // schema: - // "$ref": "#/definitions/LoginResponse" - // '401': - // description: invalid login - // schema: - // "$ref": "#/definitions/ErrorResponse" - // '500': - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode")) - return - } - - if len(a.singleUserToken) > 0 { - // Not permitted in single-user mode - a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode")) - return - } - - requestBody, err := io.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r, err) - return - } - - var loginData model.LoginRequest - err = json.Unmarshal(requestBody, &loginData) - if err != nil { - a.errorResponse(w, r, err) - return - } - - auditRec := a.makeAuditRecord(r, "login", audit.Fail) - defer a.audit.LogRecord(audit.LevelAuth, auditRec) - auditRec.AddMeta("username", loginData.Username) - auditRec.AddMeta("type", loginData.Type) - - if loginData.Type == "normal" { - token, err := a.app.Login(loginData.Username, loginData.Email, loginData.Password, loginData.MfaToken) - if err != nil { - a.errorResponse(w, r, model.NewErrUnauthorized("incorrect login")) - return - } - json, err := json.Marshal(model.LoginResponse{Token: token}) - if err != nil { - a.errorResponse(w, r, err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - auditRec.Success() - return - } - - a.errorResponse(w, r, model.NewErrBadRequest("invalid login type")) -} - -func (a *API) handleLogout(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /logout logout - // - // Logout user - // - // --- - // produces: - // - application/json - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '500': - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode")) - return - } - - if len(a.singleUserToken) > 0 { - // Not permitted in single-user mode - a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode")) - return - } - - ctx := r.Context() - - session := ctx.Value(sessionContextKey).(*model.Session) - - auditRec := a.makeAuditRecord(r, "logout", audit.Fail) - defer a.audit.LogRecord(audit.LevelAuth, auditRec) - auditRec.AddMeta("userID", session.UserID) - - if err := a.app.Logout(session.ID); err != nil { - a.errorResponse(w, r, model.NewErrUnauthorized("incorrect logout")) - return - } - - auditRec.AddMeta("sessionID", session.ID) - - jsonStringResponse(w, http.StatusOK, "{}") - auditRec.Success() -} - -func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /register register - // - // Register new user - // - // --- - // produces: - // - application/json - // parameters: - // - name: body - // in: body - // description: Register request - // required: true - // schema: - // "$ref": "#/definitions/RegisterRequest" - // responses: - // '200': - // description: success - // '401': - // description: invalid registration token - // '500': - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode")) - return - } - - if len(a.singleUserToken) > 0 { - // Not permitted in single-user mode - a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode")) - return - } - - requestBody, err := io.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r, err) - return - } - - var registerData model.RegisterRequest - err = json.Unmarshal(requestBody, ®isterData) - if err != nil { - a.errorResponse(w, r, err) - return - } - registerData.Email = strings.TrimSpace(registerData.Email) - registerData.Username = strings.TrimSpace(registerData.Username) - - // Validate token - if len(registerData.Token) > 0 { - team, err2 := a.app.GetRootTeam() - if err2 != nil { - a.errorResponse(w, r, err2) - return - } - - if registerData.Token != team.SignupToken { - a.errorResponse(w, r, model.NewErrUnauthorized("invalid token")) - return - } - } else { - // No signup token, check if no active users - userCount, err2 := a.app.GetRegisteredUserCount() - if err2 != nil { - a.errorResponse(w, r, err2) - return - } - if userCount > 0 { - a.errorResponse(w, r, model.NewErrUnauthorized("no sign-up token and user(s) already exist")) - return - } - } - - if err = registerData.IsValid(); err != nil { - a.errorResponse(w, r, err) - return - } - - auditRec := a.makeAuditRecord(r, "register", audit.Fail) - defer a.audit.LogRecord(audit.LevelAuth, auditRec) - auditRec.AddMeta("username", registerData.Username) - - err = a.app.RegisterUser(registerData.Username, registerData.Email, registerData.Password) - if err != nil { - a.errorResponse(w, r, model.NewErrBadRequest(err.Error())) - return - } - - jsonStringResponse(w, http.StatusOK, "{}") - auditRec.Success() -} - -func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /users/{userID}/changepassword changePassword - // - // Change a user's password - // - // --- - // produces: - // - application/json - // parameters: - // - name: userID - // in: path - // description: User ID - // required: true - // type: string - // - name: body - // in: body - // description: Change password request - // required: true - // schema: - // "$ref": "#/definitions/ChangePasswordRequest" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // '400': - // description: invalid request - // schema: - // "$ref": "#/definitions/ErrorResponse" - // '500': - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode")) - return - } - - if len(a.singleUserToken) > 0 { - // Not permitted in single-user mode - a.errorResponse(w, r, model.NewErrUnauthorized("not permitted in single-user mode")) - return - } - - vars := mux.Vars(r) - userID := vars["userID"] - - requestBody, err := io.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r, err) - return - } - - var requestData model.ChangePasswordRequest - if err = json.Unmarshal(requestBody, &requestData); err != nil { - a.errorResponse(w, r, err) - return - } - - if err = requestData.IsValid(); err != nil { - a.errorResponse(w, r, err) - return - } - - auditRec := a.makeAuditRecord(r, "changePassword", audit.Fail) - defer a.audit.LogRecord(audit.LevelAuth, auditRec) - - if err = a.app.ChangePassword(userID, requestData.OldPassword, requestData.NewPassword); err != nil { - a.errorResponse(w, r, model.NewErrBadRequest(err.Error())) - return - } - - jsonStringResponse(w, http.StatusOK, "{}") - auditRec.Success() -} - func (a *API) sessionRequired(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { - return a.attachSession(handler, true) + return a.attachSession(handler) } -func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request), required bool) func(w http.ResponseWriter, r *http.Request) { +func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - token, _ := auth.ParseAuthTokenFromRequest(r) - - a.logger.Debug(`attachSession`, mlog.Bool("single_user", len(a.singleUserToken) > 0)) - if len(a.singleUserToken) > 0 { - if required && (token != a.singleUserToken) { - a.errorResponse(w, r, model.NewErrUnauthorized("invalid single user token")) - return - } - - now := utils.GetMillis() - session := &model.Session{ - ID: model.SingleUser, - Token: token, - UserID: model.SingleUser, - AuthService: a.authService, - Props: map[string]interface{}{}, - CreateAt: now, - UpdateAt: now, - } - ctx := context.WithValue(r.Context(), sessionContextKey, session) - handler(w, r.WithContext(ctx)) - return - } - - if a.MattermostAuth && r.Header.Get("Mattermost-User-Id") != "" { + if r.Header.Get("Mattermost-User-Id") != "" { userID := r.Header.Get("Mattermost-User-Id") now := utils.GetMillis() session := &model.Session{ @@ -370,43 +32,6 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request) return } - session, err := a.app.GetSession(token) - if err != nil { - if required { - a.errorResponse(w, r, model.NewErrUnauthorized(err.Error())) - return - } - - handler(w, r) - return - } - - authService := session.AuthService - if authService != a.authService { - msg := `Session authService mismatch` - a.logger.Error(msg, - mlog.String("sessionID", session.ID), - mlog.String("want", a.authService), - mlog.String("got", authService), - ) - a.errorResponse(w, r, model.NewErrUnauthorized(msg)) - return - } - - ctx := context.WithValue(r.Context(), sessionContextKey, session) - handler(w, r.WithContext(ctx)) - } -} - -func (a *API) adminRequired(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - // Currently, admin APIs require local unix connections - conn := GetContextConn(r) - if _, isUnix := conn.(*net.UnixConn); !isUnix { - a.errorResponse(w, r, model.NewErrUnauthorized("not a local unix connection")) - return - } - - handler(w, r) + a.errorResponse(w, r, model.NewErrUnauthorized("Unauthorized")) } } diff --git a/server/api/blocks.go b/server/api/blocks.go index 36732244f..772382d59 100644 --- a/server/api/blocks.go +++ b/server/api/blocks.go @@ -16,7 +16,7 @@ import ( func (a *API) registerBlocksRoutes(r *mux.Router) { // Blocks APIs - r.HandleFunc("/boards/{boardID}/blocks", a.attachSession(a.handleGetBlocks, false)).Methods("GET") + r.HandleFunc("/boards/{boardID}/blocks", a.attachSession(a.handleGetBlocks)).Methods("GET") r.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") r.HandleFunc("/boards/{boardID}/blocks", a.sessionRequired(a.handlePatchBlocks)).Methods("PATCH") r.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") diff --git a/server/api/boards.go b/server/api/boards.go index 1314c289a..a4912dbef 100644 --- a/server/api/boards.go +++ b/server/api/boards.go @@ -15,7 +15,7 @@ import ( func (a *API) registerBoardsRoutes(r *mux.Router) { r.HandleFunc("/teams/{teamID}/boards", a.sessionRequired(a.handleGetBoards)).Methods("GET") r.HandleFunc("/boards", a.sessionRequired(a.handleCreateBoard)).Methods("POST") - r.HandleFunc("/boards/{boardID}", a.attachSession(a.handleGetBoard, false)).Methods("GET") + r.HandleFunc("/boards/{boardID}", a.attachSession(a.handleGetBoard)).Methods("GET") r.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handlePatchBoard)).Methods("PATCH") r.HandleFunc("/boards/{boardID}", a.sessionRequired(a.handleDeleteBoard)).Methods("DELETE") r.HandleFunc("/boards/{boardID}/duplicate", a.sessionRequired(a.handleDuplicateBoard)).Methods("POST") @@ -255,7 +255,6 @@ func (a *API) handleGetBoard(w http.ResponseWriter, r *http.Request) { return } } - if !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { a.errorResponse(w, r, model.NewErrPermission("access denied to board")) return diff --git a/server/api/channels.go b/server/api/channels.go index 234e5b769..d9acc3c77 100644 --- a/server/api/channels.go +++ b/server/api/channels.go @@ -50,11 +50,6 @@ func (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) { // schema: // "$ref": "#/definitions/ErrorResponse" - if !a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode")) - return - } - teamID := mux.Vars(r)["teamID"] channelID := mux.Vars(r)["channelID"] userID := getUserID(r) diff --git a/server/api/context.go b/server/api/context.go index 5e4d51ef8..ff200fe95 100644 --- a/server/api/context.go +++ b/server/api/context.go @@ -3,7 +3,6 @@ package api import ( "context" "net" - "net/http" ) type contextKey int @@ -17,13 +16,3 @@ const ( func SetContextConn(ctx context.Context, c net.Conn) context.Context { return context.WithValue(ctx, httpConnContextKey, c) } - -// GetContextConn gets the stored connection from the request context. -func GetContextConn(r *http.Request) net.Conn { - value := r.Context().Value(httpConnContextKey) - if value == nil { - return nil - } - - return value.(net.Conn) -} diff --git a/server/api/files.go b/server/api/files.go index f270916d2..58c0161dd 100644 --- a/server/api/files.go +++ b/server/api/files.go @@ -75,8 +75,8 @@ func FileInfoResponseFromJSON(data io.Reader) (*mmModel.FileInfo, error) { func (a *API) registerFilesRoutes(r *mux.Router) { // Files API - r.HandleFunc("/files/teams/{teamID}/{boardID}/{filename}", a.attachSession(a.handleServeFile, false)).Methods("GET") - r.HandleFunc("/files/teams/{teamID}/{boardID}/{filename}/info", a.attachSession(a.getFileInfo, false)).Methods("GET") + r.HandleFunc("/files/teams/{teamID}/{boardID}/{filename}", a.attachSession(a.handleServeFile)).Methods("GET") + r.HandleFunc("/files/teams/{teamID}/{boardID}/{filename}/info", a.attachSession(a.getFileInfo)).Methods("GET") r.HandleFunc("/teams/{teamID}/{boardID}/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") } diff --git a/server/api/search.go b/server/api/search.go index d6ba4560a..5a964fa1b 100644 --- a/server/api/search.go +++ b/server/api/search.go @@ -51,11 +51,6 @@ func (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) { // schema: // "$ref": "#/definitions/ErrorResponse" - if !a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode")) - return - } - query := r.URL.Query() searchQuery := query.Get("search") @@ -226,11 +221,6 @@ func (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request) // schema: // "$ref": "#/definitions/ErrorResponse" - if !a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode")) - return - } - teamID := mux.Vars(r)["teamID"] term := r.URL.Query().Get("q") userID := getUserID(r) diff --git a/server/api/sharing.go b/server/api/sharing.go index 8d79633e5..8b83cc9b3 100644 --- a/server/api/sharing.go +++ b/server/api/sharing.go @@ -147,15 +147,7 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) { // Stamp ModifiedBy modifiedBy := userID - if userID == model.SingleUser { - modifiedBy = "" - } sharing.ModifiedBy = modifiedBy - - if userID == model.SingleUser { - userID = "" - } - if !a.app.GetClientConfig().EnablePublicSharedBoards { a.logger.Warn( "Attempt to turn on sharing for board via API failed, sharing off in configuration.", diff --git a/server/api/statistics.go b/server/api/statistics.go index bf773eaa9..9f4a10b8f 100644 --- a/server/api/statistics.go +++ b/server/api/statistics.go @@ -33,10 +33,6 @@ func (a *API) handleStatistics(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" - if !a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in standalone mode")) - return - } // user must have right to access analytics userID := getUserID(r) diff --git a/server/api/system.go b/server/api/system.go index 1e579b452..4cefb3ff6 100644 --- a/server/api/system.go +++ b/server/api/system.go @@ -40,10 +40,6 @@ func (a *API) handlePing(w http.ResponseWriter, r *http.Request) { // description: success serverMetadata := a.app.GetServerMetadata() - if a.singleUserToken != "" { - serverMetadata.SKU = "personal_desktop" - } - if serverMetadata.Edition == "plugin" { serverMetadata.SKU = "suite" } diff --git a/server/api/system_test.go b/server/api/system_test.go index dd750539b..70fd41baf 100644 --- a/server/api/system_test.go +++ b/server/api/system_test.go @@ -71,41 +71,6 @@ func TestPing(t *testing.T) { } }) - t.Run("Sets SKU to 'personal_desktop' when in single-user mode", func(t *testing.T) { - testAPI.singleUserToken = "abc-123-xyz-456" - request, _ := http.NewRequest(http.MethodGet, "/ping", nil) - response := httptest.NewRecorder() - - testAPI.handlePing(response, request) - - var got app.ServerMetadata - err := json.NewDecoder(response.Body).Decode(&got) - if err != nil { - t.Fatalf("Unable to JSON decode response body %q", response.Body) - } - - want := app.ServerMetadata{ - Version: model.CurrentVersion, - BuildNumber: model.BuildNumber, - BuildDate: model.BuildDate, - Commit: model.BuildHash, - Edition: model.Edition, - DBType: "", - DBVersion: "", - OSType: runtime.GOOS, - OSArch: runtime.GOARCH, - SKU: "personal_desktop", - } - - if got != want { - t.Errorf("got %q want %q", got, want) - } - - if response.Code != http.StatusOK { - t.Errorf("got HTTP %d want %d", response.Code, http.StatusOK) - } - }) - t.Run("Sets SKU to 'suite' when in plugin mode", func(t *testing.T) { model.Edition = "plugin" request, _ := http.NewRequest(http.MethodGet, "/ping", nil) diff --git a/server/api/teams.go b/server/api/teams.go index 9723a9005..f41936ae4 100644 --- a/server/api/teams.go +++ b/server/api/teams.go @@ -8,7 +8,6 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/mattermost-plugin-boards/server/model" "github.com/mattermost/mattermost-plugin-boards/server/services/audit" - "github.com/mattermost/mattermost-plugin-boards/server/utils" ) func (a *API) registerTeamsRoutes(r *mux.Router) { @@ -101,20 +100,12 @@ func (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) { var team *model.Team var err error - if a.MattermostAuth { - team, err = a.app.GetTeam(teamID) - if model.IsErrNotFound(err) { - a.errorResponse(w, r, model.NewErrUnauthorized("invalid team")) - } - if err != nil { - a.errorResponse(w, r, err) - } - } else { - team, err = a.app.GetRootTeam() - if err != nil { - a.errorResponse(w, r, err) - return - } + team, err = a.app.GetTeam(teamID) + if model.IsErrNotFound(err) { + a.errorResponse(w, r, model.NewErrUnauthorized("invalid team")) + } + if err != nil { + a.errorResponse(w, r, err) } auditRec := a.makeAuditRecord(r, "getTeam", audit.Fail) @@ -131,54 +122,6 @@ func (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) { auditRec.Success() } -func (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /teams/{teamID}/regenerate_signup_token regenerateSignupToken - // - // Regenerates the signup token for the root team - // - // --- - // produces: - // - application/json - // parameters: - // - name: teamID - // in: path - // description: Team ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - if a.MattermostAuth { - a.errorResponse(w, r, model.NewErrNotImplemented("not permitted in plugin mode")) - return - } - - team, err := a.app.GetRootTeam() - if err != nil { - a.errorResponse(w, r, err) - return - } - - auditRec := a.makeAuditRecord(r, "regenerateSignupToken", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - - team.SignupToken = utils.NewID(utils.IDTypeToken) - - if err = a.app.UpsertTeamSignupToken(*team); err != nil { - a.errorResponse(w, r, err) - return - } - - jsonStringResponse(w, http.StatusOK, "{}") - auditRec.Success() -} - func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) { // swagger:operation GET /teams/{teamID}/users getTeamUsers // @@ -325,31 +268,18 @@ func (a *API) handleGetTeamUsersByID(w http.ResponseWriter, r *http.Request) { return } - if userIDs[0] == model.SingleUser { - ws, _ := a.app.GetRootTeam() - now := utils.GetMillis() - user := &model.User{ - ID: model.SingleUser, - Username: model.SingleUser, - Email: model.SingleUser, - CreateAt: ws.UpdateAt, - UpdateAt: now, - } - users = append(users, user) - } else { - users, error = a.app.GetUsersList(userIDs) - if error != nil { - a.errorResponse(w, r, error) - return - } + users, error = a.app.GetUsersList(userIDs) + if error != nil { + a.errorResponse(w, r, error) + return + } - for i, u := range users { - if a.permissions.HasPermissionToTeam(u.ID, teamID, model.PermissionManageTeam) { - users[i].Permissions = append(users[i].Permissions, model.PermissionManageTeam.Id) - } - if a.permissions.HasPermissionTo(u.ID, model.PermissionManageSystem) { - users[i].Permissions = append(users[i].Permissions, model.PermissionManageSystem.Id) - } + for i, u := range users { + if a.permissions.HasPermissionToTeam(u.ID, teamID, model.PermissionManageTeam) { + users[i].Permissions = append(users[i].Permissions, model.PermissionManageTeam.Id) + } + if a.permissions.HasPermissionTo(u.ID, model.PermissionManageSystem) { + users[i].Permissions = append(users[i].Permissions, model.PermissionManageSystem.Id) } } diff --git a/server/api/users.go b/server/api/users.go index 52780c6e2..8610dc59c 100644 --- a/server/api/users.go +++ b/server/api/users.go @@ -8,7 +8,6 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/mattermost-plugin-boards/server/model" "github.com/mattermost/mattermost-plugin-boards/server/services/audit" - "github.com/mattermost/mattermost-plugin-boards/server/utils" ) func (a *API) registerUsersRoutes(r *mux.Router) { @@ -70,23 +69,10 @@ func (a *API) handleGetUsersList(w http.ResponseWriter, r *http.Request) { return } - if userIDs[0] == model.SingleUser { - ws, _ := a.app.GetRootTeam() - now := utils.GetMillis() - user := &model.User{ - ID: model.SingleUser, - Username: model.SingleUser, - Email: model.SingleUser, - CreateAt: ws.UpdateAt, - UpdateAt: now, - } - users = append(users, user) - } else { - users, error = a.app.GetUsersList(userIDs) - if error != nil { - a.errorResponse(w, r, error) - return - } + users, error = a.app.GetUsersList(userIDs) + if error != nil { + a.errorResponse(w, r, error) + return } ctx := r.Context() @@ -163,23 +149,11 @@ func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) { auditRec := a.makeAuditRecord(r, "getMe", audit.Fail) defer a.audit.LogRecord(audit.LevelRead, auditRec) - if userID == model.SingleUser { - ws, _ := a.app.GetRootTeam() - now := utils.GetMillis() - user = &model.User{ - ID: model.SingleUser, - Username: model.SingleUser, - Email: model.SingleUser, - CreateAt: ws.UpdateAt, - UpdateAt: now, - } - } else { - user, err = a.app.GetUser(userID) - if err != nil { - // ToDo: wrap with an invalid token error - a.errorResponse(w, r, err) - return - } + user, err = a.app.GetUser(userID) + if err != nil { + // ToDo: wrap with an invalid token error + a.errorResponse(w, r, err) + return } if teamID != "" && a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionManageTeam) { diff --git a/server/app/auth.go b/server/app/auth.go index 42f77a2b3..d484a9871 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -2,10 +2,6 @@ package app import ( "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/auth" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/pkg/errors" ) @@ -18,11 +14,6 @@ const ( SecondsPerMinute = 60 ) -// GetSession Get a user active session and refresh the session if is needed. -func (a *App) GetSession(token string) (*model.Session, error) { - return a.auth.GetSession(token) -} - // IsValidReadToken validates the read token for a block. func (a *App) IsValidReadToken(boardID string, readToken string) (bool, error) { return a.auth.IsValidReadToken(boardID, readToken) @@ -75,157 +66,3 @@ func (a *App) GetUsersList(userIDs []string) ([]*model.User, error) { } return users, nil } - -// Login create a new user session if the authentication data is valid. -func (a *App) Login(username, email, password, mfaToken string) (string, error) { - var user *model.User - if username != "" { - var err error - user, err = a.store.GetUserByUsername(username) - if err != nil && !model.IsErrNotFound(err) { - a.metrics.IncrementLoginFailCount(1) - return "", errors.Wrap(err, "invalid username or password") - } - } - - if user == nil && email != "" { - var err error - user, err = a.store.GetUserByEmail(email) - if err != nil && model.IsErrNotFound(err) { - a.metrics.IncrementLoginFailCount(1) - return "", errors.Wrap(err, "invalid username or password") - } - } - - if user == nil { - a.metrics.IncrementLoginFailCount(1) - return "", errors.New("invalid username or password") - } - - if !auth.ComparePassword(user.Password, password) { - a.metrics.IncrementLoginFailCount(1) - a.logger.Debug("Invalid password for user", mlog.String("userID", user.ID)) - return "", errors.New("invalid username or password") - } - - authService := user.AuthService - if authService == "" { - authService = "native" - } - - session := model.Session{ - ID: utils.NewID(utils.IDTypeSession), - Token: utils.NewID(utils.IDTypeToken), - UserID: user.ID, - AuthService: authService, - Props: map[string]interface{}{}, - } - err := a.store.CreateSession(&session) - if err != nil { - return "", errors.Wrap(err, "unable to create session") - } - - a.metrics.IncrementLoginCount(1) - - // TODO: MFA verification - return session.Token, nil -} - -// Logout invalidates the user session. -func (a *App) Logout(sessionID string) error { - err := a.store.DeleteSession(sessionID) - if err != nil { - return errors.Wrap(err, "unable to delete the session") - } - - a.metrics.IncrementLogoutCount(1) - - return nil -} - -// RegisterUser creates a new user if the provided data is valid. -func (a *App) RegisterUser(username, email, password string) error { - var user *model.User - if username != "" { - var err error - user, err = a.store.GetUserByUsername(username) - if err != nil && !model.IsErrNotFound(err) { - return err - } - if user != nil { - return errors.New("The username already exists") - } - } - - if user == nil && email != "" { - var err error - user, err = a.store.GetUserByEmail(email) - if err != nil && !model.IsErrNotFound(err) { - return err - } - if user != nil { - return errors.New("The email already exists") - } - } - - // TODO: Move this into the config - passwordSettings := auth.PasswordSettings{ - MinimumLength: 6, - } - - err := auth.IsPasswordValid(password, passwordSettings) - if err != nil { - return errors.Wrap(err, "Invalid password") - } - - _, err = a.store.CreateUser(&model.User{ - ID: utils.NewID(utils.IDTypeUser), - Username: username, - Email: email, - Password: auth.HashPassword(password), - MfaSecret: "", - AuthService: a.config.AuthMode, - AuthData: "", - }) - if err != nil { - return errors.Wrap(err, "Unable to create the new user") - } - - return nil -} - -func (a *App) UpdateUserPassword(username, password string) error { - err := a.store.UpdateUserPassword(username, auth.HashPassword(password)) - if err != nil { - return err - } - - return nil -} - -func (a *App) ChangePassword(userID, oldPassword, newPassword string) error { - var user *model.User - if userID != "" { - var err error - user, err = a.store.GetUserByID(userID) - if err != nil { - return errors.Wrap(err, "invalid username or password") - } - } - - if user == nil { - return errors.New("invalid username or password") - } - - if !auth.ComparePassword(user.Password, oldPassword) { - a.logger.Debug("Invalid password for user", mlog.String("userID", user.ID)) - return errors.New("invalid username or password") - } - - err := a.store.UpdateUserPasswordByID(userID, auth.HashPassword(newPassword)) - if err != nil { - return errors.Wrap(err, "unable to update password") - } - - return nil -} diff --git a/server/app/auth_test.go b/server/app/auth_test.go index f63b08749..9819f2d39 100644 --- a/server/app/auth_test.go +++ b/server/app/auth_test.go @@ -3,9 +3,7 @@ package app import ( "testing" - "github.com/golang/mock/gomock" "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/auth" "github.com/mattermost/mattermost-plugin-boards/server/utils" "github.com/pkg/errors" "github.com/stretchr/testify/require" @@ -15,46 +13,7 @@ var mockUser = &model.User{ ID: utils.NewID(utils.IDTypeUser), Username: "testUsername", Email: "testEmail", - Password: auth.HashPassword("testPassword"), -} - -func TestLogin(t *testing.T) { - th, tearDown := SetupTestHelper(t) - defer tearDown() - - testcases := []struct { - title string - userName string - email string - password string - mfa string - isError bool - }{ - {"fail, missing login information", "", "", "", "", true}, - {"fail, invalid username", "badUsername", "", "", "", true}, - {"fail, invalid email", "", "badEmail", "", "", true}, - {"fail, invalid password", "testUsername", "", "badPassword", "", true}, - {"success, using username", "testUsername", "", "testPassword", "", false}, - {"success, using email", "", "testEmail", "testPassword", "", false}, - } - - th.Store.EXPECT().GetUserByUsername("badUsername").Return(nil, errors.New("Bad Username")) - th.Store.EXPECT().GetUserByEmail("badEmail").Return(nil, errors.New("Bad Email")) - th.Store.EXPECT().GetUserByUsername("testUsername").Return(mockUser, nil).Times(2) - th.Store.EXPECT().GetUserByEmail("testEmail").Return(mockUser, nil) - th.Store.EXPECT().CreateSession(gomock.Any()).Return(nil).Times(2) - - for _, test := range testcases { - t.Run(test.title, func(t *testing.T) { - token, err := th.App.Login(test.userName, test.email, test.password, test.mfa) - if test.isError { - require.Error(t, err) - } else { - require.NoError(t, err) - require.NotNil(t, token) - } - }) - } + Password: "testPassword", } func TestGetUser(t *testing.T) { @@ -86,103 +45,3 @@ func TestGetUser(t *testing.T) { }) } } - -func TestRegisterUser(t *testing.T) { - th, tearDown := SetupTestHelper(t) - defer tearDown() - - testcases := []struct { - title string - userName string - email string - password string - isError bool - }{ - {"fail, missing login information", "", "", "", true}, - {"fail, username exists", "existingUsername", "", "", true}, - {"fail, email exists", "", "existingEmail", "", true}, - {"fail, invalid password", "newUsername", "", "test", true}, - {"success, using email", "", "newEmail", "testPassword", false}, - } - - th.Store.EXPECT().GetUserByUsername("existingUsername").Return(mockUser, nil) - th.Store.EXPECT().GetUserByUsername("newUsername").Return(mockUser, errors.New("user not found")) - th.Store.EXPECT().GetUserByEmail("existingEmail").Return(mockUser, nil) - th.Store.EXPECT().GetUserByEmail("newEmail").Return(nil, model.NewErrNotFound("user")) - th.Store.EXPECT().CreateUser(gomock.Any()).Return(nil, nil) - - for _, test := range testcases { - t.Run(test.title, func(t *testing.T) { - err := th.App.RegisterUser(test.userName, test.email, test.password) - if test.isError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestUpdateUserPassword(t *testing.T) { - th, tearDown := SetupTestHelper(t) - defer tearDown() - - testcases := []struct { - title string - userName string - password string - isError bool - }{ - {"fail, missing login information", "", "", true}, - {"fail, invalid username", "badUsername", "", true}, - {"success, username", "testUsername", "testPassword", false}, - } - - th.Store.EXPECT().UpdateUserPassword("", gomock.Any()).Return(errors.New("user not found")) - th.Store.EXPECT().UpdateUserPassword("badUsername", gomock.Any()).Return(errors.New("user not found")) - th.Store.EXPECT().UpdateUserPassword("testUsername", gomock.Any()).Return(nil) - - for _, test := range testcases { - t.Run(test.title, func(t *testing.T) { - err := th.App.UpdateUserPassword(test.userName, test.password) - if test.isError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestChangePassword(t *testing.T) { - th, tearDown := SetupTestHelper(t) - defer tearDown() - - testcases := []struct { - title string - userName string - oldPassword string - password string - isError bool - }{ - {"fail, missing login information", "", "", "", true}, - {"fail, invalid userId", "badID", "", "", true}, - {"fail, invalid password", mockUser.ID, "wrongPassword", "newPassword", true}, - {"success, using username", mockUser.ID, "testPassword", "newPassword", false}, - } - - th.Store.EXPECT().GetUserByID("badID").Return(nil, errors.New("userID not found")) - th.Store.EXPECT().GetUserByID(mockUser.ID).Return(mockUser, nil).Times(2) - th.Store.EXPECT().UpdateUserPasswordByID(mockUser.ID, gomock.Any()).Return(nil) - - for _, test := range testcases { - t.Run(test.title, func(t *testing.T) { - err := th.App.ChangePassword(test.userName, test.oldPassword, test.password) - if test.isError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/server/app/helper_test.go b/server/app/helper_test.go index 43b46e9e8..828e948a9 100644 --- a/server/app/helper_test.go +++ b/server/app/helper_test.go @@ -36,8 +36,7 @@ func SetupTestHelper(t *testing.T) (*TestHelper, func()) { filesBackend := &mocks.FileBackend{} auth := auth.New(&cfg, store, nil) logger, _ := mlog.NewLogger() - sessionToken := "TESTTOKEN" - wsserver := ws.NewServer(auth, sessionToken, false, logger, store) + wsserver := ws.NewServer(auth, logger, store) webhook := webhook.NewClient(&cfg, logger) metricsService := metrics.NewMetrics(metrics.InstanceInfo{}) diff --git a/server/app/import.go b/server/app/import.go index 325cca68d..47b300af3 100644 --- a/server/app/import.go +++ b/server/app/import.go @@ -159,9 +159,6 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (*mo scanner := bufio.NewScanner(lineReader) userID := opt.ModifiedBy - if userID == model.SingleUser { - userID = "" - } now := utils.GetMillis() var boardID string var boardMembers []*model.BoardMember diff --git a/server/auth/auth.go b/server/auth/auth.go index 5474b9eff..267d060ea 100644 --- a/server/auth/auth.go +++ b/server/auth/auth.go @@ -6,12 +6,10 @@ import ( "github.com/mattermost/mattermost-plugin-boards/server/services/config" "github.com/mattermost/mattermost-plugin-boards/server/services/permissions" "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" "github.com/pkg/errors" ) type AuthInterface interface { - GetSession(token string) (*model.Session, error) IsValidReadToken(boardID string, readToken string) (bool, error) DoesUserHaveTeamAccess(userID string, teamID string) bool } @@ -28,22 +26,6 @@ func New(config *config.Configuration, store store.Store, permissions permission return &Auth{config: config, store: store, permissions: permissions} } -// GetSession Get a user active session and refresh the session if needed. -func (a *Auth) GetSession(token string) (*model.Session, error) { - if len(token) < 1 { - return nil, errors.New("no session token") - } - - session, err := a.store.GetSession(token, a.config.SessionExpireTime) - if err != nil { - return nil, errors.Wrap(err, "unable to get the session for the token") - } - if session.UpdateAt < (utils.GetMillis() - utils.SecondsToMillis(a.config.SessionRefreshTime)) { - _ = a.store.RefreshSession(session) - } - return session, nil -} - // IsValidReadToken validates the read token for a board. func (a *Auth) IsValidReadToken(boardID string, readToken string) (bool, error) { sharing, err := a.store.GetSharing(boardID) diff --git a/server/auth/auth_test.go b/server/auth/auth_test.go index eabded6b3..076eefd12 100644 --- a/server/auth/auth_test.go +++ b/server/auth/auth_test.go @@ -3,17 +3,8 @@ package auth import ( "testing" - "github.com/golang/mock/gomock" "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/config" - "github.com/mattermost/mattermost-plugin-boards/server/services/permissions/localpermissions" - mockpermissions "github.com/mattermost/mattermost-plugin-boards/server/services/permissions/mocks" "github.com/mattermost/mattermost-plugin-boards/server/services/store/mockstore" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost/server/public/shared/mlog" ) type TestHelper struct { @@ -22,71 +13,6 @@ type TestHelper struct { Store *mockstore.MockStore } -var mockSession = &model.Session{ - ID: utils.NewID(utils.IDTypeSession), - Token: "goodToken", - UserID: "12345", - CreateAt: utils.GetMillis() - utils.SecondsToMillis(2000), - UpdateAt: utils.GetMillis() - utils.SecondsToMillis(2000), -} - -func setupTestHelper(t *testing.T) *TestHelper { - ctrl := gomock.NewController(t) - ctrlPermissions := gomock.NewController(t) - cfg := config.Configuration{} - mockStore := mockstore.NewMockStore(ctrl) - mockPermissions := mockpermissions.NewMockStore(ctrlPermissions) - logger, err := mlog.NewLogger() - require.NoError(t, err) - newAuth := New(&cfg, mockStore, localpermissions.New(mockPermissions, logger)) - - // called during default template setup for every test - mockStore.EXPECT().GetTemplateBoards("0", "").AnyTimes() - mockStore.EXPECT().RemoveDefaultTemplates(gomock.Any()).AnyTimes() - mockStore.EXPECT().InsertBlock(gomock.Any(), gomock.Any()).AnyTimes() - - return &TestHelper{ - Auth: newAuth, - Session: *mockSession, - Store: mockStore, - } -} - -func TestGetSession(t *testing.T) { - th := setupTestHelper(t) - - testcases := []struct { - title string - token string - refreshTime int64 - isError bool - }{ - {"fail, no token", "", 0, true}, - {"fail, invalid username", "badToken", 0, true}, - {"success, good token", "goodToken", 1000, false}, - } - - th.Store.EXPECT().GetSession("badToken", gomock.Any()).Return(nil, errors.New("Invalid Token")) - th.Store.EXPECT().GetSession("goodToken", gomock.Any()).Return(mockSession, nil) - th.Store.EXPECT().RefreshSession(gomock.Any()).Return(nil) - - for _, test := range testcases { - t.Run(test.title, func(t *testing.T) { - if test.refreshTime > 0 { - th.Auth.config.SessionRefreshTime = test.refreshTime - } - - session, err := th.Auth.GetSession(test.token) - if test.isError { - require.Error(t, err) - } else { - require.NoError(t, err) - require.NotNil(t, session) - } - }) - } -} - func TestIsValidReadToken(t *testing.T) { // ToDo: reimplement diff --git a/server/boards/boardsapp.go b/server/boards/boardsapp.go index c1a733e49..7fcc2a9fa 100644 --- a/server/boards/boardsapp.go +++ b/server/boards/boardsapp.go @@ -78,7 +78,6 @@ func NewBoardsApp(api model.ServicesAPI) (*BoardsApp, error) { TablePrefix: cfg.DBTablePrefix, Logger: logger, DB: sqlDB, - IsPlugin: true, NewMutexFn: func(name string) (*cluster.Mutex, error) { return cluster.NewMutex(&mutexAPIAdapter{api: api}, name) }, @@ -91,13 +90,11 @@ func NewBoardsApp(api model.ServicesAPI) (*BoardsApp, error) { if err != nil { return nil, fmt.Errorf("error initializing the DB: %w", err) } - if cfg.AuthMode == server.MattermostAuthMod { - layeredStore, err2 := mattermostauthlayer.New(cfg.DBType, sqlDB, db, logger, api, storeParams.TablePrefix) - if err2 != nil { - return nil, fmt.Errorf("error initializing the DB: %w", err2) - } - db = layeredStore + layeredStore, err2 := mattermostauthlayer.New(cfg.DBType, sqlDB, db, logger, api, storeParams.TablePrefix) + if err2 != nil { + return nil, fmt.Errorf("error initializing the DB: %w", err2) } + db = layeredStore permissionsService := mmpermissions.New(db, api, logger) diff --git a/server/client/client.go b/server/client/client.go index 83cbe59e3..0ac0b44ef 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -523,43 +523,6 @@ 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 *model.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 *model.LoginRequest) (*model.LoginResponse, *Response) { - r, err := c.DoAPIPost(c.GetLoginRoute(), toJSON(&request)) - if err != nil { - return nil, BuildErrorResponse(r, err) - } - defer closeBody(r) - - data, err := model.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" } @@ -624,20 +587,6 @@ func (c *Client) GetUserList(ids []string) ([]model.User, *Response) { return users, BuildResponse(r) } -func (c *Client) GetUserChangePasswordRoute(id string) string { - return fmt.Sprintf("/users/%s/changepassword", id) -} - -func (c *Client) UserChangePassword(id string, data *model.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) CreateBoard(board *model.Board) (*model.Board, *Response) { r, err := c.DoAPIPost(c.GetBoardsRoute(), toJSON(board)) if err != nil { diff --git a/server/integrationtests/board_test.go b/server/integrationtests/board_test.go index 3bf9fc9fb..9d01a1a83 100644 --- a/server/integrationtests/board_test.go +++ b/server/integrationtests/board_test.go @@ -17,7 +17,6 @@ func TestGetBoards(t *testing.T) { t.Run("a non authenticated client should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) teamID := "0" newBoard := &model.Board{ @@ -117,7 +116,6 @@ func TestCreateBoard(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) newBoard := &model.Board{ Title: "board title", @@ -475,7 +473,6 @@ func TestSearchBoards(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) boards, resp := th.Client.SearchBoardsForTeam(testTeamID, "term") th.CheckUnauthorized(resp) @@ -587,7 +584,6 @@ func TestGetBoard(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) board, resp := th.Client.GetBoard("boar-id", "") th.CheckUnauthorized(resp) @@ -621,9 +617,6 @@ func TestGetBoard(t *testing.T) { th.CheckOK(resp) require.True(t, success) - // the client logs out - th.Logout(th.Client) - // we make sure that the client cannot currently retrieve the // board with no session board, resp = th.Client.GetBoard(rBoard.ID, "") @@ -702,7 +695,6 @@ func TestGetBoardMetadata(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelperWithLicense(t, LicenseEnterprise).InitBasic() defer th.TearDown() - th.Logout(th.Client) boardMetadata, resp := th.Client.GetBoardMetadata("boar-id", "") th.CheckUnauthorized(resp) @@ -861,9 +853,6 @@ func TestGetBoardMetadata(t *testing.T) { th.CheckOK(resp) require.True(t, success) - // the client logs out - th.Logout(th.Client) - // we make sure that the client cannot currently retrieve the // board with no session boardMetadata, resp := th.Client.GetBoardMetadata(rBoard.ID, "") @@ -883,7 +872,6 @@ func TestPatchBoard(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) initialTitle := "title 1" newBoard := &model.Board{ @@ -998,7 +986,6 @@ func TestDeleteBoard(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) newBoard := &model.Board{ Title: "title", @@ -1076,7 +1063,6 @@ func TestUndeleteBoard(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) newBoard := &model.Board{ Title: "title", @@ -1225,7 +1211,6 @@ func TestGetMembersForBoard(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() board := createBoardWithUsers(th) - th.Logout(th.Client) members, resp := th.Client.GetMembersForBoard(board.ID) th.CheckUnauthorized(resp) @@ -1270,7 +1255,6 @@ func TestAddMember(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) newBoard := &model.Board{ Title: "title", @@ -1490,7 +1474,6 @@ func TestUpdateMember(t *testing.T) { SchemeEditor: true, } - th.Logout(th.Client) member, resp := th.Client.UpdateBoardMember(updatedMember) th.CheckUnauthorized(resp) require.Nil(t, member) @@ -1665,7 +1648,6 @@ func TestDeleteMember(t *testing.T) { BoardID: board.ID, } - th.Logout(th.Client) success, resp := th.Client.DeleteBoardMember(member) th.CheckUnauthorized(resp) require.False(t, success) diff --git a/server/integrationtests/cards_test.go b/server/integrationtests/cards_test.go index ac3fa5186..7a212a518 100644 --- a/server/integrationtests/cards_test.go +++ b/server/integrationtests/cards_test.go @@ -17,7 +17,6 @@ func TestCreateCard(t *testing.T) { defer th.TearDown() board := th.CreateBoard(testTeamID, model.BoardTypeOpen) - th.Logout(th.Client) card := &model.Card{ Title: "basic card", @@ -157,8 +156,6 @@ func TestGetCards(t *testing.T) { }) t.Run("a non authenticated user should be rejected", func(t *testing.T) { - th.Logout(th.Client) - cards, resp := th.Client.GetCards(board.ID, 0, 10) th.CheckUnauthorized(resp) require.Nil(t, cards) @@ -173,8 +170,6 @@ func TestPatchCard(t *testing.T) { _, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 1) card := cards[0] - th.Logout(th.Client) - newTitle := "another title" patch := &model.CardPatch{ Title: &newTitle, @@ -243,8 +238,6 @@ func TestGetCard(t *testing.T) { _, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 1) card := cards[0] - th.Logout(th.Client) - cardFetched, resp := th.Client.GetCard(card.ID) th.CheckUnauthorized(resp) require.Nil(t, cardFetched) diff --git a/server/integrationtests/clienttestlib.go b/server/integrationtests/clienttestlib.go index cc60195bc..27cb15bba 100644 --- a/server/integrationtests/clienttestlib.go +++ b/server/integrationtests/clienttestlib.go @@ -11,7 +11,6 @@ import ( "github.com/mattermost/mattermost-plugin-boards/server/client" "github.com/mattermost/mattermost-plugin-boards/server/model" "github.com/mattermost/mattermost-plugin-boards/server/server" - "github.com/mattermost/mattermost-plugin-boards/server/services/auth" "github.com/mattermost/mattermost-plugin-boards/server/services/config" "github.com/mattermost/mattermost-plugin-boards/server/services/permissions/localpermissions" "github.com/mattermost/mattermost-plugin-boards/server/services/permissions/mmpermissions" @@ -151,8 +150,7 @@ func newTestServerWithLicense(singleUserToken string, licenseType LicenseType) * if err = logger.Configure("", cfg.LoggingCfgJSON, nil); err != nil { panic(err) } - singleUser := len(singleUserToken) > 0 - innerStore, err := server.NewStore(cfg, singleUser, logger) + innerStore, err := server.NewStore(cfg, logger) if err != nil { panic(err) } @@ -200,7 +198,7 @@ func NewTestServerPluginMode() *server.Server { if err = logger.Configure("", cfg.LoggingCfgJSON, nil); err != nil { panic(err) } - innerStore, err := server.NewStore(cfg, false, logger) + innerStore, err := server.NewStore(cfg, logger) if err != nil { panic(err) } @@ -236,7 +234,7 @@ func newTestServerLocalMode() *server.Server { panic(err) } - db, err := server.NewStore(cfg, false, logger) + db, err := server.NewStore(cfg, logger) if err != nil { panic(err) } @@ -255,9 +253,6 @@ func newTestServerLocalMode() *server.Server { panic(err) } - // Reduce password has strength for unit tests to dramatically speed up account creation and login - auth.PasswordHashStrength = 4 - return srv } @@ -363,23 +358,14 @@ func (th *TestHelper) Start() *TestHelper { // InitBasic starts the test server and initializes the clients of the // helper, registering them and logging them into the system. func (th *TestHelper) InitBasic() *TestHelper { - // Reduce password has strength for unit tests to dramatically speed up account creation and login - auth.PasswordHashStrength = 4 - th.Start() - // user1 - th.RegisterAndLogin(th.Client, user1Username, "user1@sample.com", password, "") - // get token team, resp := th.Client.GetTeam(model.GlobalTeamID) th.CheckOK(resp) require.NotNil(th.T, team) require.NotNil(th.T, team.SignupToken) - // user2 - th.RegisterAndLogin(th.Client2, user2Username, "user2@sample.com", password, team.SignupToken) - return th } @@ -406,44 +392,6 @@ func (th *TestHelper) TearDown() { } } -func (th *TestHelper) RegisterAndLogin(client *client.Client, username, email, password, token string) { - req := &model.RegisterRequest{ - Username: username, - Email: email, - Password: password, - Token: token, - } - - success, resp := th.Client.Register(req) - th.CheckOK(resp) - require.True(th.T, success) - - th.Login(client, username, password) -} - -func (th *TestHelper) Login(client *client.Client, username, password string) { - req := &model.LoginRequest{ - Type: "normal", - Username: username, - Password: password, - } - data, resp := client.Login(req) - th.CheckOK(resp) - require.NotNil(th.T, data) -} - -func (th *TestHelper) Login1() { - th.Login(th.Client, user1Username, password) -} - -func (th *TestHelper) Login2() { - th.Login(th.Client2, user2Username, password) -} - -func (th *TestHelper) Logout(client *client.Client) { - client.Token = "" -} - func (th *TestHelper) Me(client *client.Client) *model.User { user, resp := client.GetMe() th.CheckOK(resp) diff --git a/server/integrationtests/compliance_test.go b/server/integrationtests/compliance_test.go index 32425e450..b4d99f054 100644 --- a/server/integrationtests/compliance_test.go +++ b/server/integrationtests/compliance_test.go @@ -47,7 +47,6 @@ func TestGetBoardsForCompliance(t *testing.T) { defer th.TearDown() _ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2) - th.Logout(th.Client) bcr, resp := clients.Anon.GetBoardsForCompliance(testTeamID, 0, 0) @@ -134,7 +133,6 @@ func TestGetBoardsComplianceHistory(t *testing.T) { defer th.TearDown() _ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2) - th.Logout(th.Client) bchr, resp := clients.Anon.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, 0, 0) diff --git a/server/integrationtests/file_test.go b/server/integrationtests/file_test.go index 2a431d820..fa03e987b 100644 --- a/server/integrationtests/file_test.go +++ b/server/integrationtests/file_test.go @@ -17,7 +17,6 @@ func TestUploadFile(t *testing.T) { t.Run("a non authenticated user should be rejected", func(t *testing.T) { th := SetupTestHelper(t).InitBasic() defer th.TearDown() - th.Logout(th.Client) file, resp := th.Client.TeamUploadFile(testTeamID, "test-board-id", bytes.NewBuffer([]byte("test"))) th.CheckUnauthorized(resp) diff --git a/server/integrationtests/permissions_test.go b/server/integrationtests/permissions_test.go index aca374a4f..be6874de9 100644 --- a/server/integrationtests/permissions_test.go +++ b/server/integrationtests/permissions_test.go @@ -91,7 +91,6 @@ func setupClients(th *TestHelper) Clients { func setupLocalClients(th *TestHelper) Clients { th.Client = client.NewClient(th.Server.Config().ServerRoot, "") - th.RegisterAndLogin(th.Client, "sysadmin", "sysadmin@sample.com", password, "") clients := Clients{ Anon: client.NewClient(th.Server.Config().ServerRoot, ""), @@ -109,23 +108,11 @@ func setupLocalClients(th *TestHelper) Clients { th.CheckOK(resp) require.NotNil(th.T, team) require.NotNil(th.T, team.SignupToken) - - th.RegisterAndLogin(clients.NoTeamMember, userNoTeamMember, userNoTeamMember+"@sample.com", password, team.SignupToken) userNoTeamMemberID = clients.NoTeamMember.GetUserID() - - th.RegisterAndLogin(clients.TeamMember, userTeamMember, userTeamMember+"@sample.com", password, team.SignupToken) userTeamMemberID = clients.TeamMember.GetUserID() - - th.RegisterAndLogin(clients.Viewer, userViewer, userViewer+"@sample.com", password, team.SignupToken) userViewerID = clients.Viewer.GetUserID() - - th.RegisterAndLogin(clients.Commenter, userCommenter, userCommenter+"@sample.com", password, team.SignupToken) userCommenterID = clients.Commenter.GetUserID() - - th.RegisterAndLogin(clients.Editor, userEditor, userEditor+"@sample.com", password, team.SignupToken) userEditorID = clients.Editor.GetUserID() - - th.RegisterAndLogin(clients.Admin, userAdmin, userAdmin+"@sample.com", password, team.SignupToken) userAdminID = clients.Admin.GetUserID() return clients @@ -2657,36 +2644,6 @@ func TestPermissionsGetUser(t *testing.T) { }) } -func TestPermissionsUserChangePassword(t *testing.T) { - postBody := toJSON(t, model.ChangePasswordRequest{ - OldPassword: password, - NewPassword: "newpa$$word123", - }) - - t.Run("plugin", func(t *testing.T) { - th := SetupTestHelperPluginMode(t) - defer th.TearDown() - clients := setupClients(th) - testData := setupData(t, th) - ttCases := []TestCase{ - {"/users/{USER_ADMIN_ID}/changepassword", methodPost, postBody, userAnon, http.StatusUnauthorized, 0}, - {"/users/{USER_ADMIN_ID}/changepassword", methodPost, postBody, userAdmin, http.StatusNotImplemented, 0}, - } - runTestCases(t, ttCases, testData, clients) - }) - t.Run("local", func(t *testing.T) { - th := SetupTestHelperLocalMode(t) - defer th.TearDown() - clients := setupLocalClients(th) - testData := setupData(t, th) - ttCases := []TestCase{ - {"/users/{USER_ADMIN_ID}/changepassword", methodPost, postBody, userAnon, http.StatusUnauthorized, 0}, - {"/users/{USER_ADMIN_ID}/changepassword", methodPost, postBody, userAdmin, http.StatusOK, 0}, - } - runTestCases(t, ttCases, testData, clients) - }) -} - func TestPermissionsUpdateUserConfig(t *testing.T) { patch := toJSON(t, model.UserPreferencesPatch{UpdatedFields: map[string]string{"test": "test"}}) @@ -2873,39 +2830,6 @@ func TestPermissionsDeleteBoardsAndBlocks(t *testing.T) { }) } -func TestPermissionsLogin(t *testing.T) { - loginReq := func(username, password string) string { - return toJSON(t, model.LoginRequest{ - Type: "normal", - Username: username, - Password: password, - }) - } - - t.Run("plugin", func(t *testing.T) { - th := SetupTestHelperPluginMode(t) - defer th.TearDown() - clients := setupClients(th) - testData := setupData(t, th) - ttCases := []TestCase{ - {"/login", methodPost, loginReq(userAnon, password), userAnon, http.StatusNotImplemented, 0}, - {"/login", methodPost, loginReq(userAdmin, password), userAdmin, http.StatusNotImplemented, 0}, - } - runTestCases(t, ttCases, testData, clients) - }) - t.Run("local", func(t *testing.T) { - th := SetupTestHelperLocalMode(t) - defer th.TearDown() - clients := setupLocalClients(th) - testData := setupData(t, th) - ttCases := []TestCase{ - {"/login", methodPost, loginReq(userAnon, password), userAnon, http.StatusUnauthorized, 0}, - {"/login", methodPost, loginReq(userAdmin, password), userAdmin, http.StatusOK, 1}, - } - runTestCases(t, ttCases, testData, clients) - }) -} - func TestPermissionsLogout(t *testing.T) { t.Run("plugin", func(t *testing.T) { th := SetupTestHelperPluginMode(t) @@ -2931,43 +2855,6 @@ func TestPermissionsLogout(t *testing.T) { }) } -func TestPermissionsRegister(t *testing.T) { - t.Run("plugin", func(t *testing.T) { - th := SetupTestHelperPluginMode(t) - defer th.TearDown() - clients := setupClients(th) - testData := setupData(t, th) - ttCases := []TestCase{ - {"/register", methodPost, "", userAnon, http.StatusNotImplemented, 0}, - {"/register", methodPost, "", userAdmin, http.StatusNotImplemented, 0}, - } - runTestCases(t, ttCases, testData, clients) - }) - t.Run("local", func(t *testing.T) { - th := SetupTestHelperLocalMode(t) - defer th.TearDown() - clients := setupLocalClients(th) - testData := setupData(t, th) - - team, resp := th.Client.GetTeam(model.GlobalTeamID) - th.CheckOK(resp) - require.NotNil(th.T, team) - require.NotNil(th.T, team.SignupToken) - - postData := toJSON(t, model.RegisterRequest{ - Username: "newuser", - Email: "newuser@test.com", - Password: password, - Token: team.SignupToken, - }) - - ttCases := []TestCase{ - {"/register", methodPost, postData, userAnon, http.StatusOK, 0}, - } - runTestCases(t, ttCases, testData, clients) - }) -} - func TestPermissionsClientConfig(t *testing.T) { ttCases := []TestCase{ {"/clientConfig", methodGet, "", userAnon, http.StatusOK, 1}, diff --git a/server/integrationtests/sharing_test.go b/server/integrationtests/sharing_test.go index 2288dd3be..687343fff 100644 --- a/server/integrationtests/sharing_test.go +++ b/server/integrationtests/sharing_test.go @@ -16,16 +16,12 @@ func TestSharing(t *testing.T) { token := utils.NewID(utils.IDTypeToken) t.Run("an unauthenticated client should not be able to get a sharing", func(t *testing.T) { - th.Logout(th.Client) - sharing, resp := th.Client.GetSharing("board-id") th.CheckUnauthorized(resp) require.Nil(t, sharing) }) t.Run("Check no initial sharing", func(t *testing.T) { - th.Login1() - teamID := "0" newBoard := &model.Board{ TeamID: teamID, diff --git a/server/integrationtests/user_test.go b/server/integrationtests/user_test.go index e2246ec2a..28b3c0344 100644 --- a/server/integrationtests/user_test.go +++ b/server/integrationtests/user_test.go @@ -10,73 +10,6 @@ import ( "github.com/stretchr/testify/require" ) -const ( - fakeUsername = "fakeUsername" - fakeEmail = "mock@test.com" -) - -func TestUserRegister(t *testing.T) { - th := SetupTestHelper(t).Start() - defer th.TearDown() - - // register - registerRequest := &model.RegisterRequest{ - Username: fakeUsername, - Email: fakeEmail, - Password: utils.NewID(utils.IDTypeNone), - } - success, resp := th.Client.Register(registerRequest) - require.NoError(t, resp.Error) - require.True(t, success) - - // register again will fail - success, resp = th.Client.Register(registerRequest) - require.Error(t, resp.Error) - require.False(t, success) -} - -func TestUserLogin(t *testing.T) { - th := SetupTestHelper(t).Start() - defer th.TearDown() - - t.Run("with nonexist user", func(t *testing.T) { - loginRequest := &model.LoginRequest{ - Type: "normal", - Username: "nonexistuser", - Email: "", - Password: utils.NewID(utils.IDTypeNone), - } - 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.NewID(utils.IDTypeNone) - // register - registerRequest := &model.RegisterRequest{ - Username: fakeUsername, - Email: fakeEmail, - Password: password, - } - success, resp := th.Client.Register(registerRequest) - require.NoError(t, resp.Error) - require.True(t, success) - - // login - loginRequest := &model.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 := SetupTestHelper(t).Start() defer th.TearDown() @@ -86,65 +19,12 @@ func TestGetMe(t *testing.T) { require.Error(t, resp.Error) require.Nil(t, me) }) - - t.Run("logged in", func(t *testing.T) { - // register - password := utils.NewID(utils.IDTypeNone) - registerRequest := &model.RegisterRequest{ - Username: fakeUsername, - Email: fakeEmail, - Password: password, - } - success, resp := th.Client.Register(registerRequest) - require.NoError(t, resp.Error) - require.True(t, success) - // login - loginRequest := &model.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, "", me.Email) - require.Equal(t, registerRequest.Username, me.Username) - }) } func TestGetUser(t *testing.T) { th := SetupTestHelper(t).Start() defer th.TearDown() - // register - password := utils.NewID(utils.IDTypeNone) - registerRequest := &model.RegisterRequest{ - Username: fakeUsername, - Email: fakeEmail, - Password: password, - } - success, resp := th.Client.Register(registerRequest) - require.NoError(t, resp.Error) - require.True(t, success) - // login - loginRequest := &model.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) @@ -241,39 +121,9 @@ func TestUserChangePassword(t *testing.T) { th := SetupTestHelper(t).Start() defer th.TearDown() - // register - password := utils.NewID(utils.IDTypeNone) - registerRequest := &model.RegisterRequest{ - Username: fakeUsername, - Email: fakeEmail, - Password: password, - } - success, resp := th.Client.Register(registerRequest) - require.NoError(t, resp.Error) - require.True(t, success) - // login - loginRequest := &model.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, &model.ChangePasswordRequest{ - OldPassword: password, - NewPassword: utils.NewID(utils.IDTypeNone), - }) - require.NoError(t, resp.Error) - require.True(t, success) } func randomBytes(t *testing.T, n int) []byte { diff --git a/server/main/doc.go b/server/main/doc.go deleted file mode 100644 index 7d7427eba..000000000 --- a/server/main/doc.go +++ /dev/null @@ -1,26 +0,0 @@ -// Package classification Focalboard Server -// -// Focalboard Server -// -// Schemes: http, https -// Host: localhost -// BasePath: /api/v2 -// Version: 2.0.0 -// License: Custom https://github.com/mattermost/focalboard/blob/main/LICENSE.txt -// Contact: Focalboard https://www.focalboard.com -// -// Consumes: -// - application/json -// -// Produces: -// - application/json -// -// securityDefinitions: -// BearerAuth: -// type: apiKey -// name: Authorization -// in: header -// description: 'Pass session token using Bearer authentication, e.g. set header "Authorization: Bearer "' -// -// swagger:meta -package main diff --git a/server/main/main.go b/server/main/main.go deleted file mode 100644 index 0327c78bd..000000000 --- a/server/main/main.go +++ /dev/null @@ -1,297 +0,0 @@ -// Server for Focalboard -package main - -import ( - "C" - "flag" - "log" - "os" - "os/signal" - "syscall" - "time" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/server" - "github.com/mattermost/mattermost-plugin-boards/server/services/config" - "github.com/mattermost/mattermost-plugin-boards/server/services/permissions/localpermissions" -) -import ( - "github.com/mattermost/mattermost/server/public/shared/mlog" -) - -// Active server used with shared code (dll) -var pServer *server.Server - -const ( - timeBetweenPidMonitoringChecks = 2 * time.Second -) - -func isProcessRunning(pid int) bool { - process, err := os.FindProcess(pid) - if err != nil { - return false - } - - err = process.Signal(syscall.Signal(0)) - - return err == nil -} - -// monitorPid is used to keep the server lifetime in sync with another (client app) process -func monitorPid(pid int, logger *mlog.Logger) { - logger.Info("Monitoring PID", mlog.Int("pid", pid)) - - go func() { - for { - if !isProcessRunning(pid) { - logger.Info("Monitored process not found, exiting.") - os.Exit(1) - } - - time.Sleep(timeBetweenPidMonitoringChecks) - } - }() -} - -func main() { - // Command line args - pMonitorPid := flag.Int("monitorpid", -1, "a process ID") - pPort := flag.Int("port", 0, "the port number") - pSingleUser := flag.Bool("single-user", false, "single user mode") - pDBType := flag.String("dbtype", "", "Database type") - pDBConfig := flag.String("dbconfig", "", "Database config") - pConfigFilePath := flag.String( - "config", - "", - "Location of the JSON config file", - ) - flag.Parse() - - config, err := config.ReadConfigFile(*pConfigFilePath) - if err != nil { - log.Fatal("Unable to read the config file: ", err) - return - } - - logger, _ := mlog.NewLogger() - cfgJSON := config.LoggingCfgJSON - if config.LoggingCfgFile == "" && cfgJSON == "" { - // if no logging defined, use default config (console output) - cfgJSON = defaultLoggingConfig() - } - err = logger.Configure(config.LoggingCfgFile, cfgJSON, nil) - if err != nil { - log.Fatal("Error in config file for logger: ", err) - return - } - defer func() { _ = logger.Shutdown() }() - - if logger.HasTargets() { - restore := logger.RedirectStdLog(mlog.LvlInfo, mlog.String("src", "stdlog")) - defer restore() - } - - model.LogServerInfo(logger) - - singleUser := false - if pSingleUser != nil { - singleUser = *pSingleUser - } - - singleUserToken := "" - if singleUser { - singleUserToken = os.Getenv("FOCALBOARD_SINGLE_USER_TOKEN") - if len(singleUserToken) < 1 { - logger.Fatal("The FOCALBOARD_SINGLE_USER_TOKEN environment variable must be set for single user mode ") - return - } - logger.Info("Single user mode") - } - - if pMonitorPid != nil && *pMonitorPid > 0 { - monitorPid(*pMonitorPid, logger) - } - - // Override config from commandline - - if pDBType != nil && len(*pDBType) > 0 { - config.DBType = *pDBType - logger.Info("DBType from commandline", mlog.String("DBType", *pDBType)) - } - - if pDBConfig != nil && len(*pDBConfig) > 0 { - config.DBConfigString = *pDBConfig - // Don't echo, as the confix string may contain passwords - logger.Info("DBConfigString overridden from commandline") - } - - if pPort != nil && *pPort > 0 && *pPort != config.Port { - // Override port - logger.Info("Port from commandline", mlog.Int("port", *pPort)) - config.Port = *pPort - } - - db, err := server.NewStore(config, singleUser, logger) - if err != nil { - logger.Fatal("server.NewStore ERROR", mlog.Err(err)) - } - - permissionsService := localpermissions.New(db, logger) - - params := server.Params{ - Cfg: config, - SingleUserToken: singleUserToken, - DBStore: db, - Logger: logger, - PermissionsService: permissionsService, - } - - server, err := server.New(params) - if err != nil { - logger.Fatal("server.New ERROR", mlog.Err(err)) - } - - if err := server.Start(); err != nil { - logger.Fatal("server.Start ERROR", mlog.Err(err)) - } - - // Setting up signal capturing - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt) - - // Waiting for SIGINT (pkill -2) - <-stop - - _ = server.Shutdown() -} - -// StartServer starts the server -// -//export StartServer -func StartServer(webPath *C.char, filesPath *C.char, port int, singleUserToken, dbConfigString, configFilePath *C.char) { - startServer( - C.GoString(webPath), - C.GoString(filesPath), - port, - C.GoString(singleUserToken), - C.GoString(dbConfigString), - C.GoString(configFilePath), - ) -} - -// StopServer stops the server -// -//export StopServer -func StopServer() { - stopServer() -} - -func startServer(webPath string, filesPath string, port int, singleUserToken, dbConfigString, configFilePath string) { - if pServer != nil { - stopServer() - pServer = nil - } - - // config.json file - config, err := config.ReadConfigFile(configFilePath) - if err != nil { - log.Fatal("Unable to read the config file: ", err) - return - } - - logger, _ := mlog.NewLogger() - err = logger.Configure(config.LoggingCfgFile, config.LoggingCfgJSON, nil) - if err != nil { - log.Fatal("Error in config file for logger: ", err) - return - } - - model.LogServerInfo(logger) - - if len(filesPath) > 0 { - config.FilesPath = filesPath - } - - if len(webPath) > 0 { - config.WebPath = webPath - } - - if port > 0 { - config.Port = port - } - - if len(dbConfigString) > 0 { - config.DBConfigString = dbConfigString - } - - singleUser := len(singleUserToken) > 0 - db, err := server.NewStore(config, singleUser, logger) - if err != nil { - logger.Fatal("server.NewStore ERROR", mlog.Err(err)) - } - - permissionsService := localpermissions.New(db, logger) - - params := server.Params{ - Cfg: config, - SingleUserToken: singleUserToken, - DBStore: db, - Logger: logger, - PermissionsService: permissionsService, - } - - pServer, err = server.New(params) - if err != nil { - logger.Fatal("server.New ERROR", mlog.Err(err)) - } - - if err := pServer.Start(); err != nil { - logger.Fatal("server.Start ERROR", mlog.Err(err)) - } -} - -func stopServer() { - if pServer == nil { - return - } - - logger := pServer.Logger() - - err := pServer.Shutdown() - if err != nil { - logger.Error("server.Shutdown ERROR", mlog.Err(err)) - } - - if l, ok := logger.(*mlog.Logger); ok { - _ = l.Shutdown() - } - pServer = nil -} - -func defaultLoggingConfig() string { - return ` - { - "def": { - "type": "console", - "options": { - "out": "stdout" - }, - "format": "plain", - "format_options": { - "delim": " ", - "min_level_len": 5, - "min_msg_len": 40, - "enable_color": true, - "enable_caller": true - }, - "levels": [ - {"id": 5, "name": "debug"}, - {"id": 4, "name": "info", "color": 36}, - {"id": 3, "name": "warn"}, - {"id": 2, "name": "error", "color": 31}, - {"id": 1, "name": "fatal", "stacktrace": true}, - {"id": 0, "name": "panic", "stacktrace": true} - ] - } - }` -} diff --git a/server/model/auth.go b/server/model/auth.go deleted file mode 100644 index 629701c7d..000000000 --- a/server/model/auth.go +++ /dev/null @@ -1,135 +0,0 @@ -package model - -import ( - "encoding/json" - "fmt" - "io" - "strings" - - "github.com/mattermost/mattermost-plugin-boards/server/services/auth" -) - -const ( - MinimumPasswordLength = 8 -) - -func NewErrAuthParam(msg string) *ErrAuthParam { - return &ErrAuthParam{ - msg: msg, - } -} - -type ErrAuthParam struct { - msg string -} - -func (pe *ErrAuthParam) Error() string { - return pe.msg -} - -// LoginRequest is a login request -// swagger:model -type LoginRequest struct { - // Type of login, currently must be set to "normal" - // required: true - Type string `json:"type"` - - // If specified, login using username - // required: false - Username string `json:"username"` - - // If specified, login using email - // required: false - Email string `json:"email"` - - // Password - // required: true - Password string `json:"password"` - - // MFA token - // required: false - // swagger:ignore - MfaToken string `json:"mfa_token"` -} - -// LoginResponse is a login response -// swagger:model -type LoginResponse struct { - // Session token - // required: true - 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 { - // User name - // required: true - Username string `json:"username"` - - // User's email - // required: true - Email string `json:"email"` - - // Password - // required: true - Password string `json:"password"` - - // Registration authorization token - // required: true - Token string `json:"token"` -} - -func (rd *RegisterRequest) IsValid() error { - if strings.TrimSpace(rd.Username) == "" { - return NewErrAuthParam("username is required") - } - if strings.TrimSpace(rd.Email) == "" { - return NewErrAuthParam("email is required") - } - if !auth.IsEmailValid(rd.Email) { - return NewErrAuthParam("invalid email format") - } - if rd.Password == "" { - return NewErrAuthParam("password is required") - } - return isValidPassword(rd.Password) -} - -// ChangePasswordRequest is a user password change request -// swagger:model -type ChangePasswordRequest struct { - // Old password - // required: true - OldPassword string `json:"oldPassword"` - - // New password - // required: true - NewPassword string `json:"newPassword"` -} - -// IsValid validates a password change request. -func (rd *ChangePasswordRequest) IsValid() error { - if rd.OldPassword == "" { - return NewErrAuthParam("old password is required") - } - if rd.NewPassword == "" { - return NewErrAuthParam("new password is required") - } - return isValidPassword(rd.NewPassword) -} - -func isValidPassword(password string) error { - if len(password) < MinimumPasswordLength { - return NewErrAuthParam(fmt.Sprintf("password must be at least %d characters", MinimumPasswordLength)) - } - return nil -} diff --git a/server/model/block.go b/server/model/block.go index 1a1e4bb50..f03845520 100644 --- a/server/model/block.go +++ b/server/model/block.go @@ -245,10 +245,6 @@ type QueryBlockHistoryChildOptions struct { } func StampModificationMetadata(userID string, blocks []*Block, auditRec *audit.Record) { - if userID == SingleUser { - userID = "" - } - now := GetMillis() for i := range blocks { blocks[i].ModifiedBy = userID diff --git a/server/model/error.go b/server/model/error.go index 772c90450..aad437c94 100644 --- a/server/model/error.go +++ b/server/model/error.go @@ -185,12 +185,6 @@ func IsErrBadRequest(err error) bool { return true } - // check if this is a model.ErrAuthParam - var ap *ErrAuthParam - if errors.As(err, &ap) { - return true - } - // check if this is a model.ErrInvalidCategory var ic *ErrInvalidCategory if errors.As(err, &ic) { diff --git a/server/model/user.go b/server/model/user.go index 1840e082f..d73e57455 100644 --- a/server/model/user.go +++ b/server/model/user.go @@ -6,7 +6,6 @@ import ( ) const ( - SingleUser = "single-user" GlobalTeamID = "0" SystemUserID = "system" PreferencesCategoryFocalboard = "focalboard" diff --git a/server/server/initHandlers.go b/server/server/initHandlers.go deleted file mode 100644 index e891032aa..000000000 --- a/server/server/initHandlers.go +++ /dev/null @@ -1,6 +0,0 @@ -package server - -func (s *Server) initHandlers() { - cfg := s.config - s.api.MattermostAuth = cfg.AuthMode == MattermostAuthMod -} diff --git a/server/server/server.go b/server/server/server.go index fd2c5c1f5..25e010077 100644 --- a/server/server/server.go +++ b/server/server/server.go @@ -3,6 +3,7 @@ package server import ( "database/sql" "fmt" + "log" "net" "net/http" "os" @@ -31,8 +32,10 @@ import ( "github.com/mattermost/mattermost-plugin-boards/server/utils" "github.com/mattermost/mattermost-plugin-boards/server/web" "github.com/mattermost/mattermost-plugin-boards/server/ws" + "github.com/mattermost/mattermost/server/public/model" "github.com/oklog/run" + "github.com/mattermost/mattermost/server/public/pluginapi/cluster" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/v8/platform/shared/filestore" ) @@ -40,10 +43,6 @@ import ( const ( cleanupSessionTaskFrequency = 10 * time.Minute updateMetricsTaskFrequency = 15 * time.Minute - - minSessionExpiryTime = int64(60 * 60 * 24 * 31) // 31 days - - MattermostAuthMod = "mattermost" ) type Server struct { @@ -78,7 +77,7 @@ func New(params Params) (*Server, error) { // if no ws adapter is provided, we spin up a websocket server wsAdapter := params.WSAdapter if wsAdapter == nil { - wsAdapter = ws.NewServer(authenticator, params.SingleUserToken, params.Cfg.AuthMode == MattermostAuthMod, params.Logger, params.DBStore) + wsAdapter = ws.NewServer(authenticator, params.Logger, params.DBStore) } filesBackendSettings := filestore.FileBackendSettings{} @@ -143,11 +142,10 @@ func New(params Params) (*Server, error) { } app := app.New(params.Cfg, wsAdapter, appServices) - focalboardAPI := api.NewAPI(app, params.SingleUserToken, params.Cfg.AuthMode, params.PermissionsService, params.Logger, auditService, params.IsPlugin) + focalboardAPI := api.NewAPI(app, params.SingleUserToken, params.Cfg.AuthMode, params.PermissionsService, params.Logger, auditService) // Local router for admin APIs localRouter := mux.NewRouter() - focalboardAPI.RegisterAdminRoutes(localRouter) // Init team if _, err := app.GetRootTeam(); err != nil { @@ -203,12 +201,26 @@ func New(params Params) (*Server, error) { app: app, } - server.initHandlers() - return &server, nil } -func NewStore(config *config.Configuration, isSingleUser bool, logger mlog.LoggerIFace) (store.Store, error) { +type MyPluginAPI struct { + // Add fields as necessary +} + +func (api *MyPluginAPI) KVSetWithOptions(key string, value []byte, options model.PluginKVSetOptions) (bool, *model.AppError) { + // Implement the method + // For example: + return true, nil +} + +func (api *MyPluginAPI) LogError(msg string, keyValuePairs ...interface{}) { + // Implement the method + // For example: + log.Printf(msg, keyValuePairs...) +} + +func NewStore(config *config.Configuration, logger mlog.LoggerIFace) (store.Store, error) { sqlDB, err := sql.Open(config.DBType, config.DBConfigString) if err != nil { logger.Error("connectDatabase failed", mlog.Err(err)) @@ -221,6 +233,8 @@ func NewStore(config *config.Configuration, isSingleUser bool, logger mlog.Logge return nil, err } + myAPI := &MyPluginAPI{} + storeParams := sqlstore.Params{ DBType: config.DBType, DBPingAttempts: config.DBPingAttempts, @@ -228,8 +242,9 @@ func NewStore(config *config.Configuration, isSingleUser bool, logger mlog.Logge TablePrefix: config.DBTablePrefix, Logger: logger, DB: sqlDB, - IsPlugin: false, - IsSingleUser: isSingleUser, + NewMutexFn: func(name string) (*cluster.Mutex, error) { + return cluster.NewMutex(myAPI, "test") + }, } var db store.Store @@ -254,19 +269,6 @@ func (s *Server) Start() error { } } - if s.config.AuthMode != MattermostAuthMod { - s.cleanUpSessionsTask = scheduler.CreateRecurringTask("cleanUpSessions", func() { - secondsAgo := minSessionExpiryTime - if secondsAgo < s.config.SessionExpireTime { - secondsAgo = s.config.SessionExpireTime - } - - if err := s.store.CleanUpSessions(secondsAgo); err != nil { - s.logger.Error("Unable to clean up the sessions", mlog.Err(err)) - } - }, cleanupSessionTaskFrequency) - } - metricsUpdater := func() { blockCounts, err := s.store.GetBlockCountsByType() if err != nil { diff --git a/server/services/auth/email.go b/server/services/auth/email.go deleted file mode 100644 index f8f1d930f..000000000 --- a/server/services/auth/email.go +++ /dev/null @@ -1,13 +0,0 @@ -package auth - -import "regexp" - -var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") - -// IsEmailValid checks if the email provided passes the required structure and length. -func IsEmailValid(e string) bool { - if len(e) < 3 || len(e) > 254 { - return false - } - return emailRegex.MatchString(e) -} diff --git a/server/services/auth/password.go b/server/services/auth/password.go deleted file mode 100644 index 6f371eda0..000000000 --- a/server/services/auth/password.go +++ /dev/null @@ -1,106 +0,0 @@ -package auth - -import ( - "fmt" - "strings" - - "golang.org/x/crypto/bcrypt" -) - -const ( - PasswordMaximumLength = 64 - PasswordSpecialChars = "!\"\\#$%&'()*+,-./:;<=>?@[]^_`|~" //nolint:gosec - PasswordNumbers = "0123456789" - PasswordUpperCaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - PasswordLowerCaseLetters = "abcdefghijklmnopqrstuvwxyz" - PasswordAllChars = PasswordSpecialChars + PasswordNumbers + PasswordUpperCaseLetters + PasswordLowerCaseLetters - - InvalidLowercasePassword = "lowercase" - InvalidMinLengthPassword = "min-length" - InvalidMaxLengthPassword = "max-length" - InvalidNumberPassword = "number" - InvalidUppercasePassword = "uppercase" - InvalidSymbolPassword = "symbol" -) - -var PasswordHashStrength = 10 - -// HashPassword generates a hash using the bcrypt.GenerateFromPassword. -func HashPassword(password string) string { - hash, err := bcrypt.GenerateFromPassword([]byte(password), PasswordHashStrength) - if err != nil { - panic(err) - } - - return string(hash) -} - -// ComparePassword compares the hash. -func ComparePassword(hash, password string) bool { - if len(password) == 0 || len(hash) == 0 { - return false - } - - err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - return err == nil -} - -type InvalidPasswordError struct { - FailingCriterias []string -} - -func (ipe *InvalidPasswordError) Error() string { - return fmt.Sprintf("invalid password, failing criteria: %s", strings.Join(ipe.FailingCriterias, ", ")) -} - -type PasswordSettings struct { - MinimumLength int - Lowercase bool - Number bool - Uppercase bool - Symbol bool -} - -func IsPasswordValid(password string, settings PasswordSettings) error { - err := &InvalidPasswordError{ - FailingCriterias: []string{}, - } - - if len(password) < settings.MinimumLength { - err.FailingCriterias = append(err.FailingCriterias, InvalidMinLengthPassword) - } - - if len(password) > PasswordMaximumLength { - err.FailingCriterias = append(err.FailingCriterias, InvalidMaxLengthPassword) - } - - if settings.Lowercase { - if !strings.ContainsAny(password, PasswordLowerCaseLetters) { - err.FailingCriterias = append(err.FailingCriterias, InvalidLowercasePassword) - } - } - - if settings.Uppercase { - if !strings.ContainsAny(password, PasswordUpperCaseLetters) { - err.FailingCriterias = append(err.FailingCriterias, InvalidUppercasePassword) - } - } - - if settings.Number { - if !strings.ContainsAny(password, PasswordNumbers) { - err.FailingCriterias = append(err.FailingCriterias, InvalidNumberPassword) - } - } - - if settings.Symbol { - if !strings.ContainsAny(password, PasswordSpecialChars) { - err.FailingCriterias = append(err.FailingCriterias, InvalidSymbolPassword) - } - } - - if len(err.FailingCriterias) > 0 { - return err - } - - return nil -} diff --git a/server/services/auth/password_test.go b/server/services/auth/password_test.go deleted file mode 100644 index 52697ab26..000000000 --- a/server/services/auth/password_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package auth - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestPasswordHash(t *testing.T) { - hash := HashPassword("Test") - - assert.True(t, ComparePassword(hash, "Test"), "Passwords don't match") - assert.False(t, ComparePassword(hash, "Test2"), "Passwords should not have matched") -} - -func TestIsPasswordValidWithSettings(t *testing.T) { - for name, tc := range map[string]struct { - Password string - Settings PasswordSettings - ExpectedFailingCriterias []string - }{ - "Short": { - Password: strings.Repeat("x", 3), - Settings: PasswordSettings{ - MinimumLength: 3, - Lowercase: false, - Uppercase: false, - Number: false, - Symbol: false, - }, - }, - "Long": { - Password: strings.Repeat("x", PasswordMaximumLength), - Settings: PasswordSettings{ - MinimumLength: 3, - Lowercase: false, - Uppercase: false, - Number: false, - Symbol: false, - }, - }, - "TooShort": { - Password: strings.Repeat("x", 2), - Settings: PasswordSettings{ - MinimumLength: 3, - Lowercase: false, - Uppercase: false, - Number: false, - Symbol: false, - }, - ExpectedFailingCriterias: []string{"min-length"}, - }, - "TooLong": { - Password: strings.Repeat("x", PasswordMaximumLength+1), - Settings: PasswordSettings{ - MinimumLength: 3, - Lowercase: false, - Uppercase: false, - Number: false, - Symbol: false, - }, - ExpectedFailingCriterias: []string{"max-length"}, - }, - "MissingLower": { - Password: "AAAAAAAAAAASD123!@#", - Settings: PasswordSettings{ - MinimumLength: 3, - Lowercase: true, - Uppercase: false, - Number: false, - Symbol: false, - }, - ExpectedFailingCriterias: []string{"lowercase"}, - }, - "MissingUpper": { - Password: "aaaaaaaaaaaaasd123!@#", - Settings: PasswordSettings{ - MinimumLength: 3, - Uppercase: true, - Lowercase: false, - Number: false, - Symbol: false, - }, - ExpectedFailingCriterias: []string{"uppercase"}, - }, - "MissingNumber": { - Password: "asasdasdsadASD!@#", - Settings: PasswordSettings{ - MinimumLength: 3, - Number: true, - Lowercase: false, - Uppercase: false, - Symbol: false, - }, - ExpectedFailingCriterias: []string{"number"}, - }, - "MissingSymbol": { - Password: "asdasdasdasdasdASD123", - Settings: PasswordSettings{ - MinimumLength: 3, - Symbol: true, - Lowercase: false, - Uppercase: false, - Number: false, - }, - ExpectedFailingCriterias: []string{"symbol"}, - }, - "MissingMultiple": { - Password: "asdasdasdasdasdasd", - Settings: PasswordSettings{ - MinimumLength: 3, - Lowercase: true, - Uppercase: true, - Number: true, - Symbol: true, - }, - ExpectedFailingCriterias: []string{"uppercase", "number", "symbol"}, - }, - "Everything": { - Password: "asdASD!@#123", - Settings: PasswordSettings{ - MinimumLength: 3, - Lowercase: true, - Uppercase: true, - Number: true, - Symbol: true, - }, - }, - } { - t.Run(name, func(t *testing.T) { - err := IsPasswordValid(tc.Password, tc.Settings) - if len(tc.ExpectedFailingCriterias) == 0 { - assert.NoError(t, err) - } else { - require.Error(t, err) - var errFC *InvalidPasswordError - if assert.ErrorAs(t, err, &errFC) { - assert.Equal(t, tc.ExpectedFailingCriterias, errFC.FailingCriterias) - } - } - }) - } -} diff --git a/server/services/store/mattermostauthlayer/mattermostauthlayer.go b/server/services/store/mattermostauthlayer/mattermostauthlayer.go index bc3248743..2b2c48a80 100644 --- a/server/services/store/mattermostauthlayer/mattermostauthlayer.go +++ b/server/services/store/mattermostauthlayer/mattermostauthlayer.go @@ -115,22 +115,6 @@ func (s *MattermostAuthLayer) GetUserByUsername(username string) (*model.User, e return &user, nil } -func (s *MattermostAuthLayer) CreateUser(user *model.User) (*model.User, error) { - return nil, store.NewNotSupportedError("no user creation allowed from focalboard, create it using mattermost") -} - -func (s *MattermostAuthLayer) UpdateUser(user *model.User) (*model.User, error) { - return nil, store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - -func (s *MattermostAuthLayer) UpdateUserPassword(username, password string) error { - return store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - -func (s *MattermostAuthLayer) UpdateUserPasswordByID(userID, password string) error { - return store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - func (s *MattermostAuthLayer) PatchUserPreferences(userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error) { preferences, err := s.GetUserPreferences(userID) if err != nil { @@ -236,30 +220,6 @@ func (s *MattermostAuthLayer) GetActiveUserCount(updatedSecondsAgo int64) (int, return count, nil } -func (s *MattermostAuthLayer) GetSession(token string, expireTime int64) (*model.Session, error) { - return nil, store.NewNotSupportedError("sessions not used when using mattermost") -} - -func (s *MattermostAuthLayer) CreateSession(session *model.Session) error { - return store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - -func (s *MattermostAuthLayer) RefreshSession(session *model.Session) error { - return store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - -func (s *MattermostAuthLayer) UpdateSession(session *model.Session) error { - return store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - -func (s *MattermostAuthLayer) DeleteSession(sessionID string) error { - return store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - -func (s *MattermostAuthLayer) CleanUpSessions(expireTime int64) error { - return store.NewNotSupportedError("no update allowed from focalboard, update it using mattermost") -} - func (s *MattermostAuthLayer) GetTeam(id string) (*model.Team, error) { if id == "0" { team := model.Team{ diff --git a/server/services/store/mockstore/mockstore.go b/server/services/store/mockstore/mockstore.go index 973dae3f2..77c8a555f 100644 --- a/server/services/store/mockstore/mockstore.go +++ b/server/services/store/mockstore/mockstore.go @@ -65,20 +65,6 @@ func (mr *MockStoreMockRecorder) CanSeeUser(arg0, arg1 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanSeeUser", reflect.TypeOf((*MockStore)(nil).CanSeeUser), arg0, arg1) } -// CleanUpSessions mocks base method. -func (m *MockStore) CleanUpSessions(arg0 int64) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CleanUpSessions", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// CleanUpSessions indicates an expected call of CleanUpSessions. -func (mr *MockStoreMockRecorder) CleanUpSessions(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanUpSessions", reflect.TypeOf((*MockStore)(nil).CleanUpSessions), arg0) -} - // CreateBoardsAndBlocks mocks base method. func (m *MockStore) CreateBoardsAndBlocks(arg0 *model.BoardsAndBlocks, arg1 string) (*model.BoardsAndBlocks, error) { m.ctrl.T.Helper() @@ -906,12 +892,6 @@ func (m *MockStore) GetSession(arg0 string, arg1 int64) (*model.Session, error) return ret0, ret1 } -// GetSession indicates an expected call of GetSession. -func (mr *MockStoreMockRecorder) GetSession(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockStore)(nil).GetSession), arg0, arg1) -} - // GetSharing mocks base method. func (m *MockStore) GetSharing(arg0 string) (*model.Sharing, error) { m.ctrl.T.Helper() @@ -1396,12 +1376,6 @@ func (m *MockStore) RefreshSession(arg0 *model.Session) error { return ret0 } -// RefreshSession indicates an expected call of RefreshSession. -func (mr *MockStoreMockRecorder) RefreshSession(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshSession", reflect.TypeOf((*MockStore)(nil).RefreshSession), arg0) -} - // RemoveDefaultTemplates mocks base method. func (m *MockStore) RemoveDefaultTemplates(arg0 []*model.Board) error { m.ctrl.T.Helper() diff --git a/server/services/store/sqlstore/board.go b/server/services/store/sqlstore/board.go index 48998c687..42896baa5 100644 --- a/server/services/store/sqlstore/board.go +++ b/server/services/store/sqlstore/board.go @@ -234,36 +234,6 @@ func (s *SQLStore) getBoard(db sq.BaseRunner, boardID string) (*model.Board, err return s.getBoardByCondition(db, sq.Eq{"id": boardID}) } -func (s *SQLStore) getBoardsForUserAndTeam(db sq.BaseRunner, userID, teamID string, includePublicBoards bool) ([]*model.Board, error) { - query := s.getQueryBuilder(db). - Select(boardFields("b.")...). - Distinct(). - From(s.tablePrefix + "boards as b"). - LeftJoin(s.tablePrefix + "board_members as bm on b.id=bm.board_id"). - Where(sq.Eq{"b.team_id": teamID}). - Where(sq.Eq{"b.is_template": false}) - - if includePublicBoards { - query = query.Where(sq.Or{ - sq.Eq{"b.type": model.BoardTypeOpen}, - sq.Eq{"bm.user_id": userID}, - }) - } else { - query = query.Where(sq.Or{ - sq.Eq{"bm.user_id": userID}, - }) - } - - rows, err := query.Query() - if err != nil { - s.logger.Error(`getBoardsForUserAndTeam ERROR`, mlog.Err(err)) - return nil, err - } - defer s.CloseRows(rows) - - return s.boardsFromRows(rows) -} - func (s *SQLStore) getBoardsInTeamByIds(db sq.BaseRunner, boardIDs []string, teamID string) ([]*model.Board, error) { query := s.getQueryBuilder(db). Select(boardFields("b.")...). @@ -646,112 +616,6 @@ func (s *SQLStore) getMembersForBoard(db sq.BaseRunner, boardID string) ([]*mode return s.boardMembersFromRows(rows) } -// searchBoardsForUser returns all boards that match with the -// term that are either private and which the user is a member of, or -// they're open, regardless of the user membership. -// Search is case-insensitive. -func (s *SQLStore) searchBoardsForUser(db sq.BaseRunner, term string, searchField model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) { - query := s.getQueryBuilder(db). - Select(boardFields("b.")...). - Distinct(). - From(s.tablePrefix + "boards as b"). - LeftJoin(s.tablePrefix + "board_members as bm on b.id=bm.board_id"). - Where(sq.Eq{"b.is_template": false}) - - if includePublicBoards { - query = query.Where(sq.Or{ - sq.Eq{"b.type": model.BoardTypeOpen}, - sq.Eq{"bm.user_id": userID}, - }) - } else { - query = query.Where(sq.Or{ - sq.Eq{"bm.user_id": userID}, - }) - } - - if term != "" { - if searchField == model.BoardSearchFieldPropertyName { - switch s.dbType { - case model.PostgresDBType: - where := "b.properties->? is not null" - query = query.Where(where, term) - case model.MysqlDBType, model.SqliteDBType: - where := "JSON_EXTRACT(b.properties, ?) IS NOT NULL" - query = query.Where(where, "$."+term) - default: - where := "b.properties LIKE ?" - query = query.Where(where, "%\""+term+"\"%") - } - } else { // model.BoardSearchFieldTitle - // break search query into space separated words - // and search for all words. - // This should later be upgraded to industrial-strength - // word tokenizer, that uses much more than space - // to break words. - conditions := sq.And{} - for _, word := range strings.Split(strings.TrimSpace(term), " ") { - conditions = append(conditions, sq.Like{"lower(b.title)": "%" + strings.ToLower(word) + "%"}) - } - query = query.Where(conditions) - } - } - - rows, err := query.Query() - if err != nil { - s.logger.Error(`searchBoardsForUser ERROR`, mlog.Err(err)) - return nil, err - } - defer s.CloseRows(rows) - - return s.boardsFromRows(rows) -} - -// searchBoardsForUserInTeam returns all boards that match with the -// term that are either private and which the user is a member of, or -// they're open, regardless of the user membership. -// Search is case-insensitive. -func (s *SQLStore) searchBoardsForUserInTeam(db sq.BaseRunner, teamID, term, userID string) ([]*model.Board, error) { - query := s.getQueryBuilder(db). - Select(boardFields("b.")...). - Distinct(). - From(s.tablePrefix + "boards as b"). - LeftJoin(s.tablePrefix + "board_members as bm on b.id=bm.board_id"). - Where(sq.Eq{"b.is_template": false}). - Where(sq.Eq{"b.team_id": teamID}). - Where(sq.Or{ - sq.Eq{"b.type": model.BoardTypeOpen}, - sq.And{ - sq.Eq{"b.type": model.BoardTypePrivate}, - sq.Eq{"bm.user_id": userID}, - }, - }) - - if term != "" { - // break search query into space separated words - // and search for all words. - // This should later be upgraded to industrial-strength - // word tokenizer, that uses much more than space - // to break words. - - conditions := sq.And{} - - for _, word := range strings.Split(strings.TrimSpace(term), " ") { - conditions = append(conditions, sq.Like{"lower(b.title)": "%" + strings.ToLower(word) + "%"}) - } - - query = query.Where(conditions) - } - - rows, err := query.Query() - if err != nil { - s.logger.Error(`searchBoardsForUser ERROR`, mlog.Err(err)) - return nil, err - } - defer s.CloseRows(rows) - - return s.boardsFromRows(rows) -} - func (s *SQLStore) getBoardHistory(db sq.BaseRunner, boardID string, opts model.QueryBoardHistoryOptions) ([]*model.Board, error) { var order string if opts.Descending { diff --git a/server/services/store/sqlstore/data_migrations.go b/server/services/store/sqlstore/data_migrations.go index 07169ae5d..264a83d00 100644 --- a/server/services/store/sqlstore/data_migrations.go +++ b/server/services/store/sqlstore/data_migrations.go @@ -154,20 +154,18 @@ func (s *SQLStore) RunCategoryUUIDIDMigration() error { return txErr } - if s.isPlugin { - if err := s.createCategories(tx); err != nil { - if rollbackErr := tx.Rollback(); rollbackErr != nil { - s.logger.Error("category UUIDs insert categories transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) - } - return err + if err := s.createCategories(tx); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + s.logger.Error("category UUIDs insert categories transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) } + return err + } - if err := s.createCategoryBoards(tx); err != nil { - if rollbackErr := tx.Rollback(); rollbackErr != nil { - s.logger.Error("category UUIDs insert category boards transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) - } - return err + if err := s.createCategoryBoards(tx); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + s.logger.Error("category UUIDs insert category boards transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting")) } + return err } if err := s.setSystemSetting(tx, CategoryUUIDIDMigrationKey, strconv.FormatBool(true)); err != nil { @@ -353,10 +351,6 @@ func (s *SQLStore) createCategoryBoards(db sq.BaseRunner) error { // group messages. This function migrates all boards // belonging to a DM to the best possible team. func (s *SQLStore) RunTeamLessBoardsMigration() error { - if !s.isPlugin { - return nil - } - setting, err := s.GetSystemSetting(TeamLessBoardsMigrationKey) if err != nil { return fmt.Errorf("cannot get teamless boards migration state: %w", err) @@ -555,10 +549,6 @@ func (s *SQLStore) getBoardUserTeams(tx sq.BaseRunner, board *model.Board) (map[ } func (s *SQLStore) RunDeletedMembershipBoardsMigration() error { - if !s.isPlugin { - return nil - } - setting, err := s.GetSystemSetting(DeletedMembershipBoardsMigrationKey) if err != nil { return fmt.Errorf("cannot get deleted membership boards migration state: %w", err) @@ -661,7 +651,7 @@ func (s *SQLStore) RunFixCollationsAndCharsetsMigration() error { var collation string var charSet string var err error - if !s.isPlugin || os.Getenv("FOCALBOARD_UNIT_TESTING") == "1" { + if os.Getenv("FOCALBOARD_UNIT_TESTING") == "1" { collation = "utf8mb4_general_ci" charSet = "utf8mb4" } else { diff --git a/server/services/store/sqlstore/data_migrations_test.go b/server/services/store/sqlstore/data_migrations_test.go deleted file mode 100644 index fabb93f49..000000000 --- a/server/services/store/sqlstore/data_migrations_test.go +++ /dev/null @@ -1,270 +0,0 @@ -package sqlstore - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetBlocksWithSameID(t *testing.T) { - t.Skip("we need to setup a test with the database migrated up to version 14 and then run these tests") - - store, tearDown := SetupTests(t) - sqlStore := store.(*SQLStore) - defer tearDown() - - container1 := "1" - container2 := "2" - container3 := "3" - - block1 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"} - block2 := &model.Block{ID: "block-id-2", BoardID: "board-id-2"} - block3 := &model.Block{ID: "block-id-3", BoardID: "board-id-3"} - - block4 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"} - block5 := &model.Block{ID: "block-id-2", BoardID: "board-id-2"} - - block6 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"} - block7 := &model.Block{ID: "block-id-7", BoardID: "board-id-7"} - block8 := &model.Block{ID: "block-id-8", BoardID: "board-id-8"} - - for _, block := range []*model.Block{block1, block2, block3} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, "user-id") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - for _, block := range []*model.Block{block4, block5} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, "user-id") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - for _, block := range []*model.Block{block6, block7, block8} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container3, block, "user-id") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - blocksWithDuplicatedID := []*model.Block{block1, block2, block4, block5, block6} - - blocks, err := sqlStore.getBlocksWithSameID(sqlStore.db) - require.NoError(t, err) - - // we process the found blocks to remove extra information and be - // able to compare both expected and found sets - foundBlocks := []*model.Block{} - for _, foundBlock := range blocks { - foundBlocks = append(foundBlocks, &model.Block{ID: foundBlock.ID, BoardID: foundBlock.BoardID}) - } - - require.ElementsMatch(t, blocksWithDuplicatedID, foundBlocks) -} - -func TestReplaceBlockID(t *testing.T) { - t.Skip("we need to setup a test with the database migrated up to version 14 and then run these tests") - - store, tearDown := SetupTests(t) - sqlStore := store.(*SQLStore) - defer tearDown() - - container1 := "1" - container2 := "2" - - // blocks from team1 - block1 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"} - block2 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"} - block3 := &model.Block{ID: "block-id-3", BoardID: "block-id-1"} - block4 := &model.Block{ID: "block-id-4", BoardID: "block-id-2"} - block5 := &model.Block{ID: "block-id-5", BoardID: "block-id-1", ParentID: "block-id-1"} - block8 := &model.Block{ - ID: "block-id-8", BoardID: "board-id-2", Type: model.TypeCard, - Fields: map[string]interface{}{"contentOrder": []string{"block-id-1", "block-id-2"}}, - } - - // blocks from team2. They're identical to blocks 1 and 2, - // but they shouldn't change - block6 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"} - block7 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"} - block9 := &model.Block{ - ID: "block-id-8", BoardID: "board-id-2", Type: model.TypeCard, - Fields: map[string]interface{}{"contentOrder": []string{"block-id-1", "block-id-2"}}, - } - - for _, block := range []*model.Block{block1, block2, block3, block4, block5, block8} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, "user-id") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - for _, block := range []*model.Block{block6, block7, block9} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, "user-id") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - currentID := "block-id-1" - newID := "new-id-1" - err := sqlStore.replaceBlockID(sqlStore.db, currentID, newID, "1") - require.NoError(t, err) - - newBlock1, err := sqlStore.getLegacyBlock(sqlStore.db, container1, newID) - require.NoError(t, err) - newBlock2, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block2.ID) - require.NoError(t, err) - newBlock3, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block3.ID) - require.NoError(t, err) - newBlock5, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block5.ID) - require.NoError(t, err) - newBlock6, err := sqlStore.getLegacyBlock(sqlStore.db, container2, block6.ID) - require.NoError(t, err) - newBlock7, err := sqlStore.getLegacyBlock(sqlStore.db, container2, block7.ID) - require.NoError(t, err) - newBlock8, err := sqlStore.GetBlock(block8.ID) - require.NoError(t, err) - newBlock9, err := sqlStore.GetBlock(block9.ID) - require.NoError(t, err) - - require.Equal(t, newID, newBlock1.ID) - require.Equal(t, newID, newBlock2.ParentID) - require.Equal(t, newID, newBlock3.BoardID) - require.Equal(t, newID, newBlock5.BoardID) - require.Equal(t, newID, newBlock5.ParentID) - require.Equal(t, newBlock8.Fields["contentOrder"].([]interface{})[0], newID) - require.Equal(t, newBlock8.Fields["contentOrder"].([]interface{})[1], "block-id-2") - - require.Equal(t, currentID, newBlock6.ID) - require.Equal(t, currentID, newBlock7.ParentID) - require.Equal(t, newBlock9.Fields["contentOrder"].([]interface{})[0], "block-id-1") - require.Equal(t, newBlock9.Fields["contentOrder"].([]interface{})[1], "block-id-2") -} - -func TestRunUniqueIDsMigration(t *testing.T) { - t.Skip("we need to setup a test with the database migrated up to version 14 and then run these tests") - - store, tearDown := SetupTests(t) - sqlStore := store.(*SQLStore) - defer tearDown() - - // we need to mark the migration as not done so we can run it - // again with the test data - keyErr := sqlStore.SetSystemSetting(UniqueIDsMigrationKey, "false") - require.NoError(t, keyErr) - - container1 := "1" - container2 := "2" - container3 := "3" - - // blocks from workspace1. They shouldn't change, as the first - // duplicated ID is preserved - block1 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"} - block2 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"} - block3 := &model.Block{ID: "block-id-3", BoardID: "block-id-1"} - - // blocks from workspace2. They're identical to blocks 1, 2 and 3, - // and they should change - block4 := &model.Block{ID: "block-id-1", BoardID: "board-id-1"} - block5 := &model.Block{ID: "block-id-2", BoardID: "board-id-2", ParentID: "block-id-1"} - block6 := &model.Block{ID: "block-id-6", BoardID: "block-id-1", ParentID: "block-id-2"} - - // block from workspace3. It should change as well - block7 := &model.Block{ID: "block-id-2", BoardID: "board-id-2"} - - for _, block := range []*model.Block{block1, block2, block3} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, "user-id-2") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - for _, block := range []*model.Block{block4, block5, block6} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, "user-id-2") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - for _, block := range []*model.Block{block7} { - err := sqlStore.insertLegacyBlock(sqlStore.db, container3, block, "user-id-2") - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - } - - err := sqlStore.RunUniqueIDsMigration() - require.NoError(t, err) - - // blocks from workspace 1 haven't changed, so we can simply fetch them - newBlock1, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block1.ID) - require.NoError(t, err) - require.NotNil(t, newBlock1) - newBlock2, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block2.ID) - require.NoError(t, err) - require.NotNil(t, newBlock2) - newBlock3, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block3.ID) - require.NoError(t, err) - require.NotNil(t, newBlock3) - - // first two blocks from workspace 2 have changed, so we fetch - // them through the third one, which points to the new IDs - newBlock6, err := sqlStore.getLegacyBlock(sqlStore.db, container2, block6.ID) - require.NoError(t, err) - require.NotNil(t, newBlock6) - newBlock4, err := sqlStore.getLegacyBlock(sqlStore.db, container2, newBlock6.BoardID) - require.NoError(t, err) - require.NotNil(t, newBlock4) - newBlock5, err := sqlStore.getLegacyBlock(sqlStore.db, container2, newBlock6.ParentID) - require.NoError(t, err) - require.NotNil(t, newBlock5) - - // block from workspace 3 changed as well, so we shouldn't be able - // to fetch it - newBlock7, err := sqlStore.getLegacyBlock(sqlStore.db, container3, block7.ID) - require.NoError(t, err) - require.Nil(t, newBlock7) - - // workspace 1 block links are maintained - require.Equal(t, newBlock1.ID, newBlock2.ParentID) - require.Equal(t, newBlock1.ID, newBlock3.BoardID) - - // workspace 2 first two block IDs have changed - require.NotEqual(t, block4.ID, newBlock4.BoardID) - require.NotEqual(t, block5.ID, newBlock5.ParentID) -} - -func TestCheckForMismatchedCollation(t *testing.T) { - store, tearDown := SetupTests(t) - sqlStore := store.(*SQLStore) - defer tearDown() - - if sqlStore.dbType != model.MysqlDBType { - return - } - - // make sure all collations are consistent. - tableNames, err := sqlStore.getFocalBoardTableNames() - require.NoError(t, err) - - sqlCollation := "SELECT table_collation FROM information_schema.tables WHERE table_name=? and table_schema=(SELECT DATABASE())" - stmtCollation, err := sqlStore.db.Prepare(sqlCollation) - require.NoError(t, err) - defer stmtCollation.Close() - - var collation string - - // make sure the correct charset is applied to each table. - for i, name := range tableNames { - row := stmtCollation.QueryRow(name) - - var actualCollation string - err = row.Scan(&actualCollation) - require.NoError(t, err) - - if collation == "" { - collation = actualCollation - } - - assert.Equalf(t, collation, actualCollation, "for table_name='%s', index=%d", name, i) - } -} diff --git a/server/services/store/sqlstore/file.go b/server/services/store/sqlstore/file.go deleted file mode 100644 index 72645690f..000000000 --- a/server/services/store/sqlstore/file.go +++ /dev/null @@ -1,92 +0,0 @@ -package sqlstore - -import ( - "database/sql" - "errors" - - sq "github.com/Masterminds/squirrel" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - - mmModel "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/public/shared/mlog" -) - -func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) error { - query := s.getQueryBuilder(db). - Insert(s.tablePrefix+"file_info"). - Columns( - "id", - "create_at", - "name", - "extension", - "size", - "delete_at", - "path", - "archived", - ). - Values( - fileInfo.Id, - fileInfo.CreateAt, - fileInfo.Name, - fileInfo.Extension, - fileInfo.Size, - fileInfo.DeleteAt, - fileInfo.Path, - false, - ) - - if _, err := query.Exec(); err != nil { - s.logger.Error( - "failed to save fileinfo", - mlog.String("file_name", fileInfo.Name), - mlog.Int("size", fileInfo.Size), - mlog.Err(err), - ) - return err - } - - return nil -} - -func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo, error) { - query := s.getQueryBuilder(db). - Select( - "id", - "create_at", - "delete_at", - "name", - "extension", - "size", - "archived", - "path", - ). - From(s.tablePrefix + "file_info"). - Where(sq.Eq{"Id": id}) - - row := query.QueryRow() - - fileInfo := mmModel.FileInfo{} - - err := row.Scan( - &fileInfo.Id, - &fileInfo.CreateAt, - &fileInfo.DeleteAt, - &fileInfo.Name, - &fileInfo.Extension, - &fileInfo.Size, - &fileInfo.Archived, - &fileInfo.Path, - ) - - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, model.NewErrNotFound("file info ID=" + id) - } - - s.logger.Error("error scanning fileinfo row", mlog.String("id", id), mlog.Err(err)) - return nil, err - } - - return &fileInfo, nil -} diff --git a/server/services/store/sqlstore/helpers_test.go b/server/services/store/sqlstore/helpers_test.go deleted file mode 100644 index 95ad63977..000000000 --- a/server/services/store/sqlstore/helpers_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package sqlstore - -import ( - "database/sql" - "os" - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost/server/public/shared/mlog" -) - -func SetupTests(t *testing.T) (store.Store, func()) { - origUnitTesting := os.Getenv("FOCALBOARD_UNIT_TESTING") - os.Setenv("FOCALBOARD_UNIT_TESTING", "1") - - dbType, connectionString, err := PrepareNewTestDatabase() - require.NoError(t, err) - - logger, _ := mlog.NewLogger() - - sqlDB, err := sql.Open(dbType, connectionString) - require.NoError(t, err) - err = sqlDB.Ping() - require.NoError(t, err) - - storeParams := Params{ - DBType: dbType, - ConnectionString: connectionString, - DBPingAttempts: 5, - TablePrefix: "test_", - Logger: logger, - DB: sqlDB, - IsPlugin: false, - } - store, err := New(storeParams) - require.NoError(t, err) - - tearDown := func() { - defer func() { _ = logger.Shutdown() }() - err = store.Shutdown() - require.Nil(t, err) - if err = os.Remove(connectionString); err == nil { - logger.Debug("Removed test database", mlog.String("file", connectionString)) - } - os.Setenv("FOCALBOARD_UNIT_TESTING", origUnitTesting) - } - - return store, tearDown -} diff --git a/server/services/store/sqlstore/legacy_blocks.go b/server/services/store/sqlstore/legacy_blocks.go index b33d552c3..532846810 100644 --- a/server/services/store/sqlstore/legacy_blocks.go +++ b/server/services/store/sqlstore/legacy_blocks.go @@ -1,16 +1,10 @@ package sqlstore import ( - "database/sql" - "encoding/json" "strings" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - sq "github.com/Masterminds/squirrel" "github.com/mattermost/mattermost-plugin-boards/server/model" - - "github.com/mattermost/mattermost/server/public/shared/mlog" ) func legacyBoardFields(prefix string) []string { @@ -56,198 +50,6 @@ func legacyBoardFields(prefix string) []string { return prefixedFields } -// legacyBlocksFromRows is the old getBlock version that still uses -// the old block model. This method is kept to enable the unique IDs -// data migration. -func (s *SQLStore) legacyBlocksFromRows(rows *sql.Rows) ([]*model.Block, error) { - results := []*model.Block{} - - for rows.Next() { - var block model.Block - var fieldsJSON string - var modifiedBy sql.NullString - var insertAt string - - err := rows.Scan( - &block.ID, - &block.ParentID, - &block.BoardID, - &block.CreatedBy, - &modifiedBy, - &block.Schema, - &block.Type, - &block.Title, - &fieldsJSON, - &insertAt, - &block.CreateAt, - &block.UpdateAt, - &block.DeleteAt, - &block.WorkspaceID) - if err != nil { - // handle this error - s.logger.Error(`ERROR blocksFromRows`, mlog.Err(err)) - - return nil, err - } - - if modifiedBy.Valid { - block.ModifiedBy = modifiedBy.String - } - - err = json.Unmarshal([]byte(fieldsJSON), &block.Fields) - if err != nil { - // handle this error - s.logger.Error(`ERROR blocksFromRows fields`, mlog.Err(err)) - - return nil, err - } - - results = append(results, &block) - } - - return results, nil -} - -// getLegacyBlock is the old getBlock version that still uses the old -// block model. This method is kept to enable the unique IDs data -// migration. -func (s *SQLStore) getLegacyBlock(db sq.BaseRunner, workspaceID string, blockID string) (*model.Block, error) { - query := s.getQueryBuilder(db). - Select( - "id", - "parent_id", - "root_id", - "created_by", - "modified_by", - s.escapeField("schema"), - "type", - "title", - "COALESCE(fields, '{}')", - "insert_at", - "create_at", - "update_at", - "delete_at", - "COALESCE(workspace_id, '0')", - ). - From(s.tablePrefix + "blocks"). - Where(sq.Eq{"id": blockID}). - Where(sq.Eq{"coalesce(workspace_id, '0')": workspaceID}) - - rows, err := query.Query() - if err != nil { - s.logger.Error(`GetBlock ERROR`, mlog.Err(err)) - return nil, err - } - - blocks, err := s.legacyBlocksFromRows(rows) - if err != nil { - return nil, err - } - - if len(blocks) == 0 { - return nil, nil - } - - return blocks[0], nil -} - -// insertLegacyBlock is the old insertBlock version that still uses -// the old block model. This method is kept to enable the unique IDs -// data migration. -func (s *SQLStore) insertLegacyBlock(db sq.BaseRunner, workspaceID string, block *model.Block, userID string) error { - if block.BoardID == "" { - return model.ErrBlockEmptyBoardID - } - - fieldsJSON, err := json.Marshal(block.Fields) - if err != nil { - return err - } - - existingBlock, err := s.getLegacyBlock(db, workspaceID, block.ID) - if err != nil { - return err - } - - block.UpdateAt = utils.GetMillis() - block.ModifiedBy = userID - - insertQuery := s.getQueryBuilder(db).Insert(""). - Columns( - "workspace_id", - "id", - "parent_id", - "root_id", - "created_by", - "modified_by", - s.escapeField("schema"), - "type", - "title", - "fields", - "create_at", - "update_at", - "delete_at", - ) - - insertQueryValues := map[string]interface{}{ - "workspace_id": workspaceID, - "id": block.ID, - "parent_id": block.ParentID, - "root_id": block.BoardID, - s.escapeField("schema"): block.Schema, - "type": block.Type, - "title": block.Title, - "fields": fieldsJSON, - "delete_at": block.DeleteAt, - "created_by": block.CreatedBy, - "modified_by": block.ModifiedBy, - "create_at": block.CreateAt, - "update_at": block.UpdateAt, - } - - if existingBlock != nil { - // block with ID exists, so this is an update operation - query := s.getQueryBuilder(db).Update(s.tablePrefix+"blocks"). - Where(sq.Eq{"id": block.ID}). - Where(sq.Eq{"COALESCE(workspace_id, '0')": workspaceID}). - Set("parent_id", block.ParentID). - Set("root_id", block.BoardID). - Set("modified_by", block.ModifiedBy). - Set(s.escapeField("schema"), block.Schema). - Set("type", block.Type). - Set("title", block.Title). - Set("fields", fieldsJSON). - Set("update_at", block.UpdateAt). - Set("delete_at", block.DeleteAt) - - if _, err := query.Exec(); err != nil { - s.logger.Error(`InsertBlock error occurred while updating existing block`, mlog.String("blockID", block.ID), mlog.Err(err)) - return err - } - } else { - block.CreatedBy = userID - block.CreateAt = utils.GetMillis() - - insertQueryValues["created_by"] = block.CreatedBy - insertQueryValues["create_at"] = block.CreateAt - insertQueryValues["update_at"] = block.UpdateAt - insertQueryValues["modified_by"] = block.ModifiedBy - - query := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + "blocks") - if _, err := query.Exec(); err != nil { - return err - } - } - - // writing block history - query := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + "blocks_history") - if _, err := query.Exec(); err != nil { - return err - } - - return nil -} - func (s *SQLStore) getLegacyBoardsByCondition(db sq.BaseRunner, conditions ...interface{}) ([]*model.Board, error) { return s.getBoardsFieldsByCondition(db, legacyBoardFields(""), conditions...) } diff --git a/server/services/store/sqlstore/migrate.go b/server/services/store/sqlstore/migrate.go index 294e1c00e..419865684 100644 --- a/server/services/store/sqlstore/migrate.go +++ b/server/services/store/sqlstore/migrate.go @@ -74,20 +74,18 @@ func (s *SQLStore) getMigrationConnection() (*sql.DB, error) { } func (s *SQLStore) Migrate() error { - if s.isPlugin { - mutex, mutexErr := s.NewMutexFn("Boards_dbMutex") - if mutexErr != nil { - return fmt.Errorf("error creating database mutex: %w", mutexErr) - } - - s.logger.Debug("Acquiring cluster lock for Focalboard migrations") - mutex.Lock() - defer func() { - s.logger.Debug("Releasing cluster lock for Focalboard migrations") - mutex.Unlock() - }() + mutex, mutexErr := s.NewMutexFn("Boards_dbMutex") + if mutexErr != nil { + return fmt.Errorf("error creating database mutex: %w", mutexErr) } + s.logger.Debug("Acquiring cluster lock for Focalboard migrations") + mutex.Lock() + defer func() { + s.logger.Debug("Releasing cluster lock for Focalboard migrations") + mutex.Unlock() + }() + if err := s.EnsureSchemaMigrationFormat(); err != nil { return err } @@ -148,12 +146,10 @@ func (s *SQLStore) Migrate() error { } params := map[string]interface{}{ - "prefix": s.tablePrefix, - "postgres": s.dbType == model.PostgresDBType, - "sqlite": s.dbType == model.SqliteDBType, - "mysql": s.dbType == model.MysqlDBType, - "plugin": s.isPlugin, - "singleUser": s.isSingleUser, + "prefix": s.tablePrefix, + "postgres": s.dbType == model.PostgresDBType, + "sqlite": s.dbType == model.SqliteDBType, + "mysql": s.dbType == model.MysqlDBType, } migrationAssets := &embedded.AssetSource{ diff --git a/server/services/store/sqlstore/migrationstests/boards_migrator_test.go b/server/services/store/sqlstore/migrationstests/boards_migrator_test.go index d82c6c0ad..11f601679 100644 --- a/server/services/store/sqlstore/migrationstests/boards_migrator_test.go +++ b/server/services/store/sqlstore/migrationstests/boards_migrator_test.go @@ -210,7 +210,6 @@ func (bm *BoardsMigrator) Setup() error { TablePrefix: tablePrefix, Logger: logger, DB: bm.db, - IsPlugin: bm.withMattermostMigrations, NewMutexFn: func(name string) (*cluster.Mutex, error) { return nil, fmt.Errorf("not implemented") }, diff --git a/server/services/store/sqlstore/migrationstests/helpers_test.go b/server/services/store/sqlstore/migrationstests/helpers_test.go index c4532e45c..4508828ac 100644 --- a/server/services/store/sqlstore/migrationstests/helpers_test.go +++ b/server/services/store/sqlstore/migrationstests/helpers_test.go @@ -10,9 +10,8 @@ import ( ) type TestHelper struct { - t *testing.T - f *foundation.Foundation - isPlugin bool + t *testing.T + f *foundation.Foundation } func (th *TestHelper) IsPostgres() bool { @@ -33,20 +32,19 @@ func SetupPluginTestHelper(t *testing.T) (*TestHelper, func()) { t.Skip("Skipping plugin mode test for SQLite") } - return setupTestHelper(t, true) + return setupTestHelper(t) } func SetupTestHelper(t *testing.T) (*TestHelper, func()) { - return setupTestHelper(t, false) + return setupTestHelper(t) } -func setupTestHelper(t *testing.T, isPlugin bool) (*TestHelper, func()) { - f := foundation.New(t, NewBoardsMigrator(isPlugin)) +func setupTestHelper(t *testing.T) (*TestHelper, func()) { + f := foundation.New(t, NewBoardsMigrator(true)) th := &TestHelper{ - t: t, - f: f, - isPlugin: isPlugin, + t: t, + f: f, } tearDown := func() { diff --git a/server/services/store/sqlstore/migrationstests/migration38_test.go b/server/services/store/sqlstore/migrationstests/migration38_test.go index d58f73bff..de407d6b9 100644 --- a/server/services/store/sqlstore/migrationstests/migration38_test.go +++ b/server/services/store/sqlstore/migrationstests/migration38_test.go @@ -7,11 +7,6 @@ import ( ) func Test38RemoveHiddenBoardIDsFromPreferences(t *testing.T) { - t.Run("standalone - no data exist", func(t *testing.T) { - th, tearDown := SetupTestHelper(t) - defer tearDown() - th.f.MigrateToStep(38) - }) t.Run("plugin - no data exist", func(t *testing.T) { th, tearDown := SetupTestHelper(t) @@ -19,27 +14,6 @@ func Test38RemoveHiddenBoardIDsFromPreferences(t *testing.T) { th.f.MigrateToStep(38) }) - t.Run("standalone - some data exist", func(t *testing.T) { - th, tearDown := SetupTestHelper(t) - defer tearDown() - th.f.MigrateToStep(37). - ExecFile("./fixtures/test38_add_standalone_preferences.sql") - - // verify existing data count - var count int - countQuery := "SELECT COUNT(*) FROM focalboard_preferences" - err := th.f.DB().Get(&count, countQuery) - require.NoError(t, err) - require.Equal(t, 4, count) - - th.f.MigrateToStep(38) - - // now the count should be 0 - err = th.f.DB().Get(&count, countQuery) - require.NoError(t, err) - require.Equal(t, 2, count) - }) - t.Run("plugin - some data exist", func(t *testing.T) { th, tearDown := SetupPluginTestHelper(t) defer tearDown() diff --git a/server/services/store/sqlstore/migrationstests/migration_27_test.go b/server/services/store/sqlstore/migrationstests/migration_27_test.go deleted file mode 100644 index 0047d8c59..000000000 --- a/server/services/store/sqlstore/migrationstests/migration_27_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package migrationstests - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func Test27MigrateUserPropsToPreferences(t *testing.T) { - t.Run("should correctly migrate properties on personal server and desktop", func(t *testing.T) { - th, tearDown := SetupTestHelper(t) - defer tearDown() - - th.f.MigrateToStep(26). - ExecFile("./fixtures/test27MigrateUserPropsToPreferences.sql") - - // first we check that the data was correctly loaded from the - // fixtures. We could perfectly skip this step, but as the - // failing data is in a JSON field, I preferred to leave it - // for clarity - user := struct { - ID string - Username string - Props string - }{} - - err := th.f.DB().Get(&user, "SELECT id, username, props FROM focalboard_users WHERE id = 'user-id'") - require.NoError(t, err) - userProps := map[string]any{} - require.NoError(t, json.Unmarshal([]byte(user.Props), &userProps)) - - require.Equal(t, "johndoe", user.Username) - require.Contains(t, userProps, "focalboard_welcomePageViewed") - require.True(t, userProps["focalboard_welcomePageViewed"].(bool)) - require.Contains(t, userProps, "hiddenBoardIDs") - require.ElementsMatch(t, []string{"board1", "board2"}, userProps["hiddenBoardIDs"]) - require.Contains(t, userProps, "focalboard_tourCategory") - require.Equal(t, "onboarding", userProps["focalboard_tourCategory"]) - require.Contains(t, userProps, "focalboard_onboardingTourStep") - require.Equal(t, float64(1), userProps["focalboard_onboardingTourStep"]) - require.Contains(t, userProps, "focalboard_onboardingTourStarted") - // initially, onboardingTourStarted will be false on the user, - // but already inserted in the preferences table as true. The - // migration should not overwrite the already existing value, - // so after migration #27, this value should be true - require.False(t, userProps["focalboard_onboardingTourStarted"].(bool)) - require.Contains(t, userProps, "focalboard_version72MessageCanceled") - require.True(t, userProps["focalboard_version72MessageCanceled"].(bool)) - require.Contains(t, userProps, "focalboard_lastWelcomeVersion") - require.Equal(t, float64(7), userProps["focalboard_lastWelcomeVersion"]) - - // we apply the migration - th.f.MigrateToStep(27) - - // then we load the preferences on a new struct - userPreferences := []struct { - Name string - Value string - }{} - - nErr := th.f.DB().Select(&userPreferences, "SELECT name, value FROM focalboard_preferences WHERE UserId = 'user-id'") - require.NoError(t, nErr) - - // helper function to quickly get a preference value from the - // userPreferences slice - getValue := func(name string) string { - for _, userPreference := range userPreferences { - if userPreference.Name == name { - return userPreference.Value - } - } - require.FailNow(t, "could not found preference", "while searching for name %q", name) - return "this should never be reached" - } - - // and we check that the values are correct - welcomePageViewedValue := getValue("welcomePageViewed") - // the checks for true or 1 make the test work for all DBs, - // that were representing the boolean values in the JSON - // struct in different ways - require.True(t, welcomePageViewedValue == "true" || welcomePageViewedValue == "1") - - hiddenBoardIDsValue := getValue("hiddenBoardIDs") - require.Contains(t, hiddenBoardIDsValue, "board1") - require.Contains(t, hiddenBoardIDsValue, "board2") - - require.Equal(t, "onboarding", getValue("tourCategory")) - - onboardingTourStepValue := getValue("onboardingTourStep") - require.True(t, onboardingTourStepValue == "true" || onboardingTourStepValue == "1") - - onboardingTourStartedValue := getValue("onboardingTourStarted") - require.True(t, onboardingTourStartedValue == "true" || onboardingTourStartedValue == "1") - - version72MessageCanceledValue := getValue("version72MessageCanceled") - require.True(t, version72MessageCanceledValue == "true" || version72MessageCanceledValue == "1") - - require.Equal(t, "7", getValue("lastWelcomeVersion")) - }) -} diff --git a/server/services/store/sqlstore/params.go b/server/services/store/sqlstore/params.go index eb2e38493..352ae3b38 100644 --- a/server/services/store/sqlstore/params.go +++ b/server/services/store/sqlstore/params.go @@ -22,8 +22,6 @@ type Params struct { TablePrefix string Logger mlog.LoggerIFace DB *sql.DB - IsPlugin bool - IsSingleUser bool NewMutexFn MutexFactory ServicesAPI servicesAPI SkipMigrations bool @@ -31,7 +29,7 @@ type Params struct { } func (p Params) CheckValid() error { - if p.IsPlugin && p.NewMutexFn == nil { + if p.NewMutexFn == nil { return ErrStoreParam{name: "NewMutexFn", issue: "cannot be nil in plugin mode"} } return nil diff --git a/server/services/store/sqlstore/public_methods.go b/server/services/store/sqlstore/public_methods.go index 45e1b53ca..1f4d52146 100644 --- a/server/services/store/sqlstore/public_methods.go +++ b/server/services/store/sqlstore/public_methods.go @@ -14,6 +14,7 @@ package sqlstore import ( "context" + "errors" "time" "github.com/mattermost/mattermost-plugin-boards/server/model" @@ -47,12 +48,7 @@ func (s *SQLStore) AddUpdateCategoryBoard(userID string, categoryID string, boar } func (s *SQLStore) CanSeeUser(seerID string, seenID string) (bool, error) { - return s.canSeeUser(s.db, seerID, seenID) - -} - -func (s *SQLStore) CleanUpSessions(expireTime int64) error { - return s.cleanUpSessions(s.db, expireTime) + return false, errors.New("can see user not supported in focalboard, will fetch from mattermost") } @@ -128,21 +124,11 @@ func (s *SQLStore) CreateCategory(category model.Category) error { } -func (s *SQLStore) CreateSession(session *model.Session) error { - return s.createSession(s.db, session) - -} - func (s *SQLStore) CreateSubscription(sub *model.Subscription) (*model.Subscription, error) { return s.createSubscription(s.db, sub) } -func (s *SQLStore) CreateUser(user *model.User) (*model.User, error) { - return s.createUser(s.db, user) - -} - func (s *SQLStore) DeleteBlock(blockID string, modifiedBy string) error { if s.dbType == model.SqliteDBType { return s.deleteBlock(s.db, blockID, modifiedBy) @@ -240,11 +226,6 @@ func (s *SQLStore) DeleteNotificationHint(blockID string) error { } -func (s *SQLStore) DeleteSession(sessionID string) error { - return s.deleteSession(s.db, sessionID) - -} - func (s *SQLStore) DeleteSubscription(blockID string, subscriberID string) error { return s.deleteSubscription(s.db, blockID, subscriberID) @@ -299,7 +280,7 @@ func (s *SQLStore) DuplicateBoard(boardID string, userID string, toTeam string, } func (s *SQLStore) GetActiveUserCount(updatedSecondsAgo int64) (int, error) { - return s.getActiveUserCount(s.db, updatedSecondsAgo) + return 0, errors.New("active user count not supported in focalboard when using plugin mode, will fetch from mattermost") } @@ -409,7 +390,7 @@ func (s *SQLStore) GetBoardsForCompliance(opts model.QueryBoardsForComplianceOpt } func (s *SQLStore) GetBoardsForUserAndTeam(userID string, teamID string, includePublicBoards bool) ([]*model.Board, error) { - return s.getBoardsForUserAndTeam(s.db, userID, teamID, includePublicBoards) + return nil, errors.New("boards for user and team will be fetch from mattermost not from focalboard while using in plugin mode") } @@ -429,17 +410,17 @@ func (s *SQLStore) GetCategory(id string) (*model.Category, error) { } func (s *SQLStore) GetChannel(teamID string, channelID string) (*mmModel.Channel, error) { - return s.getChannel(s.db, teamID, channelID) + return nil, errors.New("channel will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) GetFileInfo(id string) (*mmModel.FileInfo, error) { - return s.getFileInfo(s.db, id) + return nil, errors.New("file info will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) GetLicense() *mmModel.License { - return s.getLicense(s.db) + return nil } @@ -469,12 +450,7 @@ func (s *SQLStore) GetNotificationHint(blockID string) (*model.NotificationHint, } func (s *SQLStore) GetRegisteredUserCount() (int, error) { - return s.getRegisteredUserCount(s.db) - -} - -func (s *SQLStore) GetSession(token string, expireTime int64) (*model.Session, error) { - return s.getSession(s.db, token, expireTime) + return 0, errors.New("registered user count not supported in focalboard, will fetch from mattermost") } @@ -519,7 +495,7 @@ func (s *SQLStore) GetSystemSettings() (map[string]string, error) { } func (s *SQLStore) GetTeam(ID string) (*model.Team, error) { - return s.getTeam(s.db, ID) + return nil, errors.New("team will be fetch from mattermost not from focalboard while using in plugin mode") } @@ -529,7 +505,7 @@ func (s *SQLStore) GetTeamCount() (int64, error) { } func (s *SQLStore) GetTeamsForUser(userID string) ([]*model.Team, error) { - return s.getTeamsForUser(s.db, userID) + return nil, errors.New("teams for user will be fetch from mattermost not from focalboard while using in plugin mode") } @@ -544,17 +520,17 @@ func (s *SQLStore) GetUsedCardsCount() (int, error) { } func (s *SQLStore) GetUserByEmail(email string) (*model.User, error) { - return s.getUserByEmail(s.db, email) + return nil, errors.New("user email will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) GetUserByID(userID string) (*model.User, error) { - return s.getUserByID(s.db, userID) + return nil, errors.New("user id will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) GetUserByUsername(username string) (*model.User, error) { - return s.getUserByUsername(s.db, username) + return nil, errors.New("user username will be fetch from mattermost not from focalboard while using in plugin mode") } @@ -569,22 +545,22 @@ func (s *SQLStore) GetUserCategoryBoards(userID string, teamID string) ([]model. } func (s *SQLStore) GetUserPreferences(userID string) (mmModel.Preferences, error) { - return s.getUserPreferences(s.db, userID) + return nil, errors.New("user preferences will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) GetUserTimezone(userID string) (string, error) { - return s.getUserTimezone(s.db, userID) + return "", errors.New("user timezone will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) GetUsersByTeam(teamID string, asGuestID string, showEmail bool, showName bool) ([]*model.User, error) { - return s.getUsersByTeam(s.db, teamID, asGuestID, showEmail, showName) + return nil, errors.New("users by team will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) GetUsersList(userIDs []string, showEmail bool, showName bool) ([]*model.User, error) { - return s.getUsersList(s.db, userIDs, showEmail, showName) + return nil, errors.New("users list will be fetch from mattermost not from focalboard while using in plugin mode") } @@ -762,17 +738,12 @@ func (s *SQLStore) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID } func (s *SQLStore) PatchUserPreferences(userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error) { - return s.patchUserPreferences(s.db, userID, patch) + return nil, errors.New("no patch allowed from focalboard, patch it using mattermost") } func (s *SQLStore) PostMessage(message string, postType string, channelID string) error { - return s.postMessage(s.db, message, postType, channelID) - -} - -func (s *SQLStore) RefreshSession(session *model.Session) error { - return s.refreshSession(s.db, session) + return errors.New("no post message allowed from focalboard, post it using mattermost") } @@ -816,7 +787,7 @@ func (s *SQLStore) RunDataRetention(globalRetentionDate int64, batchSize int64) } func (s *SQLStore) SaveFileInfo(fileInfo *mmModel.FileInfo) error { - return s.saveFileInfo(s.db, fileInfo) + return errors.New("no save file info allowed from focalboard, save it using mattermost") } @@ -826,27 +797,27 @@ func (s *SQLStore) SaveMember(bm *model.BoardMember) (*model.BoardMember, error) } func (s *SQLStore) SearchBoardsForUser(term string, searchField model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) { - return s.searchBoardsForUser(s.db, term, searchField, userID, includePublicBoards) + return nil, errors.New("searching boards for user will be using from mattermost not from focalboard in plugin mode") } func (s *SQLStore) SearchBoardsForUserInTeam(teamID string, term string, userID string) ([]*model.Board, error) { - return s.searchBoardsForUserInTeam(s.db, teamID, term, userID) + return nil, errors.New("searching boards for user in team will be using from mattermost not from focalboard in plugin mode") } func (s *SQLStore) SearchUserChannels(teamID string, userID string, query string) ([]*mmModel.Channel, error) { - return s.searchUserChannels(s.db, teamID, userID, query) + return nil, errors.New("searching user channels will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool, showEmail bool, showName bool) ([]*model.User, error) { - return s.searchUsersByTeam(s.db, teamID, searchQuery, asGuestID, excludeBots, showEmail, showName) + return nil, errors.New("searching users by team will be fetch from mattermost not from focalboard while using in plugin mode") } func (s *SQLStore) SendMessage(message string, postType string, receipts []string) error { - return s.sendMessage(s.db, message, postType, receipts) + return errors.New("no send message allowed from focalboard, send it using mattermost") } @@ -918,31 +889,11 @@ func (s *SQLStore) UpdateCategory(category model.Category) error { } -func (s *SQLStore) UpdateSession(session *model.Session) error { - return s.updateSession(s.db, session) - -} - func (s *SQLStore) UpdateSubscribersNotifiedAt(blockID string, notifiedAt int64) error { return s.updateSubscribersNotifiedAt(s.db, blockID, notifiedAt) } -func (s *SQLStore) UpdateUser(user *model.User) (*model.User, error) { - return s.updateUser(s.db, user) - -} - -func (s *SQLStore) UpdateUserPassword(username string, password string) error { - return s.updateUserPassword(s.db, username, password) - -} - -func (s *SQLStore) UpdateUserPasswordByID(userID string, password string) error { - return s.updateUserPasswordByID(s.db, userID, password) - -} - func (s *SQLStore) UpsertNotificationHint(hint *model.NotificationHint, notificationFreq time.Duration) (*model.NotificationHint, error) { return s.upsertNotificationHint(s.db, hint, notificationFreq) diff --git a/server/services/store/sqlstore/session.go b/server/services/store/sqlstore/session.go deleted file mode 100644 index f87b68ebf..000000000 --- a/server/services/store/sqlstore/session.go +++ /dev/null @@ -1,111 +0,0 @@ -package sqlstore - -import ( - "encoding/json" - - sq "github.com/Masterminds/squirrel" - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/utils" -) - -// GetActiveUserCount returns the number of users with active sessions within N seconds ago. -func (s *SQLStore) getActiveUserCount(db sq.BaseRunner, updatedSecondsAgo int64) (int, error) { - query := s.getQueryBuilder(db). - Select("count(distinct user_id)"). - From(s.tablePrefix + "sessions"). - Where(sq.Gt{"update_at": utils.GetMillis() - utils.SecondsToMillis(updatedSecondsAgo)}) - - row := query.QueryRow() - - var count int - err := row.Scan(&count) - if err != nil { - return 0, err - } - - return count, nil -} - -func (s *SQLStore) getSession(db sq.BaseRunner, token string, expireTimeSeconds int64) (*model.Session, error) { - query := s.getQueryBuilder(db). - Select("id", "token", "user_id", "auth_service", "props"). - From(s.tablePrefix + "sessions"). - Where(sq.Eq{"token": token}). - Where(sq.Gt{"update_at": utils.GetMillis() - utils.SecondsToMillis(expireTimeSeconds)}) - - row := query.QueryRow() - session := model.Session{} - - var propsBytes []byte - err := row.Scan(&session.ID, &session.Token, &session.UserID, &session.AuthService, &propsBytes) - if err != nil { - return nil, err - } - - err = json.Unmarshal(propsBytes, &session.Props) - if err != nil { - return nil, err - } - - return &session, nil -} - -func (s *SQLStore) createSession(db sq.BaseRunner, session *model.Session) error { - now := utils.GetMillis() - - propsBytes, err := json.Marshal(session.Props) - if err != nil { - return err - } - - query := s.getQueryBuilder(db).Insert(s.tablePrefix+"sessions"). - Columns("id", "token", "user_id", "auth_service", "props", "create_at", "update_at"). - Values(session.ID, session.Token, session.UserID, session.AuthService, propsBytes, now, now) - - _, err = query.Exec() - return err -} - -func (s *SQLStore) refreshSession(db sq.BaseRunner, session *model.Session) error { - now := utils.GetMillis() - - query := s.getQueryBuilder(db).Update(s.tablePrefix+"sessions"). - Where(sq.Eq{"token": session.Token}). - Set("update_at", now) - - _, err := query.Exec() - return err -} - -func (s *SQLStore) updateSession(db sq.BaseRunner, session *model.Session) error { - now := utils.GetMillis() - - propsBytes, err := json.Marshal(session.Props) - if err != nil { - return err - } - - query := s.getQueryBuilder(db).Update(s.tablePrefix+"sessions"). - Where(sq.Eq{"token": session.Token}). - Set("update_at", now). - Set("props", propsBytes) - - _, err = query.Exec() - return err -} - -func (s *SQLStore) deleteSession(db sq.BaseRunner, sessionID string) error { - query := s.getQueryBuilder(db).Delete(s.tablePrefix + "sessions"). - Where(sq.Eq{"id": sessionID}) - - _, err := query.Exec() - return err -} - -func (s *SQLStore) cleanUpSessions(db sq.BaseRunner, expireTimeSeconds int64) error { - query := s.getQueryBuilder(db).Delete(s.tablePrefix + "sessions"). - Where(sq.Lt{"update_at": utils.GetMillis() - utils.SecondsToMillis(expireTimeSeconds)}) - - _, err := query.Exec() - return err -} diff --git a/server/services/store/sqlstore/sqlstore.go b/server/services/store/sqlstore/sqlstore.go index d9b43a35b..7c2b97272 100644 --- a/server/services/store/sqlstore/sqlstore.go +++ b/server/services/store/sqlstore/sqlstore.go @@ -2,14 +2,12 @@ package sqlstore import ( "database/sql" - "fmt" "net/url" "strings" sq "github.com/Masterminds/squirrel" "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" "github.com/mattermost/mattermost/server/public/pluginapi/cluster" mmModel "github.com/mattermost/mattermost/server/public/model" @@ -23,8 +21,6 @@ type SQLStore struct { tablePrefix string connectionString string dbPingAttempts int - isPlugin bool - isSingleUser bool logger mlog.LoggerIFace NewMutexFn MutexFactory servicesAPI servicesAPI @@ -52,8 +48,6 @@ func New(params Params) (*SQLStore, error) { tablePrefix: params.TablePrefix, connectionString: params.ConnectionString, logger: params.Logger, - isPlugin: params.IsPlugin, - isSingleUser: params.IsSingleUser, NewMutexFn: params.NewMutexFn, servicesAPI: params.ServicesAPI, configFn: params.ConfigFn, @@ -148,41 +142,6 @@ func (s *SQLStore) escapeField(fieldName string) string { //nolint:unparam return fieldName } -func (s *SQLStore) concatenationSelector(field string, delimiter string) string { - if s.dbType == model.SqliteDBType { - return fmt.Sprintf("group_concat(%s)", field) - } - if s.dbType == model.PostgresDBType { - return fmt.Sprintf("string_agg(%s, '%s')", field, delimiter) - } - if s.dbType == model.MysqlDBType { - return fmt.Sprintf("GROUP_CONCAT(%s SEPARATOR '%s')", field, delimiter) - } - return "" -} - -func (s *SQLStore) elementInColumn(column string) string { - if s.dbType == model.SqliteDBType || s.dbType == model.MysqlDBType { - return fmt.Sprintf("instr(%s, ?) > 0", column) - } - if s.dbType == model.PostgresDBType { - return fmt.Sprintf("position(? in %s) > 0", column) - } - return "" -} - -func (s *SQLStore) getLicense(db sq.BaseRunner) *mmModel.License { - return nil -} - -func (s *SQLStore) searchUserChannels(db sq.BaseRunner, teamID, userID, query string) ([]*mmModel.Channel, error) { - return nil, store.NewNotSupportedError("search user channels not supported on standalone mode") -} - -func (s *SQLStore) getChannel(db sq.BaseRunner, teamID, channel string) (*mmModel.Channel, error) { - return nil, store.NewNotSupportedError("get channel not supported on standalone mode") -} - func (s *SQLStore) DBVersion() string { var version string var row *sql.Row diff --git a/server/services/store/sqlstore/sqlstore_test.go b/server/services/store/sqlstore/sqlstore_test.go deleted file mode 100644 index cdc3f495c..000000000 --- a/server/services/store/sqlstore/sqlstore_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package sqlstore - -import ( - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/services/store/storetests" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/stretchr/testify/require" -) - -func TestSQLStore(t *testing.T) { - t.Run("BlocksStore", func(t *testing.T) { storetests.StoreTestBlocksStore(t, SetupTests) }) - t.Run("SharingStore", func(t *testing.T) { storetests.StoreTestSharingStore(t, SetupTests) }) - t.Run("SystemStore", func(t *testing.T) { storetests.StoreTestSystemStore(t, SetupTests) }) - t.Run("UserStore", func(t *testing.T) { storetests.StoreTestUserStore(t, SetupTests) }) - t.Run("SessionStore", func(t *testing.T) { storetests.StoreTestSessionStore(t, SetupTests) }) - t.Run("TeamStore", func(t *testing.T) { storetests.StoreTestTeamStore(t, SetupTests) }) - t.Run("BoardStore", func(t *testing.T) { storetests.StoreTestBoardStore(t, SetupTests) }) - t.Run("BoardsAndBlocksStore", func(t *testing.T) { storetests.StoreTestBoardsAndBlocksStore(t, SetupTests) }) - t.Run("SubscriptionStore", func(t *testing.T) { storetests.StoreTestSubscriptionsStore(t, SetupTests) }) - t.Run("NotificationHintStore", func(t *testing.T) { storetests.StoreTestNotificationHintsStore(t, SetupTests) }) - t.Run("DataRetention", func(t *testing.T) { storetests.StoreTestDataRetention(t, SetupTests) }) - t.Run("CloudStore", func(t *testing.T) { storetests.StoreTestCloudStore(t, SetupTests) }) - t.Run("StoreTestFileStore", func(t *testing.T) { storetests.StoreTestFileStore(t, SetupTests) }) - t.Run("StoreTestCategoryStore", func(t *testing.T) { storetests.StoreTestCategoryStore(t, SetupTests) }) - t.Run("StoreTestCategoryBoardsStore", func(t *testing.T) { storetests.StoreTestCategoryBoardsStore(t, SetupTests) }) - t.Run("ComplianceHistoryStore", func(t *testing.T) { storetests.StoreTestComplianceHistoryStore(t, SetupTests) }) -} - -// tests for utility functions inside sqlstore.go - -func TestConcatenationSelector(t *testing.T) { - store, tearDown := SetupTests(t) - sqlStore := store.(*SQLStore) - defer tearDown() - - concatenationString := sqlStore.concatenationSelector("a", ",") - switch sqlStore.dbType { - case model.SqliteDBType: - require.Equal(t, concatenationString, "group_concat(a)") - case model.MysqlDBType: - require.Equal(t, concatenationString, "GROUP_CONCAT(a SEPARATOR ',')") - case model.PostgresDBType: - require.Equal(t, concatenationString, "string_agg(a, ',')") - } -} - -func TestElementInColumn(t *testing.T) { - store, _ := SetupTests(t) - sqlStore := store.(*SQLStore) - - inLiteral := sqlStore.elementInColumn("test_column") - switch sqlStore.dbType { - case model.SqliteDBType: - require.Equal(t, inLiteral, "instr(test_column, ?) > 0") - case model.MysqlDBType: - require.Equal(t, inLiteral, "instr(test_column, ?) > 0") - case model.PostgresDBType: - require.Equal(t, inLiteral, "position(? in test_column) > 0") - } -} diff --git a/server/services/store/sqlstore/team.go b/server/services/store/sqlstore/team.go index 3702d04cb..7a6c8d597 100644 --- a/server/services/store/sqlstore/team.go +++ b/server/services/store/sqlstore/team.go @@ -91,46 +91,6 @@ func (s *SQLStore) upsertTeamSettings(db sq.BaseRunner, team model.Team) error { return err } -func (s *SQLStore) getTeam(db sq.BaseRunner, id string) (*model.Team, error) { - var settingsJSON string - - query := s.getQueryBuilder(db). - Select( - "id", - "signup_token", - "COALESCE(settings, '{}')", - "modified_by", - "update_at", - ). - From(s.tablePrefix + "teams"). - Where(sq.Eq{"id": id}) - row := query.QueryRow() - team := model.Team{} - - err := row.Scan( - &team.ID, - &team.SignupToken, - &settingsJSON, - &team.ModifiedBy, - &team.UpdateAt, - ) - if err != nil { - return nil, err - } - - err = json.Unmarshal([]byte(settingsJSON), &team.Settings) - if err != nil { - s.logger.Error(`ERROR GetTeam settings json.Unmarshal`, mlog.Err(err)) - return nil, err - } - - return &team, nil -} - -func (s *SQLStore) getTeamsForUser(db sq.BaseRunner, _ string) ([]*model.Team, error) { - return s.getAllTeams(db) -} - func (s *SQLStore) getTeamCount(db sq.BaseRunner) (int64, error) { query := s.getQueryBuilder(db). Select( diff --git a/server/services/store/sqlstore/user.go b/server/services/store/sqlstore/user.go index 849d81917..ef5e747fd 100644 --- a/server/services/store/sqlstore/user.go +++ b/server/services/store/sqlstore/user.go @@ -1,23 +1,7 @@ package sqlstore import ( - "database/sql" - "errors" "fmt" - - mmModel "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/v8/channels/store" - - sq "github.com/Masterminds/squirrel" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "github.com/mattermost/mattermost/server/public/shared/mlog" -) - -var ( - errUnsupportedOperation = errors.New("unsupported operation") ) type UserNotFoundError struct { @@ -27,393 +11,3 @@ type UserNotFoundError struct { func (unf UserNotFoundError) Error() string { return fmt.Sprintf("user not found (%s)", unf.id) } - -func (s *SQLStore) getRegisteredUserCount(db sq.BaseRunner) (int, error) { - query := s.getQueryBuilder(db). - Select("count(*)"). - From(s.tablePrefix + "users"). - Where(sq.Eq{"delete_at": 0}) - row := query.QueryRow() - - var count int - err := row.Scan(&count) - if err != nil { - return 0, err - } - - return count, nil -} - -func (s *SQLStore) getUserByCondition(db sq.BaseRunner, condition sq.Eq) (*model.User, error) { - users, err := s.getUsersByCondition(db, condition, 0) - if err != nil { - return nil, err - } - - if len(users) == 0 { - return nil, model.NewErrNotFound("user") - } - - return users[0], nil -} - -func (s *SQLStore) getUsersByCondition(db sq.BaseRunner, condition interface{}, limit uint64) ([]*model.User, error) { - query := s.getQueryBuilder(db). - Select( - "id", - "username", - "email", - "password", - "mfa_secret", - "auth_service", - "auth_data", - "create_at", - "update_at", - "delete_at", - ). - From(s.tablePrefix + "users"). - Where(sq.Eq{"delete_at": 0}). - Where(condition) - - if limit != 0 { - query = query.Limit(limit) - } - - rows, err := query.Query() - if err != nil { - s.logger.Error(`getUsersByCondition ERROR`, mlog.Err(err)) - return nil, err - } - defer s.CloseRows(rows) - - users, err := s.usersFromRows(rows) - if err != nil { - return nil, err - } - - if len(users) == 0 { - return nil, model.NewErrNotFound("user") - } - - return users, nil -} - -func (s *SQLStore) getUserByID(db sq.BaseRunner, userID string) (*model.User, error) { - return s.getUserByCondition(db, sq.Eq{"id": userID}) -} - -func (s *SQLStore) getUsersList(db sq.BaseRunner, userIDs []string, _, _ bool) ([]*model.User, error) { - users, err := s.getUsersByCondition(db, sq.Eq{"id": userIDs}, 0) - if err != nil { - return nil, err - } - - if len(users) != len(userIDs) { - return users, model.NewErrNotAllFound("user", userIDs) - } - - return users, nil -} - -func (s *SQLStore) getUserByEmail(db sq.BaseRunner, email string) (*model.User, error) { - return s.getUserByCondition(db, sq.Eq{"email": email}) -} - -func (s *SQLStore) getUserByUsername(db sq.BaseRunner, username string) (*model.User, error) { - return s.getUserByCondition(db, sq.Eq{"username": username}) -} - -func (s *SQLStore) createUser(db sq.BaseRunner, user *model.User) (*model.User, error) { - now := utils.GetMillis() - user.CreateAt = now - user.UpdateAt = now - user.DeleteAt = 0 - - query := s.getQueryBuilder(db).Insert(s.tablePrefix+"users"). - Columns("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "create_at", "update_at", "delete_at"). - Values(user.ID, user.Username, user.Email, user.Password, user.MfaSecret, user.AuthService, user.AuthData, user.CreateAt, user.UpdateAt, user.DeleteAt) - - _, err := query.Exec() - return user, err -} - -func (s *SQLStore) updateUser(db sq.BaseRunner, user *model.User) (*model.User, error) { - now := utils.GetMillis() - user.UpdateAt = now - - query := s.getQueryBuilder(db).Update(s.tablePrefix+"users"). - Set("username", user.Username). - Set("email", user.Email). - Set("update_at", user.UpdateAt). - Where(sq.Eq{"id": user.ID}) - - result, err := query.Exec() - if err != nil { - return nil, err - } - - rowCount, err := result.RowsAffected() - if err != nil { - return nil, err - } - - if rowCount < 1 { - return nil, UserNotFoundError{user.ID} - } - - return user, nil -} - -func (s *SQLStore) updateUserPassword(db sq.BaseRunner, username, password string) error { - now := utils.GetMillis() - - query := s.getQueryBuilder(db).Update(s.tablePrefix+"users"). - Set("password", password). - Set("update_at", now). - Where(sq.Eq{"username": username}) - - result, err := query.Exec() - if err != nil { - return err - } - - rowCount, err := result.RowsAffected() - if err != nil { - return err - } - - if rowCount < 1 { - return UserNotFoundError{username} - } - - return nil -} - -func (s *SQLStore) updateUserPasswordByID(db sq.BaseRunner, userID, password string) error { - now := utils.GetMillis() - - query := s.getQueryBuilder(db).Update(s.tablePrefix+"users"). - Set("password", password). - Set("update_at", now). - Where(sq.Eq{"id": userID}) - - result, err := query.Exec() - if err != nil { - return err - } - - rowCount, err := result.RowsAffected() - if err != nil { - return err - } - - if rowCount < 1 { - return UserNotFoundError{userID} - } - - return nil -} - -func (s *SQLStore) getUsersByTeam(db sq.BaseRunner, _ string, _ string, _, _ bool) ([]*model.User, error) { - users, err := s.getUsersByCondition(db, nil, 0) - if model.IsErrNotFound(err) { - return []*model.User{}, nil - } - - return users, err -} - -func (s *SQLStore) searchUsersByTeam(db sq.BaseRunner, _ string, searchQuery string, _ string, _, _, _ bool) ([]*model.User, error) { - users, err := s.getUsersByCondition(db, &sq.Like{"username": "%" + searchQuery + "%"}, 10) - if model.IsErrNotFound(err) { - return []*model.User{}, nil - } - - return users, err -} - -func (s *SQLStore) usersFromRows(rows *sql.Rows) ([]*model.User, error) { - users := []*model.User{} - - for rows.Next() { - var user model.User - - err := rows.Scan( - &user.ID, - &user.Username, - &user.Email, - &user.Password, - &user.MfaSecret, - &user.AuthService, - &user.AuthData, - &user.CreateAt, - &user.UpdateAt, - &user.DeleteAt, - ) - if err != nil { - return nil, err - } - - users = append(users, &user) - } - - return users, nil -} - -func (s *SQLStore) patchUserPreferences(db sq.BaseRunner, userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error) { - preferences, err := s.getUserPreferences(db, userID) - if err != nil { - return nil, err - } - - if len(patch.UpdatedFields) > 0 { - for key, value := range patch.UpdatedFields { - preference := mmModel.Preference{ - UserId: userID, - Category: model.PreferencesCategoryFocalboard, - Name: key, - Value: value, - } - - if err := s.updateUserPreference(db, preference); err != nil { - return nil, err - } - - newPreferences := mmModel.Preferences{} - for _, existingPreference := range preferences { - if preference.Name != existingPreference.Name { - newPreferences = append(newPreferences, existingPreference) - } - } - newPreferences = append(newPreferences, preference) - preferences = newPreferences - } - } - - if len(patch.DeletedFields) > 0 { - for _, key := range patch.DeletedFields { - preference := mmModel.Preference{ - UserId: userID, - Category: model.PreferencesCategoryFocalboard, - Name: key, - } - - if err := s.deleteUserPreference(db, preference); err != nil { - return nil, err - } - - newPreferences := mmModel.Preferences{} - for _, existingPreference := range preferences { - if preference.Name != existingPreference.Name { - newPreferences = append(newPreferences, existingPreference) - } - } - preferences = newPreferences - } - } - - return preferences, nil -} - -func (s *SQLStore) updateUserPreference(db sq.BaseRunner, preference mmModel.Preference) error { - query := s.getQueryBuilder(db). - Insert(s.tablePrefix+"preferences"). - Columns("UserId", "Category", "Name", "Value"). - Values(preference.UserId, preference.Category, preference.Name, preference.Value) - - switch s.dbType { - case model.MysqlDBType: - query = query.SuffixExpr(sq.Expr("ON DUPLICATE KEY UPDATE Value = ?", preference.Value)) - case model.PostgresDBType: - query = query.SuffixExpr(sq.Expr("ON CONFLICT (userid, category, name) DO UPDATE SET Value = ?", preference.Value)) - case model.SqliteDBType: - query = query.SuffixExpr(sq.Expr(" on conflict(userid, category, name) do update set value = excluded.value")) - default: - return store.NewErrNotImplemented("failed to update preference because of missing driver") - } - - if _, err := query.Exec(); err != nil { - return fmt.Errorf("failed to upsert user preference in database: userID: %s name: %s value: %s error: %w", preference.UserId, preference.Name, preference.Value, err) - } - - return nil -} - -func (s *SQLStore) deleteUserPreference(db sq.BaseRunner, preference mmModel.Preference) error { - query := s.getQueryBuilder(db). - Delete(s.tablePrefix + "preferences"). - Where(sq.Eq{"UserId": preference.UserId}). - Where(sq.Eq{"Category": preference.Category}). - Where(sq.Eq{"Name": preference.Name}) - - if _, err := query.Exec(); err != nil { - return fmt.Errorf("failed to delete user preference from database: %w", err) - } - - return nil -} - -func (s *SQLStore) canSeeUser(db sq.BaseRunner, seerID string, seenID string) (bool, error) { - return true, nil -} - -func (s *SQLStore) sendMessage(db sq.BaseRunner, message, postType string, receipts []string) error { - return errUnsupportedOperation -} - -func (s *SQLStore) postMessage(db sq.BaseRunner, message, postType string, channel string) error { - return errUnsupportedOperation -} - -func (s *SQLStore) getUserTimezone(_ sq.BaseRunner, _ string) (string, error) { - return "", errUnsupportedOperation -} - -func (s *SQLStore) getUserPreferences(db sq.BaseRunner, userID string) (mmModel.Preferences, error) { - query := s.getQueryBuilder(db). - Select("userid", "category", "name", "value"). - From(s.tablePrefix + "preferences"). - Where(sq.Eq{ - "userid": userID, - "category": model.PreferencesCategoryFocalboard, - }) - - rows, err := query.Query() - if err != nil { - s.logger.Error("failed to fetch user preferences", mlog.String("user_id", userID), mlog.Err(err)) - return nil, err - } - - defer rows.Close() - - preferences, err := s.preferencesFromRows(rows) - if err != nil { - return nil, err - } - - return preferences, nil -} - -func (s *SQLStore) preferencesFromRows(rows *sql.Rows) ([]mmModel.Preference, error) { - preferences := []mmModel.Preference{} - - for rows.Next() { - var preference mmModel.Preference - - err := rows.Scan( - &preference.UserId, - &preference.Category, - &preference.Name, - &preference.Value, - ) - - if err != nil { - s.logger.Error("failed to scan row for user preference", mlog.Err(err)) - return nil, err - } - - preferences = append(preferences, preference) - } - - return preferences, nil -} diff --git a/server/services/store/store.go b/server/services/store/store.go index 5109ea834..a51741835 100644 --- a/server/services/store/store.go +++ b/server/services/store/store.go @@ -60,22 +60,12 @@ type Store interface { GetUsersList(userIDs []string, showEmail, showName bool) ([]*model.User, error) GetUserByEmail(email string) (*model.User, error) GetUserByUsername(username string) (*model.User, error) - CreateUser(user *model.User) (*model.User, error) - UpdateUser(user *model.User) (*model.User, error) - UpdateUserPassword(username, password string) error - UpdateUserPasswordByID(userID, password string) error GetUsersByTeam(teamID string, asGuestID string, showEmail, showName bool) ([]*model.User, error) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool, showEmail, showName bool) ([]*model.User, error) PatchUserPreferences(userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error) GetUserPreferences(userID string) (mmModel.Preferences, error) GetActiveUserCount(updatedSecondsAgo int64) (int, error) - GetSession(token string, expireTime int64) (*model.Session, error) - CreateSession(session *model.Session) error - RefreshSession(session *model.Session) error - UpdateSession(session *model.Session) error - DeleteSession(sessionID string) error - CleanUpSessions(expireTime int64) error UpsertSharing(sharing model.Sharing) error GetSharing(rootID string) (*model.Sharing, error) diff --git a/server/services/store/storetests/blocks.go b/server/services/store/storetests/blocks.go deleted file mode 100644 index 1b5ab9e65..000000000 --- a/server/services/store/storetests/blocks.go +++ /dev/null @@ -1,1349 +0,0 @@ -package storetests - -import ( - "math" - "strconv" - "strings" - "testing" - "time" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - testUserID = "user-id" - testTeamID = "team-id" - testBoardID = "board-id" -) - -func StoreTestBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("InsertBlock", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testInsertBlock(t, store) - }) - t.Run("InsertBlocks", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testInsertBlocks(t, store) - }) - t.Run("PatchBlock", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testPatchBlock(t, store) - }) - t.Run("PatchBlocks", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testPatchBlocks(t, store) - }) - t.Run("DeleteBlock", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDeleteBlock(t, store) - }) - t.Run("UndeleteBlock", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUndeleteBlock(t, store) - }) - t.Run("GetSubTree2", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetSubTree2(t, store) - }) - t.Run("GetBlocks", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBlocks(t, store) - }) - t.Run("GetBlock", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBlock(t, store) - }) - t.Run("DuplicateBlock", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDuplicateBlock(t, store) - }) - t.Run("GetBlockMetadata", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBlockMetadata(t, store) - }) - t.Run("UndeleteBlockChildren", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUndeleteBlockChildren(t, store) - }) - t.Run("GetBlockHistoryNewestChildren", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBlockHistoryNewestChildren(t, store) - }) -} - -func testInsertBlock(t *testing.T, store store.Store) { - userID := testUserID - boardID := testBoardID - - blocks, errBlocks := store.GetBlocksForBoard(boardID) - require.NoError(t, errBlocks) - initialCount := len(blocks) - - t.Run("valid block", func(t *testing.T) { - block := &model.Block{ - ID: "id-test", - BoardID: boardID, - ModifiedBy: userID, - } - - err := store.InsertBlock(block, "user-id-1") - require.NoError(t, err) - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount+1) - }) - - t.Run("invalid rootid", func(t *testing.T) { - block := &model.Block{ - ID: "id-test", - BoardID: "", - ModifiedBy: userID, - } - - err := store.InsertBlock(block, "user-id-1") - require.Error(t, err) - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount+1) - }) - - t.Run("invalid fields data", func(t *testing.T) { - block := &model.Block{ - ID: "id-test", - BoardID: "id-test", - ModifiedBy: userID, - Fields: map[string]interface{}{"no-serialiable-value": t.Run}, - } - - err := store.InsertBlock(block, "user-id-1") - require.Error(t, err) - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount+1) - }) - - t.Run("block with title too large", func(t *testing.T) { - block := &model.Block{ - ID: "id-test", - BoardID: boardID, - ModifiedBy: userID, - Title: strings.Repeat("A", model.BlockTitleMaxRunes+1), - } - - err := store.InsertBlock(block, "user-id-1") - require.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded) - }) - - t.Run("block with aggregated fields size too large", func(t *testing.T) { - block := &model.Block{ - ID: "id-test", - BoardID: boardID, - ModifiedBy: userID, - Fields: map[string]any{ - "one": strings.Repeat("1", model.BlockFieldsMaxRunes/4), - "two": strings.Repeat("2", model.BlockFieldsMaxRunes/4), - "three": strings.Repeat("3", model.BlockFieldsMaxRunes/4), - "four": strings.Repeat("4", model.BlockFieldsMaxRunes/4), - }, - } - - err := store.InsertBlock(block, "user-id-2") - require.ErrorIs(t, err, model.ErrBlockFieldsSizeLimitExceeded) - }) - - t.Run("insert new block", func(t *testing.T) { - block := &model.Block{ - BoardID: testBoardID, - } - - err := store.InsertBlock(block, "user-id-2") - require.NoError(t, err) - require.Equal(t, "user-id-2", block.CreatedBy) - }) - - t.Run("update existing block", func(t *testing.T) { - block := &model.Block{ - ID: "id-2", - BoardID: "board-id-1", - Title: "Old Title", - } - - // inserting - err := store.InsertBlock(block, "user-id-2") - require.NoError(t, err) - - // created by populated from user id for new blocks - require.Equal(t, "user-id-2", block.CreatedBy) - - // hack to avoid multiple, quick updates to a card - // violating block_history composite primary key constraint - time.Sleep(1 * time.Millisecond) - - // updating - newBlock := &model.Block{ - ID: "id-2", - BoardID: "board-id-1", - CreatedBy: "user-id-3", - Title: "New Title", - } - err = store.InsertBlock(newBlock, "user-id-4") - require.NoError(t, err) - // created by is not altered for existing blocks - require.Equal(t, "user-id-3", newBlock.CreatedBy) - require.Equal(t, "New Title", newBlock.Title) - }) - - t.Run("update existing block with title too large", func(t *testing.T) { - block := &model.Block{ - ID: "id-3", - BoardID: "board-id-1", - CreatedBy: "user-id-3", - Title: "New Title", - } - - // inserting - err := store.InsertBlock(block, "user-id-3") - require.NoError(t, err) - - // created by populated from user id for new blocks - require.Equal(t, "user-id-3", block.CreatedBy) - - // hack to avoid multiple, quick updates to a card - // violating block_history composite primary key constraint - time.Sleep(1 * time.Millisecond) - - // updating - newBlock := &model.Block{ - ID: "id-3", - BoardID: "board-id-1", - CreatedBy: "user-id-3", - Title: strings.Repeat("A", model.BlockTitleMaxRunes+1), - } - err = store.InsertBlock(newBlock, "user-id-3") - require.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded) - }) - - t.Run("update existing block with aggregated fields size too large", func(t *testing.T) { - block := &model.Block{ - ID: "id-3", - BoardID: "board-id-1", - CreatedBy: "user-id-3", - Title: "New Title", - } - - // inserting - err := store.InsertBlock(block, "user-id-3") - require.NoError(t, err) - - // created by populated from user id for new blocks - require.Equal(t, "user-id-3", block.CreatedBy) - - // hack to avoid multiple, quick updates to a card - // violating block_history composite primary key constraint - time.Sleep(1 * time.Millisecond) - - // updating - newBlock := &model.Block{ - ID: "id-3", - BoardID: "board-id-1", - CreatedBy: "user-id-3", - Fields: map[string]any{ - "one": strings.Repeat("1", model.BlockFieldsMaxRunes/4), - "two": strings.Repeat("2", model.BlockFieldsMaxRunes/4), - "three": strings.Repeat("3", model.BlockFieldsMaxRunes/4), - "four": strings.Repeat("4", model.BlockFieldsMaxRunes/4), - }, - } - err = store.InsertBlock(newBlock, "user-id-3") - require.ErrorIs(t, err, model.ErrBlockFieldsSizeLimitExceeded) - }) - - createdAt, err := time.Parse(time.RFC822, "01 Jan 90 01:00 IST") - assert.NoError(t, err) - - updateAt, err := time.Parse(time.RFC822, "02 Jan 90 01:00 IST") - assert.NoError(t, err) - - t.Run("data tamper attempt", func(t *testing.T) { - block := &model.Block{ - ID: "id-10", - BoardID: "board-id-1", - Title: "Old Title", - CreateAt: utils.GetMillisForTime(createdAt), - UpdateAt: utils.GetMillisForTime(updateAt), - CreatedBy: "user-id-5", - ModifiedBy: "user-id-6", - } - - // inserting - err := store.InsertBlock(block, "user-id-1") - require.NoError(t, err) - expectedTime := time.Now() - - retrievedBlock, err := store.GetBlock("id-10") - assert.NoError(t, err) - assert.NotNil(t, retrievedBlock) - assert.Equal(t, "board-id-1", retrievedBlock.BoardID) - assert.Equal(t, "user-id-1", retrievedBlock.CreatedBy) - assert.Equal(t, "user-id-1", retrievedBlock.ModifiedBy) - assert.WithinDurationf(t, expectedTime, utils.GetTimeForMillis(retrievedBlock.CreateAt), 1*time.Second, "create time should be current time") - assert.WithinDurationf(t, expectedTime, utils.GetTimeForMillis(retrievedBlock.UpdateAt), 1*time.Second, "update time should be current time") - }) -} - -func testInsertBlocks(t *testing.T, store store.Store) { - userID := testUserID - - blocks, errBlocks := store.GetBlocksForBoard("id-test") - require.NoError(t, errBlocks) - initialCount := len(blocks) - - t.Run("invalid block", func(t *testing.T) { - validBlock := &model.Block{ - ID: "id-test", - BoardID: "id-test", - ModifiedBy: userID, - } - - invalidBlock := &model.Block{ - ID: "id-test", - BoardID: "", - ModifiedBy: userID, - } - - newBlocks := []*model.Block{validBlock, invalidBlock} - - time.Sleep(1 * time.Millisecond) - err := store.InsertBlocks(newBlocks, "user-id-1") - require.Error(t, err) - - blocks, err := store.GetBlocksForBoard("id-test") - require.NoError(t, err) - // no blocks should have been inserted - require.Len(t, blocks, initialCount) - }) -} - -func testPatchBlock(t *testing.T, store store.Store) { - userID := testUserID - boardID := "board-id-1" - - block := &model.Block{ - ID: "id-test", - BoardID: boardID, - Title: "oldTitle", - ModifiedBy: userID, - Fields: map[string]interface{}{"test": "test value", "test2": "test value 2"}, - } - - err := store.InsertBlock(block, "user-id-1") - require.NoError(t, err) - - blocks, errBlocks := store.GetBlocksForBoard(boardID) - require.NoError(t, errBlocks) - initialCount := len(blocks) - - t.Run("not existing block id", func(t *testing.T) { - err := store.PatchBlock("invalid-block-id", &model.BlockPatch{}, "user-id-1") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.True(t, model.IsErrNotFound(err)) - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount) - }) - - t.Run("invalid fields data", func(t *testing.T) { - blockPatch := &model.BlockPatch{ - UpdatedFields: map[string]interface{}{"no-serialiable-value": t.Run}, - } - - err := store.PatchBlock("id-test", blockPatch, "user-id-1") - require.Error(t, err) - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount) - }) - - t.Run("update block fields", func(t *testing.T) { - newTitle := "New title" - blockPatch := model.BlockPatch{ - Title: &newTitle, - } - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - - // inserting - err := store.PatchBlock("id-test", &blockPatch, "user-id-2") - require.NoError(t, err) - - retrievedBlock, err := store.GetBlock("id-test") - require.NoError(t, err) - - // created by populated from user id for new blocks - require.Equal(t, "user-id-2", retrievedBlock.ModifiedBy) - require.Equal(t, "New title", retrievedBlock.Title) - }) - - t.Run("update block custom fields", func(t *testing.T) { - blockPatch := &model.BlockPatch{ - UpdatedFields: map[string]interface{}{"test": "new test value", "test3": "new value"}, - } - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - - // inserting - err := store.PatchBlock("id-test", blockPatch, "user-id-2") - require.NoError(t, err) - - retrievedBlock, err := store.GetBlock("id-test") - require.NoError(t, err) - - // created by populated from user id for new blocks - require.Equal(t, "user-id-2", retrievedBlock.ModifiedBy) - require.Equal(t, "new test value", retrievedBlock.Fields["test"]) - require.Equal(t, "test value 2", retrievedBlock.Fields["test2"]) - require.Equal(t, "new value", retrievedBlock.Fields["test3"]) - }) - - t.Run("remove block custom fields", func(t *testing.T) { - blockPatch := &model.BlockPatch{ - DeletedFields: []string{"test", "test3", "test100"}, - } - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - - // inserting - err := store.PatchBlock("id-test", blockPatch, "user-id-2") - require.NoError(t, err) - - retrievedBlock, err := store.GetBlock("id-test") - require.NoError(t, err) - - // created by populated from user id for new blocks - require.Equal(t, "user-id-2", retrievedBlock.ModifiedBy) - require.Equal(t, nil, retrievedBlock.Fields["test"]) - require.Equal(t, "test value 2", retrievedBlock.Fields["test2"]) - require.Equal(t, nil, retrievedBlock.Fields["test3"]) - }) -} - -func testPatchBlocks(t *testing.T, store store.Store) { - block := &model.Block{ - ID: "id-test", - BoardID: "id-test", - Title: "oldTitle", - } - - block2 := &model.Block{ - ID: "id-test2", - BoardID: "id-test2", - Title: "oldTitle2", - } - - insertBlocks := []*model.Block{block, block2} - err := store.InsertBlocks(insertBlocks, "user-id-1") - require.NoError(t, err) - - t.Run("successful updated existing blocks", func(t *testing.T) { - title := "updatedTitle" - blockPatch := model.BlockPatch{ - Title: &title, - } - - blockPatch2 := model.BlockPatch{ - Title: &title, - } - - blockIds := []string{"id-test", "id-test2"} - blockPatches := []model.BlockPatch{blockPatch, blockPatch2} - - time.Sleep(1 * time.Millisecond) - err := store.PatchBlocks(&model.BlockPatchBatch{BlockIDs: blockIds, BlockPatches: blockPatches}, "user-id-1") - require.NoError(t, err) - - retrievedBlock, err := store.GetBlock("id-test") - require.NoError(t, err) - require.Equal(t, title, retrievedBlock.Title) - - retrievedBlock2, err := store.GetBlock("id-test2") - require.NoError(t, err) - require.Equal(t, title, retrievedBlock2.Title) - }) - - t.Run("invalid block id, nothing updated existing blocks", func(t *testing.T) { - if store.DBType() == model.SqliteDBType { - t.Skip("No transactions support int sqlite") - } - - title := "Another Title" - blockPatch := model.BlockPatch{ - Title: &title, - } - - blockPatch2 := model.BlockPatch{ - Title: &title, - } - - blockIds := []string{"id-test", "invalid id"} - blockPatches := []model.BlockPatch{blockPatch, blockPatch2} - - time.Sleep(1 * time.Millisecond) - err := store.PatchBlocks(&model.BlockPatchBatch{BlockIDs: blockIds, BlockPatches: blockPatches}, "user-id-1") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - - retrievedBlock, err := store.GetBlock("id-test") - require.NoError(t, err) - require.NotEqual(t, title, retrievedBlock.Title) - }) -} - -var ( - subtreeSampleBlocks = []*model.Block{ - { - ID: "parent", - BoardID: testBoardID, - ModifiedBy: testUserID, - }, - { - ID: "child1", - BoardID: testBoardID, - ParentID: "parent", - ModifiedBy: testUserID, - }, - { - ID: "child2", - BoardID: testBoardID, - ParentID: "parent", - ModifiedBy: testUserID, - }, - { - ID: "grandchild1", - BoardID: testBoardID, - ParentID: "child1", - ModifiedBy: testUserID, - }, - { - ID: "grandchild2", - BoardID: testBoardID, - ParentID: "child2", - ModifiedBy: testUserID, - }, - { - ID: "greatgrandchild1", - BoardID: testBoardID, - ParentID: "grandchild1", - ModifiedBy: testUserID, - }, - } -) - -func testGetSubTree2(t *testing.T, store store.Store) { - boardID := testBoardID - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - initialCount := len(blocks) - - InsertBlocks(t, store, subtreeSampleBlocks, "user-id-1") - time.Sleep(1 * time.Millisecond) - defer DeleteBlocks(t, store, subtreeSampleBlocks, "test") - - blocks, err = store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount+6) - - t.Run("from root id", func(t *testing.T) { - blocks, err = store.GetSubTree2(boardID, "parent", model.QuerySubtreeOptions{}) - require.NoError(t, err) - require.Len(t, blocks, 3) - require.True(t, ContainsBlockWithID(blocks, "parent")) - require.True(t, ContainsBlockWithID(blocks, "child1")) - require.True(t, ContainsBlockWithID(blocks, "child2")) - }) - - t.Run("from child id", func(t *testing.T) { - blocks, err = store.GetSubTree2(boardID, "child1", model.QuerySubtreeOptions{}) - require.NoError(t, err) - require.Len(t, blocks, 2) - require.True(t, ContainsBlockWithID(blocks, "child1")) - require.True(t, ContainsBlockWithID(blocks, "grandchild1")) - }) - - t.Run("from not existing id", func(t *testing.T) { - blocks, err = store.GetSubTree2(boardID, "not-exists", model.QuerySubtreeOptions{}) - require.NoError(t, err) - require.Empty(t, blocks) - }) -} - -func testDeleteBlock(t *testing.T, store store.Store) { - userID := testUserID - boardID := testBoardID - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - initialCount := len(blocks) - - blocksToInsert := []*model.Block{ - { - ID: "block1", - BoardID: boardID, - ModifiedBy: userID, - }, - { - ID: "block2", - BoardID: boardID, - ModifiedBy: userID, - }, - { - ID: "block3", - BoardID: boardID, - ModifiedBy: userID, - }, - } - InsertBlocks(t, store, blocksToInsert, "user-id-1") - defer DeleteBlocks(t, store, blocksToInsert, "test") - - blocks, err = store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount+3) - - t.Run("existing id", func(t *testing.T) { - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err := store.DeleteBlock("block1", userID) - require.NoError(t, err) - }) - - t.Run("existing id multiple times", func(t *testing.T) { - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err := store.DeleteBlock("block1", userID) - require.NoError(t, err) - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err = store.DeleteBlock("block1", userID) - require.NoError(t, err) - }) - - t.Run("from not existing id", func(t *testing.T) { - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err := store.DeleteBlock("not-exists", userID) - require.NoError(t, err) - }) -} - -func testUndeleteBlock(t *testing.T, store store.Store) { - boardID := testBoardID - userID := testUserID - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - initialCount := len(blocks) - - blocksToInsert := []*model.Block{ - { - ID: "block1", - BoardID: boardID, - ModifiedBy: userID, - }, - { - ID: "block2", - BoardID: boardID, - ModifiedBy: userID, - }, - { - ID: "block3", - BoardID: boardID, - ModifiedBy: userID, - }, - } - InsertBlocks(t, store, blocksToInsert, "user-id-1") - defer DeleteBlocks(t, store, blocksToInsert, "test") - - blocks, err = store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, initialCount+3) - - t.Run("existing id", func(t *testing.T) { - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err := store.DeleteBlock("block1", userID) - require.NoError(t, err) - - block, err := store.GetBlock("block1") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, block) - - time.Sleep(1 * time.Millisecond) - err = store.UndeleteBlock("block1", userID) - require.NoError(t, err) - - block, err = store.GetBlock("block1") - require.NoError(t, err) - require.NotNil(t, block) - }) - - t.Run("existing id multiple times", func(t *testing.T) { - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err := store.DeleteBlock("block1", userID) - require.NoError(t, err) - - block, err := store.GetBlock("block1") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, block) - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err = store.UndeleteBlock("block1", userID) - require.NoError(t, err) - - block, err = store.GetBlock("block1") - require.NoError(t, err) - require.NotNil(t, block) - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err = store.UndeleteBlock("block1", userID) - require.NoError(t, err) - - block, err = store.GetBlock("block1") - require.NoError(t, err) - require.NotNil(t, block) - }) - - t.Run("from not existing id", func(t *testing.T) { - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err := store.UndeleteBlock("not-exists", userID) - require.NoError(t, err) - - block, err := store.GetBlock("not-exists") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, block) - }) -} - -func testGetBlocks(t *testing.T, store store.Store) { - boardID := testBoardID - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - - blocksToInsert := []*model.Block{ - { - ID: "block1", - BoardID: boardID, - ParentID: "", - ModifiedBy: testUserID, - Type: "test", - }, - { - ID: "block2", - BoardID: boardID, - ParentID: "block1", - ModifiedBy: testUserID, - Type: "test", - }, - { - ID: "block3", - BoardID: boardID, - ParentID: "block1", - ModifiedBy: testUserID, - Type: "test", - }, - { - ID: "block4", - BoardID: boardID, - ParentID: "block1", - ModifiedBy: testUserID, - Type: "test2", - }, - { - ID: "block5", - BoardID: boardID, - ParentID: "block2", - ModifiedBy: testUserID, - Type: "test", - }, - } - InsertBlocks(t, store, blocksToInsert, "user-id-1") - defer DeleteBlocks(t, store, blocksToInsert, "test") - - t.Run("not existing parent", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithParentAndType(boardID, "not-exists", "test") - require.NoError(t, err) - require.Empty(t, blocks) - }) - - t.Run("not existing type", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithParentAndType(boardID, "block1", "not-existing") - require.NoError(t, err) - require.Empty(t, blocks) - }) - - t.Run("valid parent and type", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithParentAndType(boardID, "block1", "test") - require.NoError(t, err) - require.Len(t, blocks, 2) - }) - - t.Run("not existing parent", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithParent(boardID, "not-exists") - require.NoError(t, err) - require.Empty(t, blocks) - }) - - t.Run("valid parent", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithParent(boardID, "block1") - require.NoError(t, err) - require.Len(t, blocks, 3) - }) - - t.Run("not existing type", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithType(boardID, "not-exists") - require.NoError(t, err) - require.Empty(t, blocks) - }) - - t.Run("valid type", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksWithType(boardID, "test") - require.NoError(t, err) - require.Len(t, blocks, 4) - }) - - t.Run("not existing board", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksForBoard("not-exists") - require.NoError(t, err) - require.Empty(t, blocks) - }) - - t.Run("all blocks of the a board", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, 5) - }) - - t.Run("several blocks by ids", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksByIDs([]string{"block2", "block4"}) - require.NoError(t, err) - require.Len(t, blocks, 2) - }) - - t.Run("blocks by ids where some are not found", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksByIDs([]string{"block2", "blockNonexistent"}) - var naf *model.ErrNotAllFound - require.ErrorAs(t, err, &naf) - require.True(t, model.IsErrNotFound(err)) - require.Len(t, blocks, 1) - }) - - t.Run("blocks by ids where none are found", func(t *testing.T) { - time.Sleep(1 * time.Millisecond) - blocks, err = store.GetBlocksByIDs([]string{"blockNonexistent1", "blockNonexistent2"}) - var naf *model.ErrNotAllFound - require.ErrorAs(t, err, &naf) - require.True(t, model.IsErrNotFound(err)) - require.Empty(t, blocks) - }) -} - -func testGetBlock(t *testing.T, store store.Store) { - t.Run("get a block", func(t *testing.T) { - block := &model.Block{ - ID: "block-id-10", - BoardID: "board-id-1", - ModifiedBy: "user-id-1", - } - - err := store.InsertBlock(block, "user-id-1") - require.NoError(t, err) - - fetchedBlock, err := store.GetBlock("block-id-10") - require.NoError(t, err) - require.NotNil(t, fetchedBlock) - require.Equal(t, "block-id-10", fetchedBlock.ID) - require.Equal(t, "board-id-1", fetchedBlock.BoardID) - require.Equal(t, "user-id-1", fetchedBlock.CreatedBy) - require.Equal(t, "user-id-1", fetchedBlock.ModifiedBy) - assert.WithinDurationf(t, time.Now(), utils.GetTimeForMillis(fetchedBlock.CreateAt), 1*time.Second, "create time should be current time") - assert.WithinDurationf(t, time.Now(), utils.GetTimeForMillis(fetchedBlock.UpdateAt), 1*time.Second, "update time should be current time") - }) - - t.Run("get a non-existing block", func(t *testing.T) { - fetchedBlock, err := store.GetBlock("non-existing-id") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, fetchedBlock) - }) -} - -func testDuplicateBlock(t *testing.T, store store.Store) { - blocksToInsert := subtreeSampleBlocks - blocksToInsert = append(blocksToInsert, - &model.Block{ - ID: "grandchild1a", - BoardID: testBoardID, - ParentID: "child1", - ModifiedBy: testUserID, - Type: model.TypeComment, - }, - &model.Block{ - ID: "grandchild2a", - BoardID: testBoardID, - ParentID: "child2", - ModifiedBy: testUserID, - Type: model.TypeComment, - }, - ) - - InsertBlocks(t, store, blocksToInsert, "user-id-1") - time.Sleep(1 * time.Millisecond) - defer DeleteBlocks(t, store, subtreeSampleBlocks, "test") - - t.Run("duplicate existing block as no template", func(t *testing.T) { - blocks, err := store.DuplicateBlock(testBoardID, "child1", testUserID, false) - require.NoError(t, err) - require.Len(t, blocks, 2) - require.Equal(t, false, blocks[0].Fields["isTemplate"]) - }) - - t.Run("duplicate existing block as template", func(t *testing.T) { - blocks, err := store.DuplicateBlock(testBoardID, "child1", testUserID, true) - require.NoError(t, err) - require.Len(t, blocks, 2) - require.Equal(t, true, blocks[0].Fields["isTemplate"]) - }) - - t.Run("duplicate not existing block", func(t *testing.T) { - blocks, err := store.DuplicateBlock(testBoardID, "not-existing-id", testUserID, false) - require.Error(t, err) - require.Nil(t, blocks) - }) - - t.Run("duplicate not existing board", func(t *testing.T) { - blocks, err := store.DuplicateBlock("not-existing-board", "not-existing-id", testUserID, false) - require.Error(t, err) - require.Nil(t, blocks) - }) - - t.Run("not matching board/block", func(t *testing.T) { - blocks, err := store.DuplicateBlock("other-id", "child1", testUserID, false) - require.Error(t, err) - require.Nil(t, blocks) - }) -} - -func testGetBlockMetadata(t *testing.T, store store.Store) { - boardID := testBoardID - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - - blocksToInsert := []*model.Block{ - { - ID: "block1", - BoardID: boardID, - ParentID: "", - ModifiedBy: testUserID, - Type: "test", - }, - { - ID: "block2", - BoardID: boardID, - ParentID: "block1", - ModifiedBy: testUserID, - Type: "test", - }, - { - ID: "block3", - BoardID: boardID, - ParentID: "block1", - ModifiedBy: testUserID, - Type: "test", - }, - { - ID: "block4", - BoardID: boardID, - ParentID: "block1", - ModifiedBy: testUserID, - Type: "test2", - }, - { - ID: "block5", - BoardID: boardID, - ParentID: "block2", - ModifiedBy: testUserID, - Type: "test", - }, - } - - for _, v := range blocksToInsert { - time.Sleep(20 * time.Millisecond) - subBlocks := []*model.Block{v} - InsertBlocks(t, store, subBlocks, testUserID) - } - defer DeleteBlocks(t, store, blocksToInsert, "test") - - t.Run("get full block history", func(t *testing.T) { - opts := model.QueryBlockHistoryOptions{ - Descending: false, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - require.Len(t, blocks, 5) - expectedBlock := blocksToInsert[0] - block := blocks[0] - - require.Equal(t, expectedBlock.ID, block.ID) - }) - - t.Run("get full block history descending", func(t *testing.T) { - opts := model.QueryBlockHistoryOptions{ - Descending: true, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - require.Len(t, blocks, 5) - expectedBlock := blocksToInsert[len(blocksToInsert)-1] - block := blocks[0] - - require.Equal(t, expectedBlock.ID, block.ID) - }) - - t.Run("get limited block history", func(t *testing.T) { - opts := model.QueryBlockHistoryOptions{ - Limit: 3, - Descending: false, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - require.Len(t, blocks, 3) - }) - - t.Run("get first block history", func(t *testing.T) { - opts := model.QueryBlockHistoryOptions{ - Limit: 1, - Descending: false, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - require.Len(t, blocks, 1) - expectedBlock := blocksToInsert[0] - block := blocks[0] - - require.Equal(t, expectedBlock.ID, block.ID) - }) - - t.Run("get last block history", func(t *testing.T) { - opts := model.QueryBlockHistoryOptions{ - Limit: 1, - Descending: true, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - require.Len(t, blocks, 1) - expectedBlock := blocksToInsert[len(blocksToInsert)-1] - block := blocks[0] - - require.Equal(t, expectedBlock.ID, block.ID) - }) - - t.Run("get block history after updateAt", func(t *testing.T) { - rBlock, err2 := store.GetBlock("block3") - require.NoError(t, err2) - require.NotZero(t, rBlock.UpdateAt) - - opts := model.QueryBlockHistoryOptions{ - AfterUpdateAt: rBlock.UpdateAt, - Descending: false, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - require.Len(t, blocks, 2) - expectedBlock := blocksToInsert[3] - block := blocks[0] - - require.Equal(t, expectedBlock.ID, block.ID) - }) - - t.Run("get block history before updateAt", func(t *testing.T) { - rBlock, err2 := store.GetBlock("block3") - require.NoError(t, err2) - require.NotZero(t, rBlock.UpdateAt) - - opts := model.QueryBlockHistoryOptions{ - BeforeUpdateAt: rBlock.UpdateAt, - Descending: true, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - require.Len(t, blocks, 2) - expectedBlock := blocksToInsert[1] - block := blocks[0] - - require.Equal(t, expectedBlock.ID, block.ID) - }) - - t.Run("get full block history after delete", func(t *testing.T) { - time.Sleep(20 * time.Millisecond) - // this will delete `block1` and any other blocks with `block1` as parent. - err = store.DeleteBlock(blocksToInsert[0].ID, testUserID) - require.NoError(t, err) - - opts := model.QueryBlockHistoryOptions{ - Descending: true, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - // all 5 blocks get a history record for insert, then `block1` gets a record for delete, - // and all 3 `block1` children get a record for delete. Thus total is 9. - require.Len(t, blocks, 9) - }) - - t.Run("get full block history after undelete", func(t *testing.T) { - time.Sleep(20 * time.Millisecond) - // this will undelete `block1` and its children - err = store.UndeleteBlock(blocksToInsert[0].ID, testUserID) - require.NoError(t, err) - - opts := model.QueryBlockHistoryOptions{ - Descending: true, - } - blocks, err = store.GetBlockHistoryDescendants(boardID, opts) - require.NoError(t, err) - // previous test put 9 records in history table. In this test 1 record was added for undeleting - // `block1` and another 3 for undeleting the children for a total of 13. - require.Len(t, blocks, 13) - }) - - t.Run("get block history of a board with no history", func(t *testing.T) { - opts := model.QueryBlockHistoryOptions{} - - blocks, err = store.GetBlockHistoryDescendants("nonexistent-board-id", opts) - require.NoError(t, err) - require.Empty(t, blocks) - }) -} - -func testUndeleteBlockChildren(t *testing.T, store store.Store) { - boards := createTestBoards(t, store, testTeamID, testUserID, 2) - boardDelete := boards[0] - boardKeep := boards[1] - userID := testUserID - - // create some blocks to be deleted - cardsDelete := createTestCards(t, store, userID, boardDelete.ID, 3) - blocksDelete := createTestBlocksForCard(t, store, cardsDelete[0].ID, 5) - require.Len(t, blocksDelete, 5) - - // create some blocks to keep - cardsKeep := createTestCards(t, store, userID, boardKeep.ID, 3) - blocksKeep := createTestBlocksForCard(t, store, cardsKeep[0].ID, 4) - require.Len(t, blocksKeep, 4) - - t.Run("undelete block children for card", func(t *testing.T) { - cardDelete := cardsDelete[0] - cardKeep := cardsKeep[0] - - // delete a card - err := store.DeleteBlock(cardDelete.ID, testUserID) - require.NoError(t, err) - - // ensure the card was deleted - block, err := store.GetBlock(cardDelete.ID) - require.Error(t, err) - require.Nil(t, block) - - // ensure the card children were deleted - blocks, err := store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText) - require.NoError(t, err) - assert.Empty(t, blocks) - - // ensure the other card children remain. - blocks, err = store.GetBlocksWithParentAndType(cardKeep.BoardID, cardKeep.ID, model.TypeText) - require.NoError(t, err) - assert.Len(t, blocks, len(blocksKeep)) - - // undelete the card - err = store.UndeleteBlock(cardDelete.ID, testUserID) - require.NoError(t, err) - - // ensure the card was restored - block, err = store.GetBlock(cardDelete.ID) - require.NoError(t, err) - require.NotNil(t, block) - - // ensure the card children were restored - blocks, err = store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText) - require.NoError(t, err) - assert.Len(t, blocks, len(blocksDelete)) - }) - - t.Run("undelete block children for board", func(t *testing.T) { - // delete the board - err := store.DeleteBoard(boardDelete.ID, testUserID) - require.NoError(t, err) - - // ensure the board was deleted - board, err := store.GetBoard(boardDelete.ID) - require.Error(t, err) - require.Nil(t, board) - - // ensure all cards and blocks for the board were deleted - blocks, err := store.GetBlocksForBoard(boardDelete.ID) - require.NoError(t, err) - assert.Empty(t, blocks) - - // ensure the other board's cards and blocks remain. - blocks, err = store.GetBlocksForBoard(boardKeep.ID) - require.NoError(t, err) - assert.Len(t, blocks, len(blocksKeep)+len(cardsKeep)) - - // undelete the board - err = store.UndeleteBoard(boardDelete.ID, testUserID) - require.NoError(t, err) - - // ensure the board was restored - board, err = store.GetBoard(boardDelete.ID) - require.NoError(t, err) - require.NotNil(t, board) - - // ensure the board's cards and blocks were restored. - blocks, err = store.GetBlocksForBoard(boardDelete.ID) - require.NoError(t, err) - assert.Len(t, blocks, len(blocksDelete)+len(cardsDelete)) - }) -} - -func testGetBlockHistoryNewestChildren(t *testing.T, store store.Store) { - boards := createTestBoards(t, store, testTeamID, testUserID, 2) - board := boards[0] - - const cardCount = 10 - const patchCount = 5 - - // create a card and some content blocks - cards := createTestCards(t, store, testUserID, board.ID, 1) - card := cards[0] - content := createTestBlocksForCard(t, store, card.ID, cardCount) - - // patch the content blocks to create some history records - for i := 1; i <= patchCount; i++ { - for _, block := range content { - title := strconv.FormatInt(int64(i), 10) - patch := &model.BlockPatch{ - Title: &title, - } - err := store.PatchBlock(block.ID, patch, testUserID) - require.NoError(t, err, "error patching content blocks") - } - } - - // delete some of the content blocks - err := store.DeleteBlock(content[0].ID, testUserID) - require.NoError(t, err, "error deleting content block") - err = store.DeleteBlock(content[3].ID, testUserID) - require.NoError(t, err, "error deleting content block") - err = store.DeleteBlock(content[7].ID, testUserID) - require.NoError(t, err, "error deleting content block") - - t.Run("invalid card", func(t *testing.T) { - opts := model.QueryBlockHistoryChildOptions{} - blocks, hasMore, err := store.GetBlockHistoryNewestChildren(utils.NewID(utils.IDTypeCard), opts) - require.NoError(t, err) - require.False(t, hasMore) - require.Empty(t, blocks) - }) - - t.Run("valid card with no children", func(t *testing.T) { - opts := model.QueryBlockHistoryChildOptions{} - emptyCard := createTestCards(t, store, testUserID, board.ID, 1)[0] - blocks, hasMore, err := store.GetBlockHistoryNewestChildren(emptyCard.ID, opts) - require.NoError(t, err) - require.False(t, hasMore) - require.Empty(t, blocks) - }) - - t.Run("valid card with children", func(t *testing.T) { - opts := model.QueryBlockHistoryChildOptions{} - blocks, hasMore, err := store.GetBlockHistoryNewestChildren(card.ID, opts) - require.NoError(t, err) - require.False(t, hasMore) - require.Len(t, blocks, cardCount) - require.ElementsMatch(t, extractIDs(t, blocks), extractIDs(t, content)) - - expected := strconv.FormatInt(patchCount, 10) - for _, b := range blocks { - require.Equal(t, expected, b.Title) - } - }) - - t.Run("pagination", func(t *testing.T) { - opts := model.QueryBlockHistoryChildOptions{ - PerPage: 3, - } - - collected := make([]*model.Block, 0) - reps := 0 - for { - reps++ - blocks, hasMore, err := store.GetBlockHistoryNewestChildren(card.ID, opts) - require.NoError(t, err) - collected = append(collected, blocks...) - if !hasMore { - break - } - opts.Page++ - } - - assert.Len(t, collected, cardCount) - assert.Equal(t, math.Floor(float64(cardCount/opts.PerPage)+1), float64(reps)) - - expected := strconv.FormatInt(patchCount, 10) - for _, b := range collected { - require.Equal(t, expected, b.Title) - } - }) -} diff --git a/server/services/store/storetests/boards.go b/server/services/store/storetests/boards.go deleted file mode 100644 index c173f0dfb..000000000 --- a/server/services/store/storetests/boards.go +++ /dev/null @@ -1,1201 +0,0 @@ -package storetests - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "github.com/stretchr/testify/require" -) - -func StoreTestBoardStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("GetBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBoard(t, store) - }) - t.Run("GetBoardsForUserAndTeam", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBoardsForUserAndTeam(t, store) - }) - t.Run("GetBoardsInTeamByIds", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBoardsInTeamByIds(t, store) - }) - t.Run("InsertBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testInsertBoard(t, store) - }) - t.Run("PatchBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testPatchBoard(t, store) - }) - t.Run("DeleteBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDeleteBoard(t, store) - }) - t.Run("UndeleteBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUndeleteBoard(t, store) - }) - t.Run("InsertBoardWithAdmin", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testInsertBoardWithAdmin(t, store) - }) - t.Run("SaveMember", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testSaveMember(t, store) - }) - t.Run("GetMemberForBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetMemberForBoard(t, store) - }) - t.Run("GetMembersForBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetMembersForBoard(t, store) - }) - t.Run("GetMembersForUser", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetMembersForUser(t, store) - }) - t.Run("DeleteMember", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDeleteMember(t, store) - }) - t.Run("SearchBoardsForUser", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testSearchBoardsForUser(t, store) - }) - t.Run("SearchBoardsForUserInTeam", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testSearchBoardsForUserInTeam(t, store) - }) - t.Run("GetBoardHistory", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBoardHistory(t, store) - }) - t.Run("GetBoardCount", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBoardCount(t, store) - }) -} - -func testGetBoard(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("existing board", func(t *testing.T) { - board := &model.Board{ - ID: "id-1", - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - _, err := store.InsertBoard(board, userID) - require.NoError(t, err) - - rBoard, err := store.GetBoard(board.ID) - require.NoError(t, err) - require.Equal(t, board.ID, rBoard.ID) - require.Equal(t, board.TeamID, rBoard.TeamID) - require.Equal(t, userID, rBoard.CreatedBy) - require.Equal(t, userID, rBoard.ModifiedBy) - require.Equal(t, board.Type, rBoard.Type) - require.NotZero(t, rBoard.CreateAt) - require.NotZero(t, rBoard.UpdateAt) - }) - - t.Run("nonexisting board", func(t *testing.T) { - rBoard, err := store.GetBoard("nonexistent-id") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - require.Nil(t, rBoard) - }) -} - -func testGetBoardsForUserAndTeam(t *testing.T, store store.Store) { - userID := "user-id-1" - - t.Run("should return empty list if no results are found", func(t *testing.T) { - boards, err := store.GetBoardsForUserAndTeam(testUserID, testTeamID, true) - require.NoError(t, err) - require.Empty(t, boards) - }) - - t.Run("should return only the boards of the team that the user is a member of", func(t *testing.T) { - teamID1 := "team-id-1" - teamID2 := "team-id-2" - - // team 1 boards - board1 := &model.Board{ - ID: "board-id-1", - TeamID: teamID1, - Type: model.BoardTypeOpen, - } - rBoard1, _, err := store.InsertBoardWithAdmin(board1, userID) - require.NoError(t, err) - - board2 := &model.Board{ - ID: "board-id-2", - TeamID: teamID1, - Type: model.BoardTypePrivate, - } - rBoard2, _, err := store.InsertBoardWithAdmin(board2, userID) - require.NoError(t, err) - - board3 := &model.Board{ - ID: "board-id-3", - TeamID: teamID1, - Type: model.BoardTypeOpen, - } - rBoard3, err := store.InsertBoard(board3, "other-user") - require.NoError(t, err) - - board4 := &model.Board{ - ID: "board-id-4", - TeamID: teamID1, - Type: model.BoardTypePrivate, - } - _, err = store.InsertBoard(board4, "other-user") - require.NoError(t, err) - - // team 2 boards - board5 := &model.Board{ - ID: "board-id-5", - TeamID: teamID2, - Type: model.BoardTypeOpen, - } - _, _, err = store.InsertBoardWithAdmin(board5, userID) - require.NoError(t, err) - - board6 := &model.Board{ - ID: "board-id-6", - TeamID: teamID1, - Type: model.BoardTypePrivate, - } - _, err = store.InsertBoard(board6, "other-user") - require.NoError(t, err) - - t.Run("should only find the two boards that the user is a member of for team 1 plus the one open board", func(t *testing.T) { - boards, err := store.GetBoardsForUserAndTeam(userID, teamID1, true) - require.NoError(t, err) - require.ElementsMatch(t, []*model.Board{ - rBoard1, - rBoard2, - rBoard3, - }, boards) - }) - - t.Run("should only find the two boards that the user is a member of for team 1", func(t *testing.T) { - boards, err := store.GetBoardsForUserAndTeam(userID, teamID1, false) - require.NoError(t, err) - require.ElementsMatch(t, []*model.Board{ - rBoard1, - rBoard2, - }, boards) - }) - - t.Run("should only find the board that the user is a member of for team 2", func(t *testing.T) { - boards, err := store.GetBoardsForUserAndTeam(userID, teamID2, true) - require.NoError(t, err) - require.Len(t, boards, 1) - require.Equal(t, board5.ID, boards[0].ID) - }) - }) -} - -func testGetBoardsInTeamByIds(t *testing.T, store store.Store) { - t.Run("should return err not all found if one or more of the ids are not found", func(t *testing.T) { - for _, boardID := range []string{"board-id-1", "board-id-2"} { - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - rBoard, _, err := store.InsertBoardWithAdmin(board, testUserID) - require.NoError(t, err) - require.NotNil(t, rBoard) - } - - testCases := []struct { - Name string - BoardIDs []string - ExpectedError bool - ExpectedLen int - }{ - { - Name: "if none of the IDs are found", - BoardIDs: []string{"nonexistent-1", "nonexistent-2"}, - ExpectedError: true, - ExpectedLen: 0, - }, - { - Name: "if not all of the IDs are found", - BoardIDs: []string{"nonexistent-1", "board-id-1"}, - ExpectedError: true, - ExpectedLen: 1, - }, - { - Name: "if all of the IDs are found", - BoardIDs: []string{"board-id-1", "board-id-2"}, - ExpectedError: false, - ExpectedLen: 2, - }, - } - - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - boards, err := store.GetBoardsInTeamByIds(tc.BoardIDs, testTeamID) - if tc.ExpectedError { - var naf *model.ErrNotAllFound - require.ErrorAs(t, err, &naf) - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - } else { - require.NoError(t, err) - } - require.Len(t, boards, tc.ExpectedLen) - }) - } - }) -} - -func testInsertBoard(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("valid public board", func(t *testing.T) { - board := &model.Board{ - ID: "id-test-public", - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.Equal(t, board.ID, newBoard.ID) - require.Equal(t, newBoard.Type, model.BoardTypeOpen) - require.NotZero(t, newBoard.CreateAt) - require.NotZero(t, newBoard.UpdateAt) - require.Zero(t, newBoard.DeleteAt) - require.Equal(t, userID, newBoard.CreatedBy) - require.Equal(t, newBoard.CreatedBy, newBoard.ModifiedBy) - }) - - t.Run("valid private board", func(t *testing.T) { - board := &model.Board{ - ID: "id-test-private", - TeamID: testTeamID, - Type: model.BoardTypePrivate, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.Equal(t, board.ID, newBoard.ID) - require.Equal(t, newBoard.Type, model.BoardTypePrivate) - require.NotZero(t, newBoard.CreateAt) - require.NotZero(t, newBoard.UpdateAt) - require.Zero(t, newBoard.DeleteAt) - require.Equal(t, userID, newBoard.CreatedBy) - require.Equal(t, newBoard.CreatedBy, newBoard.ModifiedBy) - }) - - t.Run("invalid properties field board", func(t *testing.T) { - board := &model.Board{ - ID: "id-test-props", - TeamID: testTeamID, - Properties: map[string]interface{}{"no-serializable-value": t.Run}, - } - - _, err := store.InsertBoard(board, userID) - require.Error(t, err) - - rBoard, err := store.GetBoard(board.ID) - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - require.Nil(t, rBoard) - }) - - t.Run("update board", func(t *testing.T) { - board := &model.Board{ - ID: "id-test-public", - TeamID: testTeamID, - Title: "New title", - } - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - newBoard, err := store.InsertBoard(board, "user2") - require.NoError(t, err) - require.Equal(t, "New title", newBoard.Title) - require.Equal(t, "user2", newBoard.ModifiedBy) - }) - - t.Run("test update board type", func(t *testing.T) { - board := &model.Board{ - ID: "id-test-type-board", - Title: "Public board", - Type: model.BoardTypeOpen, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.Equal(t, model.BoardTypeOpen, newBoard.Type) - - boardUpdate := &model.Board{ - ID: "id-test-type-board", - Type: model.BoardTypePrivate, - } - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - modifiedBoard, err := store.InsertBoard(boardUpdate, userID) - require.NoError(t, err) - require.Equal(t, model.BoardTypePrivate, modifiedBoard.Type) - }) -} - -func testPatchBoard(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("should return error if the board doesn't exist", func(t *testing.T) { - newTitle := "A new title" - patch := &model.BoardPatch{Title: &newTitle} - - board, err := store.PatchBoard("nonexistent-board-id", patch, userID) - require.Error(t, err) - require.Nil(t, board) - }) - - t.Run("should correctly apply a simple patch", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - userID2 := "user-id-2" - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - Title: "A simple title", - Description: "A simple description", - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - require.Equal(t, userID, newBoard.CreatedBy) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - newTitle := "A new title" - newDescription := "A new description" - patch := &model.BoardPatch{Title: &newTitle, Description: &newDescription} - patchedBoard, err := store.PatchBoard(boardID, patch, userID2) - require.NoError(t, err) - require.Equal(t, newTitle, patchedBoard.Title) - require.Equal(t, newDescription, patchedBoard.Description) - require.Equal(t, userID, patchedBoard.CreatedBy) - require.Equal(t, userID2, patchedBoard.ModifiedBy) - }) - - t.Run("should correctly update the board properties", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - Properties: map[string]interface{}{ - "one": "1", - "two": "2", - }, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - require.Equal(t, "1", newBoard.Properties["one"].(string)) - require.Equal(t, "2", newBoard.Properties["two"].(string)) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - patch := &model.BoardPatch{ - UpdatedProperties: map[string]interface{}{"three": "3"}, - DeletedProperties: []string{"one"}, - } - patchedBoard, err := store.PatchBoard(boardID, patch, userID) - require.NoError(t, err) - require.NotContains(t, patchedBoard.Properties, "one") - require.Equal(t, "2", patchedBoard.Properties["two"].(string)) - require.Equal(t, "3", patchedBoard.Properties["three"].(string)) - }) - - t.Run("should correctly modify the board's type", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - require.Equal(t, newBoard.Type, model.BoardTypeOpen) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - newType := model.BoardTypePrivate - patch := &model.BoardPatch{Type: &newType} - patchedBoard, err := store.PatchBoard(boardID, patch, userID) - require.NoError(t, err) - require.Equal(t, model.BoardTypePrivate, patchedBoard.Type) - }) - - t.Run("a patch that doesn't include any of the properties should not modify them", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - properties := map[string]interface{}{"prop1": "val1"} - cardProperties := []map[string]interface{}{{"prop2": "val2"}} - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - Properties: properties, - CardProperties: cardProperties, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - require.Equal(t, newBoard.Type, model.BoardTypeOpen) - require.Equal(t, properties, newBoard.Properties) - require.Equal(t, cardProperties, newBoard.CardProperties) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - newType := model.BoardTypePrivate - patch := &model.BoardPatch{Type: &newType} - patchedBoard, err := store.PatchBoard(boardID, patch, userID) - require.NoError(t, err) - require.Equal(t, model.BoardTypePrivate, patchedBoard.Type) - require.Equal(t, properties, patchedBoard.Properties) - require.Equal(t, cardProperties, patchedBoard.CardProperties) - }) - - t.Run("a patch that removes a card property and updates another should work correctly", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - prop1 := map[string]interface{}{"id": "prop1", "value": "val1"} - prop2 := map[string]interface{}{"id": "prop2", "value": "val2"} - prop3 := map[string]interface{}{"id": "prop3", "value": "val3"} - cardProperties := []map[string]interface{}{prop1, prop2, prop3} - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - CardProperties: cardProperties, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - require.Equal(t, newBoard.Type, model.BoardTypeOpen) - require.Equal(t, cardProperties, newBoard.CardProperties) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - newProp1 := map[string]interface{}{"id": "prop1", "value": "newval1"} - expectedCardProperties := []map[string]interface{}{newProp1, prop3} - patch := &model.BoardPatch{ - UpdatedCardProperties: []map[string]interface{}{newProp1}, - DeletedCardProperties: []string{"prop2"}, - } - patchedBoard, err := store.PatchBoard(boardID, patch, userID) - require.NoError(t, err) - require.ElementsMatch(t, expectedCardProperties, patchedBoard.CardProperties) - }) -} - -func testDeleteBoard(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("should return an error if the board doesn't exist", func(t *testing.T) { - require.Error(t, store.DeleteBoard("nonexistent-board-id", userID)) - }) - - t.Run("should correctly delete the board", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - - rBoard, err := store.GetBoard(boardID) - require.NoError(t, err) - require.NotNil(t, rBoard) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - require.NoError(t, store.DeleteBoard(boardID, userID)) - - r2Board, err := store.GetBoard(boardID) - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - require.Nil(t, r2Board) - }) -} - -func testInsertBoardWithAdmin(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("should correctly create a board and the admin membership with the creator", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - newBoard, newMember, err := store.InsertBoardWithAdmin(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - require.Equal(t, userID, newBoard.CreatedBy) - require.Equal(t, userID, newBoard.ModifiedBy) - require.NotNil(t, newMember) - require.Equal(t, userID, newMember.UserID) - require.Equal(t, boardID, newMember.BoardID) - require.True(t, newMember.SchemeAdmin) - require.True(t, newMember.SchemeEditor) - }) -} - -func testSaveMember(t *testing.T, store store.Store) { - userID := testUserID - boardID := testBoardID - - t.Run("should correctly create a member", func(t *testing.T) { - bm := &model.BoardMember{ - UserID: userID, - BoardID: boardID, - SchemeAdmin: true, - } - - memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - initialMemberHistory := len(memberHistory) - - nbm, err := store.SaveMember(bm) - require.NoError(t, err) - require.Equal(t, userID, nbm.UserID) - require.Equal(t, boardID, nbm.BoardID) - - require.True(t, nbm.SchemeAdmin) - - memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - require.Len(t, memberHistory, initialMemberHistory+1) - }) - - t.Run("should correctly update a member", func(t *testing.T) { - bm := &model.BoardMember{ - UserID: userID, - BoardID: boardID, - SchemeEditor: true, - SchemeViewer: true, - } - - memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - initialMemberHistory := len(memberHistory) - - nbm, err := store.SaveMember(bm) - require.NoError(t, err) - require.Equal(t, userID, nbm.UserID) - require.Equal(t, boardID, nbm.BoardID) - - require.False(t, nbm.SchemeAdmin) - require.True(t, nbm.SchemeEditor) - require.True(t, nbm.SchemeViewer) - - memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - require.Len(t, memberHistory, initialMemberHistory) - }) - - t.Run("should return empty list if no results are found", func(t *testing.T) { - memberHistory, err := store.GetBoardMemberHistory(boardID, "nonexistent-user", 0) - require.NoError(t, err) - require.Empty(t, memberHistory) - }) -} - -func testGetMemberForBoard(t *testing.T, store store.Store) { - userID := testUserID - boardID := testBoardID - - t.Run("should return an error not found for nonexisting membership", func(t *testing.T) { - bm, err := store.GetMemberForBoard(boardID, userID) - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - require.Nil(t, bm) - }) - - t.Run("should return the membership if exists", func(t *testing.T) { - bm := &model.BoardMember{ - UserID: userID, - BoardID: boardID, - SchemeAdmin: true, - } - - nbm, err := store.SaveMember(bm) - require.NoError(t, err) - require.NotNil(t, nbm) - - rbm, err := store.GetMemberForBoard(boardID, userID) - require.NoError(t, err) - require.NotNil(t, rbm) - require.Equal(t, userID, rbm.UserID) - require.Equal(t, boardID, rbm.BoardID) - require.True(t, rbm.SchemeAdmin) - }) -} - -func testGetMembersForBoard(t *testing.T, store store.Store) { - t.Run("should return empty list if there are no members on a board", func(t *testing.T) { - members, err := store.GetMembersForBoard(testBoardID) - require.NoError(t, err) - require.Empty(t, members) - }) - - t.Run("should return the members of the board", func(t *testing.T) { - boardID1 := "board-id-1" - boardID2 := "board-id-2" - - userID1 := "user-id-11" - userID2 := "user-id-12" - userID3 := "user-id-13" - - bm1 := &model.BoardMember{BoardID: boardID1, UserID: userID1, SchemeAdmin: true} - _, err1 := store.SaveMember(bm1) - require.NoError(t, err1) - - bm2 := &model.BoardMember{BoardID: boardID1, UserID: userID2, SchemeEditor: true} - _, err2 := store.SaveMember(bm2) - require.NoError(t, err2) - - bm3 := &model.BoardMember{BoardID: boardID2, UserID: userID3, SchemeAdmin: true} - _, err3 := store.SaveMember(bm3) - require.NoError(t, err3) - - getMemberIDs := func(members []*model.BoardMember) []string { - ids := make([]string, len(members)) - for i, member := range members { - ids[i] = member.UserID - } - return ids - } - - board1Members, err := store.GetMembersForBoard(boardID1) - require.NoError(t, err) - require.Len(t, board1Members, 2) - require.ElementsMatch(t, []string{userID1, userID2}, getMemberIDs(board1Members)) - - board2Members, err := store.GetMembersForBoard(boardID2) - require.NoError(t, err) - require.Len(t, board2Members, 1) - require.ElementsMatch(t, []string{userID3}, getMemberIDs(board2Members)) - }) -} - -func testGetMembersForUser(t *testing.T, store store.Store) { - t.Run("should return empty list if there are no memberships for a user", func(t *testing.T) { - members, err := store.GetMembersForUser(testUserID) - require.NoError(t, err) - require.Empty(t, members) - }) -} - -func testDeleteMember(t *testing.T, store store.Store) { - userID := testUserID - boardID := testBoardID - - t.Run("should return nil if deleting a nonexistent member", func(t *testing.T) { - memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - initialMemberHistory := len(memberHistory) - - require.NoError(t, store.DeleteMember(boardID, userID)) - - memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - require.Len(t, memberHistory, initialMemberHistory) - }) - - t.Run("should correctly delete a member", func(t *testing.T) { - bm := &model.BoardMember{ - UserID: userID, - BoardID: boardID, - SchemeAdmin: true, - } - - nbm, err := store.SaveMember(bm) - require.NoError(t, err) - require.NotNil(t, nbm) - - memberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - initialMemberHistory := len(memberHistory) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(1 * time.Millisecond) - - require.NoError(t, store.DeleteMember(boardID, userID)) - - rbm, err := store.GetMemberForBoard(boardID, userID) - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - require.Nil(t, rbm) - - memberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0) - require.NoError(t, err) - require.Len(t, memberHistory, initialMemberHistory+1) - }) -} - -func testSearchBoardsForUser(t *testing.T, store store.Store) { - teamID1 := "team-id-1" - teamID2 := "team-id-2" - userID := "user-id-1" - - t.Run("should return empty if user is not a member of any board and there are no public boards on the team", func(t *testing.T) { - boards, err := store.SearchBoardsForUser("", model.BoardSearchFieldTitle, userID, true) - require.NoError(t, err) - require.Empty(t, boards) - }) - - board1 := &model.Board{ - ID: "board-id-1", - TeamID: teamID1, - Type: model.BoardTypeOpen, - Title: "Public Board with admin", - Properties: map[string]any{"foo": "bar1"}, - } - _, _, err := store.InsertBoardWithAdmin(board1, userID) - require.NoError(t, err) - - board2 := &model.Board{ - ID: "board-id-2", - TeamID: teamID1, - Type: model.BoardTypeOpen, - Title: "Public Board", - Properties: map[string]any{"foo": "bar2"}, - } - _, err = store.InsertBoard(board2, userID) - require.NoError(t, err) - - board3 := &model.Board{ - ID: "board-id-3", - TeamID: teamID1, - Type: model.BoardTypePrivate, - Title: "Private Board with admin", - } - _, _, err = store.InsertBoardWithAdmin(board3, userID) - require.NoError(t, err) - - board4 := &model.Board{ - ID: "board-id-4", - TeamID: teamID1, - Type: model.BoardTypePrivate, - Title: "Private Board", - } - _, err = store.InsertBoard(board4, userID) - require.NoError(t, err) - - board5 := &model.Board{ - ID: "board-id-5", - TeamID: teamID2, - Type: model.BoardTypeOpen, - Title: "Public Board with admin in team 2", - } - _, _, err = store.InsertBoardWithAdmin(board5, userID) - require.NoError(t, err) - - testCases := []struct { - Name string - TeamID string - UserID string - Term string - SearchField model.BoardSearchField - IncludePublic bool - ExpectedBoardIDs []string - }{ - { - Name: "should find all private boards that the user is a member of and public boards with an empty term", - TeamID: teamID1, - UserID: userID, - Term: "", - SearchField: model.BoardSearchFieldTitle, - IncludePublic: true, - ExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID, board5.ID}, - }, - { - Name: "should find all with term board", - TeamID: teamID1, - UserID: userID, - Term: "board", - SearchField: model.BoardSearchFieldTitle, - IncludePublic: true, - ExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID, board5.ID}, - }, - { - Name: "should find all with term board where the user is member of", - TeamID: teamID1, - UserID: userID, - Term: "board", - SearchField: model.BoardSearchFieldTitle, - IncludePublic: false, - ExpectedBoardIDs: []string{board1.ID, board3.ID, board5.ID}, - }, - { - Name: "should find only public as per the term, wether user is a member or not", - TeamID: teamID1, - UserID: userID, - Term: "public", - SearchField: model.BoardSearchFieldTitle, - IncludePublic: true, - ExpectedBoardIDs: []string{board1.ID, board2.ID, board5.ID}, - }, - { - Name: "should find only private as per the term, wether user is a member or not", - TeamID: teamID1, - UserID: userID, - Term: "priv", - SearchField: model.BoardSearchFieldTitle, - IncludePublic: true, - ExpectedBoardIDs: []string{board3.ID}, - }, - { - Name: "should find no board in team 2 with a non matching term", - TeamID: teamID2, - UserID: userID, - Term: "non-matching-term", - SearchField: model.BoardSearchFieldTitle, - IncludePublic: true, - ExpectedBoardIDs: []string{}, - }, - { - Name: "should find all boards with a named property", - TeamID: teamID1, - UserID: userID, - Term: "foo", - SearchField: model.BoardSearchFieldPropertyName, - IncludePublic: true, - ExpectedBoardIDs: []string{board1.ID, board2.ID}, - }, - { - Name: "should find no boards with a non-existing named property", - TeamID: teamID1, - UserID: userID, - Term: "bogus", - SearchField: model.BoardSearchFieldPropertyName, - IncludePublic: true, - ExpectedBoardIDs: []string{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - boards, err := store.SearchBoardsForUser(tc.Term, tc.SearchField, tc.UserID, tc.IncludePublic) - require.NoError(t, err) - - boardIDs := []string{} - for _, board := range boards { - boardIDs = append(boardIDs, board.ID) - } - require.ElementsMatch(t, tc.ExpectedBoardIDs, boardIDs) - }) - } -} - -func testSearchBoardsForUserInTeam(t *testing.T, store store.Store) { - t.Run("should return empty list if there are no resutls", func(t *testing.T) { - boards, err := store.SearchBoardsForUserInTeam("nonexistent-team-id", "", testUserID) - require.NoError(t, err) - require.Empty(t, boards) - }) -} - -func testUndeleteBoard(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("existing id", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - Title: "Dunder Mifflin Scranton", - MinimumRole: model.BoardRoleCommenter, - Description: "Bears, beets, Battlestar Gallectica", - Icon: "🐻", - ShowDescription: true, - IsTemplate: false, - Properties: map[string]interface{}{ - "prop_1": "value_1", - }, - CardProperties: []map[string]interface{}{ - { - "prop_1": "value_1", - }, - }, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err = store.DeleteBoard(boardID, userID) - require.NoError(t, err) - - board, err = store.GetBoard(boardID) - require.Error(t, err) - require.Nil(t, board) - - time.Sleep(1 * time.Millisecond) - err = store.UndeleteBoard(boardID, userID) - require.NoError(t, err) - - board, err = store.GetBoard(boardID) - require.NoError(t, err) - require.NotNil(t, board) - - // verifying the data after un-delete - require.Equal(t, "Dunder Mifflin Scranton", board.Title) - require.Equal(t, "user-id", board.CreatedBy) - require.Equal(t, "user-id", board.ModifiedBy) - require.Equal(t, model.BoardRoleCommenter, board.MinimumRole) - require.Equal(t, "Bears, beets, Battlestar Gallectica", board.Description) - require.Equal(t, "🐻", board.Icon) - require.True(t, board.ShowDescription) - require.False(t, board.IsTemplate) - require.Equal(t, board.Properties["prop_1"].(string), "value_1") - require.Equal(t, 1, len(board.CardProperties)) - require.Equal(t, board.CardProperties[0]["prop_1"], "value_1") - require.Equal(t, board.CardProperties[0]["prop_1"], "value_1") - }) - - t.Run("existing id multiple times", func(t *testing.T) { - boardID := utils.NewID(utils.IDTypeBoard) - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - newBoard, err := store.InsertBoard(board, userID) - require.NoError(t, err) - require.NotNil(t, newBoard) - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err = store.DeleteBoard(boardID, userID) - require.NoError(t, err) - - board, err = store.GetBoard(boardID) - require.Error(t, err) - require.Nil(t, board) - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err = store.UndeleteBoard(boardID, userID) - require.NoError(t, err) - - board, err = store.GetBoard(boardID) - require.NoError(t, err) - require.NotNil(t, board) - - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err = store.UndeleteBoard(boardID, userID) - require.NoError(t, err) - - board, err = store.GetBoard(boardID) - require.NoError(t, err) - require.NotNil(t, board) - }) - - t.Run("from not existing id", func(t *testing.T) { - // Wait for not colliding the ID+insert_at key - time.Sleep(1 * time.Millisecond) - err := store.UndeleteBoard("not-exists", userID) - require.NoError(t, err) - - block, err := store.GetBoard("not-exists") - require.Error(t, err) - require.Nil(t, block) - }) -} - -func testGetBoardHistory(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("testGetBoardHistory: create board", func(t *testing.T) { - originalTitle := "Board: original title" - boardID := utils.NewID(utils.IDTypeBoard) - board := &model.Board{ - ID: boardID, - Title: originalTitle, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - rBoard1, err := store.InsertBoard(board, userID) - require.NoError(t, err) - - opts := model.QueryBoardHistoryOptions{ - Limit: 0, - Descending: false, - } - - boards, err := store.GetBoardHistory(board.ID, opts) - require.NoError(t, err) - require.Len(t, boards, 1) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - userID2 := "user-id-2" - newTitle := "Board: A new title" - newDescription := "A new description" - patch := &model.BoardPatch{Title: &newTitle, Description: &newDescription} - patchedBoard, err := store.PatchBoard(boardID, patch, userID2) - require.NoError(t, err) - - // Updated history - boards, err = store.GetBoardHistory(board.ID, opts) - require.NoError(t, err) - require.Len(t, boards, 2) - require.Equal(t, boards[0].Title, originalTitle) - require.Equal(t, boards[1].Title, newTitle) - require.Equal(t, boards[1].Description, newDescription) - - // Check history against latest board - rBoard2, err := store.GetBoard(board.ID) - require.NoError(t, err) - require.Equal(t, rBoard2.Title, newTitle) - require.Equal(t, rBoard2.Title, boards[1].Title) - require.NotZero(t, rBoard2.UpdateAt) - require.Equal(t, rBoard1.UpdateAt, boards[0].UpdateAt) - require.Equal(t, rBoard2.UpdateAt, patchedBoard.UpdateAt) - require.Equal(t, rBoard2.UpdateAt, boards[1].UpdateAt) - require.Equal(t, rBoard1, boards[0]) - require.Equal(t, rBoard2, boards[1]) - - // wait to avoid hitting pk uniqueness constraint in history - time.Sleep(10 * time.Millisecond) - - newTitle2 := "Board: A new title 2" - patch2 := &model.BoardPatch{Title: &newTitle2} - patchBoard2, err := store.PatchBoard(boardID, patch2, userID2) - require.NoError(t, err) - - // Updated history - opts = model.QueryBoardHistoryOptions{ - Limit: 1, - Descending: true, - } - boards, err = store.GetBoardHistory(board.ID, opts) - require.NoError(t, err) - require.Len(t, boards, 1) - require.Equal(t, boards[0].Title, newTitle2) - require.Equal(t, boards[0], patchBoard2) - - // Delete board - time.Sleep(10 * time.Millisecond) - err = store.DeleteBoard(boardID, userID) - require.NoError(t, err) - - // Updated history after delete - opts = model.QueryBoardHistoryOptions{ - Limit: 0, - Descending: true, - } - boards, err = store.GetBoardHistory(board.ID, opts) - require.NoError(t, err) - require.Len(t, boards, 4) - require.NotZero(t, boards[0].UpdateAt) - require.Greater(t, boards[0].UpdateAt, patchBoard2.UpdateAt) - require.NotZero(t, boards[0].DeleteAt) - require.Greater(t, boards[0].DeleteAt, patchBoard2.UpdateAt) - }) - - t.Run("testGetBoardHistory: nonexisting board", func(t *testing.T) { - opts := model.QueryBoardHistoryOptions{ - Limit: 0, - Descending: false, - } - boards, err := store.GetBoardHistory("nonexistent-id", opts) - require.NoError(t, err) - require.Len(t, boards, 0) - }) -} - -func testGetBoardCount(t *testing.T, store store.Store) { - userID := testUserID - - t.Run("test GetBoardCount", func(t *testing.T) { - originalCount, err := store.GetBoardCount() - require.NoError(t, err) - - title := "Board: original title" - boardID := utils.NewID(utils.IDTypeBoard) - board := &model.Board{ - ID: boardID, - Title: title, - TeamID: testTeamID, - Type: model.BoardTypeOpen, - } - - _, err = store.InsertBoard(board, userID) - require.NoError(t, err) - - newCount, err := store.GetBoardCount() - require.NoError(t, err) - require.Equal(t, originalCount+1, newCount) - }) -} diff --git a/server/services/store/storetests/boards_and_blocks.go b/server/services/store/storetests/boards_and_blocks.go deleted file mode 100644 index f714c48bf..000000000 --- a/server/services/store/storetests/boards_and_blocks.go +++ /dev/null @@ -1,697 +0,0 @@ -package storetests - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "github.com/stretchr/testify/require" -) - -func StoreTestBoardsAndBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("createBoardsAndBlocks", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testCreateBoardsAndBlocks(t, store) - }) - t.Run("patchBoardsAndBlocks", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testPatchBoardsAndBlocks(t, store) - }) - t.Run("deleteBoardsAndBlocks", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDeleteBoardsAndBlocks(t, store) - }) - - t.Run("duplicateBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDuplicateBoard(t, store) - }) -} - -func testCreateBoardsAndBlocks(t *testing.T, store store.Store) { - teamID := testTeamID - userID := testUserID - - boards, err := store.GetBoardsForUserAndTeam(userID, teamID, true) - require.Nil(t, err) - require.Empty(t, boards) - - t.Run("create boards and blocks", func(t *testing.T) { - newBab := &model.BoardsAndBlocks{ - Boards: []*model.Board{ - {ID: "board-id-1", TeamID: teamID, Type: model.BoardTypeOpen}, - {ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate}, - {ID: "board-id-3", TeamID: teamID, Type: model.BoardTypeOpen}, - }, - Blocks: []*model.Block{ - {ID: "block-id-1", BoardID: "board-id-1", Type: model.TypeCard}, - {ID: "block-id-2", BoardID: "board-id-2", Type: model.TypeCard}, - }, - } - - bab, err := store.CreateBoardsAndBlocks(newBab, userID) - require.Nil(t, err) - require.NotNil(t, bab) - require.Len(t, bab.Boards, 3) - require.Len(t, bab.Blocks, 2) - - boardIDs := []string{} - for _, board := range bab.Boards { - boardIDs = append(boardIDs, board.ID) - } - - blockIDs := []string{} - for _, block := range bab.Blocks { - blockIDs = append(blockIDs, block.ID) - } - - require.ElementsMatch(t, []string{"board-id-1", "board-id-2", "board-id-3"}, boardIDs) - require.ElementsMatch(t, []string{"block-id-1", "block-id-2"}, blockIDs) - }) - - t.Run("create boards and blocks with admin", func(t *testing.T) { - newBab := &model.BoardsAndBlocks{ - Boards: []*model.Board{ - {ID: "board-id-4", TeamID: teamID, Type: model.BoardTypeOpen}, - {ID: "board-id-5", TeamID: teamID, Type: model.BoardTypePrivate}, - {ID: "board-id-6", TeamID: teamID, Type: model.BoardTypeOpen}, - }, - Blocks: []*model.Block{ - {ID: "block-id-3", BoardID: "board-id-4", Type: model.TypeCard}, - {ID: "block-id-4", BoardID: "board-id-5", Type: model.TypeCard}, - }, - } - - bab, members, err := store.CreateBoardsAndBlocksWithAdmin(newBab, userID) - require.Nil(t, err) - require.NotNil(t, bab) - require.Len(t, bab.Boards, 3) - require.Len(t, bab.Blocks, 2) - require.Len(t, members, 3) - - boardIDs := []string{} - for _, board := range bab.Boards { - boardIDs = append(boardIDs, board.ID) - } - - blockIDs := []string{} - for _, block := range bab.Blocks { - blockIDs = append(blockIDs, block.ID) - } - - require.ElementsMatch(t, []string{"board-id-4", "board-id-5", "board-id-6"}, boardIDs) - require.ElementsMatch(t, []string{"block-id-3", "block-id-4"}, blockIDs) - - memberBoardIDs := []string{} - for _, member := range members { - require.Equal(t, userID, member.UserID) - memberBoardIDs = append(memberBoardIDs, member.BoardID) - } - require.ElementsMatch(t, []string{"board-id-4", "board-id-5", "board-id-6"}, memberBoardIDs) - }) - - t.Run("on failure, nothing should be saved", func(t *testing.T) { - // one of the blocks is invalid as it doesn't have BoardID - newBab := &model.BoardsAndBlocks{ - Boards: []*model.Board{ - {ID: "board-id-7", TeamID: teamID, Type: model.BoardTypeOpen}, - {ID: "board-id-8", TeamID: teamID, Type: model.BoardTypePrivate}, - {ID: "board-id-9", TeamID: teamID, Type: model.BoardTypeOpen}, - }, - Blocks: []*model.Block{ - {ID: "block-id-5", BoardID: "board-id-7", Type: model.TypeCard}, - {ID: "block-id-6", BoardID: "", Type: model.TypeCard}, - }, - } - - bab, err := store.CreateBoardsAndBlocks(newBab, userID) - require.Error(t, err) - require.Nil(t, bab) - - bab, members, err := store.CreateBoardsAndBlocksWithAdmin(newBab, userID) - require.Error(t, err) - require.Empty(t, bab) - require.Empty(t, members) - }) - - t.Run("should apply block size limits", func(t *testing.T) { - // one of the blocks is invalid as it has a title too large - newBab := &model.BoardsAndBlocks{ - Boards: []*model.Board{ - {ID: "board-id-7", TeamID: teamID, Type: model.BoardTypeOpen}, - {ID: "board-id-8", TeamID: teamID, Type: model.BoardTypePrivate}, - {ID: "board-id-9", TeamID: teamID, Type: model.BoardTypeOpen}, - }, - Blocks: []*model.Block{ - {ID: "block-id-5", BoardID: "board-id-7", Type: model.TypeCard}, - {ID: "block-id-6", BoardID: "board-id-8", Type: model.TypeCard, Title: strings.Repeat("A", model.BlockTitleMaxRunes+1)}, - }, - } - - bab, err := store.CreateBoardsAndBlocks(newBab, userID) - require.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded) - require.Nil(t, bab) - - bab, members, err := store.CreateBoardsAndBlocksWithAdmin(newBab, userID) - require.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded) - require.Empty(t, bab) - require.Empty(t, members) - }) -} - -func testPatchBoardsAndBlocks(t *testing.T, store store.Store) { - teamID := testTeamID - userID := testUserID - - t.Run("on failure, nothing should be saved", func(t *testing.T) { - if store.DBType() == model.SqliteDBType { - t.Skip("No transactions support int sqlite") - } - - initialTitle := "initial title" - newTitle := "new title" - - board := &model.Board{ - ID: "board-id-1", - Title: initialTitle, - TeamID: teamID, - Type: model.BoardTypeOpen, - } - _, err := store.InsertBoard(board, userID) - require.NoError(t, err) - - block := &model.Block{ - ID: "block-id-1", - BoardID: "board-id-1", - Title: initialTitle, - } - require.NoError(t, store.InsertBlock(block, userID)) - - // apply the patches - pbab := &model.PatchBoardsAndBlocks{ - BoardIDs: []string{"board-id-1"}, - BoardPatches: []*model.BoardPatch{ - {Title: &newTitle}, - }, - BlockIDs: []string{"block-id-1", "block-id-2"}, - BlockPatches: []*model.BlockPatch{ - {Title: &newTitle}, - {Title: &newTitle}, - }, - } - - time.Sleep(10 * time.Millisecond) - - bab, err := store.PatchBoardsAndBlocks(pbab, userID) - require.Error(t, err) - require.Nil(t, bab) - - // check that things have not changed - rBoard, err := store.GetBoard("board-id-1") - require.NoError(t, err) - require.Equal(t, initialTitle, rBoard.Title) - - rBlock, err := store.GetBlock("block-id-1") - require.NoError(t, err) - require.Equal(t, initialTitle, rBlock.Title) - }) - - t.Run("should apply block size limits", func(t *testing.T) { - if store.DBType() == model.SqliteDBType { - t.Skip("No transactions support int sqlite") - } - - initialTitle := "initial title" - newTitle := strings.Repeat("A", model.BlockTitleMaxRunes+1) - - board := &model.Board{ - ID: "board-id-1", - Title: initialTitle, - TeamID: teamID, - Type: model.BoardTypeOpen, - } - _, err := store.InsertBoard(board, userID) - require.NoError(t, err) - - block := &model.Block{ - ID: "block-id-1", - BoardID: "board-id-1", - Title: initialTitle, - } - require.NoError(t, store.InsertBlock(block, userID)) - - // apply the patches - pbab := &model.PatchBoardsAndBlocks{ - BlockIDs: []string{"block-id-1"}, - BlockPatches: []*model.BlockPatch{ - {Title: &newTitle}, - }, - } - - time.Sleep(10 * time.Millisecond) - - bab, err := store.PatchBoardsAndBlocks(pbab, userID) - require.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded) - require.Nil(t, bab) - - // check that things have not changed - rBlock, err := store.GetBlock("block-id-1") - require.NoError(t, err) - require.Equal(t, initialTitle, rBlock.Title) - }) - - t.Run("patch boards and blocks", func(t *testing.T) { - newBab := &model.BoardsAndBlocks{ - Boards: []*model.Board{ - {ID: "board-id-1", Description: "initial description", TeamID: teamID, Type: model.BoardTypeOpen}, - {ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate}, - {ID: "board-id-3", Title: "initial title", TeamID: teamID, Type: model.BoardTypeOpen}, - }, - Blocks: []*model.Block{ - {ID: "block-id-1", Title: "initial title", BoardID: "board-id-1", Type: model.TypeCard}, - {ID: "block-id-2", Schema: 1, BoardID: "board-id-2", Type: model.TypeCard}, - }, - } - - rBab, err := store.CreateBoardsAndBlocks(newBab, userID) - require.Nil(t, err) - require.NotNil(t, rBab) - require.Len(t, rBab.Boards, 3) - require.Len(t, rBab.Blocks, 2) - - // apply the patches - newTitle := "new title" - newDescription := "new description" - var newSchema int64 = 2 - - pbab := &model.PatchBoardsAndBlocks{ - BoardIDs: []string{"board-id-3", "board-id-1"}, - BoardPatches: []*model.BoardPatch{ - {Title: &newTitle, Description: &newDescription}, - {Description: &newDescription}, - }, - BlockIDs: []string{"block-id-1", "block-id-2"}, - BlockPatches: []*model.BlockPatch{ - {Title: &newTitle}, - {Schema: &newSchema}, - }, - } - - time.Sleep(10 * time.Millisecond) - - bab, err := store.PatchBoardsAndBlocks(pbab, userID) - require.NoError(t, err) - require.NotNil(t, bab) - require.Len(t, bab.Boards, 2) - require.Len(t, bab.Blocks, 2) - - // check that things have changed - board1, err := store.GetBoard("board-id-1") - require.NoError(t, err) - require.Equal(t, newDescription, board1.Description) - - board3, err := store.GetBoard("board-id-3") - require.NoError(t, err) - require.Equal(t, newTitle, board3.Title) - require.Equal(t, newDescription, board3.Description) - - block1, err := store.GetBlock("block-id-1") - require.NoError(t, err) - require.Equal(t, newTitle, block1.Title) - - block2, err := store.GetBlock("block-id-2") - require.NoError(t, err) - require.Equal(t, newSchema, block2.Schema) - }) -} - -func testDeleteBoardsAndBlocks(t *testing.T, store store.Store) { - teamID := testTeamID - userID := testUserID - - t.Run("should not delete anything if a block doesn't belong to any of the boards", func(t *testing.T) { - if store.DBType() == model.SqliteDBType { - t.Skip("No transactions support int sqlite") - } - - newBoard1 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board1, err := store.InsertBoard(newBoard1, userID) - require.NoError(t, err) - - block1 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block1, userID)) - - block2 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block2, userID)) - - newBoard2 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board2, err := store.InsertBoard(newBoard2, userID) - require.NoError(t, err) - - block3 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board2.ID, - } - require.NoError(t, store.InsertBlock(block3, userID)) - - block4 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: "different-board-id", - } - require.NoError(t, store.InsertBlock(block4, userID)) - - dbab := &model.DeleteBoardsAndBlocks{ - Boards: []string{board1.ID, board2.ID}, - Blocks: []string{block1.ID, block2.ID, block3.ID, block4.ID}, - } - - time.Sleep(10 * time.Millisecond) - - expectedErrorMsg := fmt.Sprintf("block %s doesn't belong to any of the boards in the delete request", block4.ID) - require.EqualError(t, store.DeleteBoardsAndBlocks(dbab, userID), expectedErrorMsg) - - // all the entities should still exist - rBoard1, err := store.GetBoard(board1.ID) - require.NoError(t, err) - require.NotNil(t, rBoard1) - rBlock1, err := store.GetBlock(block1.ID) - require.NoError(t, err) - require.NotNil(t, rBlock1) - rBlock2, err := store.GetBlock(block2.ID) - require.NoError(t, err) - require.NotNil(t, rBlock2) - - rBoard2, err := store.GetBoard(board2.ID) - require.NoError(t, err) - require.NotNil(t, rBoard2) - rBlock3, err := store.GetBlock(block3.ID) - require.NoError(t, err) - require.NotNil(t, rBlock3) - rBlock4, err := store.GetBlock(block4.ID) - require.NoError(t, err) - require.NotNil(t, rBlock4) - }) - - t.Run("should not delete anything if a board doesn't exist", func(t *testing.T) { - if store.DBType() == model.SqliteDBType { - t.Skip("No transactions support int sqlite") - } - - newBoard1 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board1, err := store.InsertBoard(newBoard1, userID) - require.NoError(t, err) - - block1 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block1, userID)) - - block2 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block2, userID)) - - newBoard2 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board2, err := store.InsertBoard(newBoard2, userID) - require.NoError(t, err) - - block3 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board2.ID, - } - require.NoError(t, store.InsertBlock(block3, userID)) - - block4 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board2.ID, - } - require.NoError(t, store.InsertBlock(block4, userID)) - - dbab := &model.DeleteBoardsAndBlocks{ - Boards: []string{board1.ID, board2.ID, "a nonexistent board ID"}, - Blocks: []string{block1.ID, block2.ID, block3.ID, block4.ID}, - } - - time.Sleep(10 * time.Millisecond) - - require.True(t, model.IsErrNotFound(store.DeleteBoardsAndBlocks(dbab, userID))) - - // all the entities should still exist - rBoard1, err := store.GetBoard(board1.ID) - require.NoError(t, err) - require.NotNil(t, rBoard1) - rBlock1, err := store.GetBlock(block1.ID) - require.NoError(t, err) - require.NotNil(t, rBlock1) - rBlock2, err := store.GetBlock(block2.ID) - require.NoError(t, err) - require.NotNil(t, rBlock2) - - rBoard2, err := store.GetBoard(board2.ID) - require.NoError(t, err) - require.NotNil(t, rBoard2) - rBlock3, err := store.GetBlock(block3.ID) - require.NoError(t, err) - require.NotNil(t, rBlock3) - rBlock4, err := store.GetBlock(block4.ID) - require.NoError(t, err) - require.NotNil(t, rBlock4) - }) - - t.Run("should not delete anything if a block doesn't exist", func(t *testing.T) { - if store.DBType() == model.SqliteDBType { - t.Skip("No transactions support int sqlite") - } - - newBoard1 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board1, err := store.InsertBoard(newBoard1, userID) - require.NoError(t, err) - - block1 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block1, userID)) - - block2 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block2, userID)) - - newBoard2 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board2, err := store.InsertBoard(newBoard2, userID) - require.NoError(t, err) - - block3 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board2.ID, - } - require.NoError(t, store.InsertBlock(block3, userID)) - - block4 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board2.ID, - } - require.NoError(t, store.InsertBlock(block4, userID)) - - dbab := &model.DeleteBoardsAndBlocks{ - Boards: []string{board1.ID, board2.ID}, - Blocks: []string{block1.ID, block2.ID, block3.ID, block4.ID, "a nonexistent block ID"}, - } - - time.Sleep(10 * time.Millisecond) - - require.True(t, model.IsErrNotFound(store.DeleteBoardsAndBlocks(dbab, userID))) - - // all the entities should still exist - rBoard1, err := store.GetBoard(board1.ID) - require.NoError(t, err) - require.NotNil(t, rBoard1) - rBlock1, err := store.GetBlock(block1.ID) - require.NoError(t, err) - require.NotNil(t, rBlock1) - rBlock2, err := store.GetBlock(block2.ID) - require.NoError(t, err) - require.NotNil(t, rBlock2) - - rBoard2, err := store.GetBoard(board2.ID) - require.NoError(t, err) - require.NotNil(t, rBoard2) - rBlock3, err := store.GetBlock(block3.ID) - require.NoError(t, err) - require.NotNil(t, rBlock3) - rBlock4, err := store.GetBlock(block4.ID) - require.NoError(t, err) - require.NotNil(t, rBlock4) - }) - - t.Run("should work properly if all the entities are related", func(t *testing.T) { - newBoard1 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board1, err := store.InsertBoard(newBoard1, userID) - require.NoError(t, err) - - block1 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block1, userID)) - - block2 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board1.ID, - } - require.NoError(t, store.InsertBlock(block2, userID)) - - newBoard2 := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: model.BoardTypeOpen, - } - board2, err := store.InsertBoard(newBoard2, userID) - require.NoError(t, err) - - block3 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board2.ID, - } - require.NoError(t, store.InsertBlock(block3, userID)) - - block4 := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: board2.ID, - } - require.NoError(t, store.InsertBlock(block4, userID)) - - dbab := &model.DeleteBoardsAndBlocks{ - Boards: []string{board1.ID, board2.ID}, - Blocks: []string{block1.ID, block2.ID, block3.ID, block4.ID}, - } - - time.Sleep(10 * time.Millisecond) - - require.NoError(t, store.DeleteBoardsAndBlocks(dbab, userID)) - - rBoard1, err := store.GetBoard(board1.ID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, rBoard1) - rBlock1, err := store.GetBlock(block1.ID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, rBlock1) - rBlock2, err := store.GetBlock(block2.ID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, rBlock2) - - rBoard2, err := store.GetBoard(board2.ID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, rBoard2) - rBlock3, err := store.GetBlock(block3.ID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, rBlock3) - rBlock4, err := store.GetBlock(block4.ID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, rBlock4) - }) -} - -func testDuplicateBoard(t *testing.T, store store.Store) { - teamID := testTeamID - userID := testUserID - - newBab := &model.BoardsAndBlocks{ - Boards: []*model.Board{ - {ID: "board-id-1", TeamID: teamID, Type: model.BoardTypeOpen, ChannelID: "test-channel"}, - {ID: "board-id-2", TeamID: teamID, Type: model.BoardTypePrivate}, - {ID: "board-id-3", TeamID: teamID, Type: model.BoardTypeOpen}, - }, - Blocks: []*model.Block{ - {ID: "block-id-1", BoardID: "board-id-1", Type: model.TypeCard}, - {ID: "block-id-1a", BoardID: "board-id-1", Type: model.TypeComment}, - {ID: "block-id-2", BoardID: "board-id-2", Type: model.TypeCard}, - }, - } - - bab, err := store.CreateBoardsAndBlocks(newBab, userID) - require.Nil(t, err) - require.NotNil(t, bab) - require.Len(t, bab.Boards, 3) - require.Len(t, bab.Blocks, 3) - - t.Run("duplicate existing board as no template", func(t *testing.T) { - bab, members, err := store.DuplicateBoard("board-id-1", userID, teamID, false) - require.NoError(t, err) - require.Len(t, members, 1) - require.Len(t, bab.Boards, 1) - require.Len(t, bab.Blocks, 1) - require.Equal(t, bab.Boards[0].IsTemplate, false) - require.Equal(t, "", bab.Boards[0].ChannelID) - }) - - t.Run("duplicate existing board as template", func(t *testing.T) { - bab, members, err := store.DuplicateBoard("board-id-1", userID, teamID, true) - require.NoError(t, err) - require.Len(t, members, 1) - require.Len(t, bab.Boards, 1) - require.Len(t, bab.Blocks, 1) - require.Equal(t, bab.Boards[0].IsTemplate, true) - require.Equal(t, "", bab.Boards[0].ChannelID) - }) - - t.Run("duplicate not existing board", func(t *testing.T) { - bab, members, err := store.DuplicateBoard("not-existing-id", userID, teamID, false) - require.Error(t, err) - require.Nil(t, members) - require.Nil(t, bab) - }) -} diff --git a/server/services/store/storetests/category.go b/server/services/store/storetests/category.go deleted file mode 100644 index 06053b6ea..000000000 --- a/server/services/store/storetests/category.go +++ /dev/null @@ -1,337 +0,0 @@ -package storetests - -import ( - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - "github.com/stretchr/testify/assert" -) - -type testFunc func(t *testing.T, store store.Store) - -func StoreTestCategoryStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - tests := map[string]testFunc{ - "CreateCategory": testGetCreateCategory, - "UpdateCategory": testUpdateCategory, - "DeleteCategory": testDeleteCategory, - "GetUserCategories": testGetUserCategories, - "ReorderCategories": testReorderCategories, - "ReorderCategoriesBoards": testReorderCategoryBoards, - } - - for name, f := range tests { - t.Run(name, func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - f(t, store) - }) - } -} - -func testGetCreateCategory(t *testing.T, store store.Store) { - t.Run("save uncollapsed category", func(t *testing.T) { - now := utils.GetMillis() - category := model.Category{ - ID: "category_id_1", - Name: "Category", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - - err := store.CreateCategory(category) - assert.NoError(t, err) - - createdCategory, err := store.GetCategory("category_id_1") - assert.NoError(t, err) - assert.Equal(t, "Category", createdCategory.Name) - assert.Equal(t, "user_id_1", createdCategory.UserID) - assert.Equal(t, "team_id_1", createdCategory.TeamID) - assert.Equal(t, false, createdCategory.Collapsed) - }) - - t.Run("save collapsed category", func(t *testing.T) { - now := utils.GetMillis() - category := model.Category{ - ID: "category_id_2", - Name: "Category", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: true, - } - - err := store.CreateCategory(category) - assert.NoError(t, err) - - createdCategory, err := store.GetCategory("category_id_2") - assert.NoError(t, err) - assert.Equal(t, "Category", createdCategory.Name) - assert.Equal(t, "user_id_1", createdCategory.UserID) - assert.Equal(t, "team_id_1", createdCategory.TeamID) - assert.Equal(t, true, createdCategory.Collapsed) - }) - - t.Run("get nonexistent category", func(t *testing.T) { - category, err := store.GetCategory("nonexistent") - assert.Error(t, err) - var nf *model.ErrNotFound - assert.ErrorAs(t, err, &nf) - assert.Nil(t, category) - }) -} - -func testUpdateCategory(t *testing.T, store store.Store) { - now := utils.GetMillis() - category := model.Category{ - ID: "category_id_1", - Name: "Category 1", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - - err := store.CreateCategory(category) - assert.NoError(t, err) - - updateNow := utils.GetMillis() - updatedCategory := model.Category{ - ID: "category_id_1", - Name: "Category 1 New", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: updateNow, - DeleteAt: 0, - Collapsed: true, - } - - err = store.UpdateCategory(updatedCategory) - assert.NoError(t, err) - - fetchedCategory, err := store.GetCategory("category_id_1") - assert.NoError(t, err) - assert.Equal(t, "category_id_1", fetchedCategory.ID) - assert.Equal(t, "Category 1 New", fetchedCategory.Name) - assert.Equal(t, true, fetchedCategory.Collapsed) - - // now lets try to un-collapse the same category - updatedCategory.Collapsed = false - err = store.UpdateCategory(updatedCategory) - assert.NoError(t, err) - - fetchedCategory, err = store.GetCategory("category_id_1") - assert.NoError(t, err) - assert.Equal(t, "category_id_1", fetchedCategory.ID) - assert.Equal(t, "Category 1 New", fetchedCategory.Name) - assert.Equal(t, false, fetchedCategory.Collapsed) -} - -func testDeleteCategory(t *testing.T, store store.Store) { - now := utils.GetMillis() - category := model.Category{ - ID: "category_id_1", - Name: "Category 1", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - - err := store.CreateCategory(category) - assert.NoError(t, err) - - err = store.DeleteCategory("category_id_1", "user_id_1", "team_id_1") - assert.NoError(t, err) - - deletedCategory, err := store.GetCategory("category_id_1") - assert.NoError(t, err) - assert.Equal(t, "category_id_1", deletedCategory.ID) - assert.Equal(t, "Category 1", deletedCategory.Name) - assert.Equal(t, false, deletedCategory.Collapsed) - assert.Greater(t, deletedCategory.DeleteAt, int64(0)) -} - -func testGetUserCategories(t *testing.T, store store.Store) { - now := utils.GetMillis() - category1 := model.Category{ - ID: "category_id_1", - Name: "Category 1", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - err := store.CreateCategory(category1) - assert.NoError(t, err) - - category2 := model.Category{ - ID: "category_id_2", - Name: "Category 2", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - err = store.CreateCategory(category2) - assert.NoError(t, err) - - category3 := model.Category{ - ID: "category_id_3", - Name: "Category 2", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - err = store.CreateCategory(category3) - assert.NoError(t, err) - - userCategories, err := store.GetUserCategoryBoards("user_id_1", "team_id_1") - assert.NoError(t, err) - assert.Equal(t, 3, len(userCategories)) -} - -func testReorderCategories(t *testing.T, store store.Store) { - // setup - err := store.CreateCategory(model.Category{ - ID: "category_id_1", - Name: "Category 1", - Type: "custom", - UserID: "user_id", - TeamID: "team_id", - }) - assert.NoError(t, err) - - err = store.CreateCategory(model.Category{ - ID: "category_id_2", - Name: "Category 2", - Type: "custom", - UserID: "user_id", - TeamID: "team_id", - }) - assert.NoError(t, err) - - err = store.CreateCategory(model.Category{ - ID: "category_id_3", - Name: "Category 3", - Type: "custom", - UserID: "user_id", - TeamID: "team_id", - }) - assert.NoError(t, err) - - // verify the current order - categories, err := store.GetUserCategories("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 3, len(categories)) - - // the categories should show up in reverse insertion order (latest one first) - assert.Equal(t, "category_id_3", categories[0].ID) - assert.Equal(t, "category_id_2", categories[1].ID) - assert.Equal(t, "category_id_1", categories[2].ID) - - // re-ordering categories normally - _, err = store.ReorderCategories("user_id", "team_id", []string{ - "category_id_2", - "category_id_3", - "category_id_1", - }) - assert.NoError(t, err) - - // verify the board order - categories, err = store.GetUserCategories("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 3, len(categories)) - assert.Equal(t, "category_id_2", categories[0].ID) - assert.Equal(t, "category_id_3", categories[1].ID) - assert.Equal(t, "category_id_1", categories[2].ID) - - // lets try specifying a non existing category ID. - // It shouldn't cause any problem - _, err = store.ReorderCategories("user_id", "team_id", []string{ - "category_id_1", - "category_id_2", - "category_id_3", - "non-existing-category-id", - }) - assert.NoError(t, err) - - categories, err = store.GetUserCategories("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 3, len(categories)) - assert.Equal(t, "category_id_1", categories[0].ID) - assert.Equal(t, "category_id_2", categories[1].ID) - assert.Equal(t, "category_id_3", categories[2].ID) -} - -func testReorderCategoryBoards(t *testing.T, store store.Store) { - // setup - err := store.CreateCategory(model.Category{ - ID: "category_id_1", - Name: "Category 1", - Type: "custom", - UserID: "user_id", - TeamID: "team_id", - }) - assert.NoError(t, err) - - err = store.AddUpdateCategoryBoard("user_id", "category_id_1", []string{ - "board_id_1", - "board_id_2", - "board_id_3", - "board_id_4", - }) - assert.NoError(t, err) - - // verify current order - categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata)) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false}) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false}) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false}) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_4", Hidden: false}) - - // reordering - newOrder, err := store.ReorderCategoryBoards("category_id_1", []string{ - "board_id_3", - "board_id_1", - "board_id_2", - "board_id_4", - }) - assert.NoError(t, err) - assert.Equal(t, "board_id_3", newOrder[0]) - assert.Equal(t, "board_id_1", newOrder[1]) - assert.Equal(t, "board_id_2", newOrder[2]) - assert.Equal(t, "board_id_4", newOrder[3]) - - // verify new order - categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata)) - assert.Equal(t, "board_id_3", categoryBoards[0].BoardMetadata[0].BoardID) - assert.Equal(t, "board_id_1", categoryBoards[0].BoardMetadata[1].BoardID) - assert.Equal(t, "board_id_2", categoryBoards[0].BoardMetadata[2].BoardID) - assert.Equal(t, "board_id_4", categoryBoards[0].BoardMetadata[3].BoardID) -} diff --git a/server/services/store/storetests/categoryBoards.go b/server/services/store/storetests/categoryBoards.go deleted file mode 100644 index 6680ea070..000000000 --- a/server/services/store/storetests/categoryBoards.go +++ /dev/null @@ -1,261 +0,0 @@ -package storetests - -import ( - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - "github.com/stretchr/testify/assert" -) - -func StoreTestCategoryBoardsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("GetUserCategoryBoards", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetUserCategoryBoards(t, store) - }) - - t.Run("AddUpdateCategoryBoard", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testAddUpdateCategoryBoard(t, store) - }) - - t.Run("SetBoardVisibility", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testSetBoardVisibility(t, store) - }) -} - -func testGetUserCategoryBoards(t *testing.T, store store.Store) { - now := utils.GetMillis() - category1 := model.Category{ - ID: "category_id_1", - Name: "Category 1", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - err := store.CreateCategory(category1) - assert.NoError(t, err) - - category2 := model.Category{ - ID: "category_id_2", - Name: "Category 2", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - err = store.CreateCategory(category2) - assert.NoError(t, err) - - category3 := model.Category{ - ID: "category_id_3", - Name: "Category 3", - UserID: "user_id_1", - TeamID: "team_id_1", - CreateAt: now, - UpdateAt: now, - DeleteAt: 0, - Collapsed: false, - } - err = store.CreateCategory(category3) - assert.NoError(t, err) - - // Adding Board 1 and Board 2 to Category 1 - // The boards don't need to exists in DB for this test - err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", []string{"board_1"}) - assert.NoError(t, err) - - err = store.AddUpdateCategoryBoard("user_id_1", "category_id_1", []string{"board_2"}) - assert.NoError(t, err) - - // Adding Board 3 to Category 2 - err = store.AddUpdateCategoryBoard("user_id_1", "category_id_2", []string{"board_3"}) - assert.NoError(t, err) - - // we'll leave category 3 empty - - userCategoryBoards, err := store.GetUserCategoryBoards("user_id_1", "team_id_1") - assert.NoError(t, err) - - // we created 3 categories for the user - assert.Equal(t, 3, len(userCategoryBoards)) - - var category1BoardCategory model.CategoryBoards - var category2BoardCategory model.CategoryBoards - var category3BoardCategory model.CategoryBoards - - for i := range userCategoryBoards { - switch userCategoryBoards[i].ID { - case "category_id_1": - category1BoardCategory = userCategoryBoards[i] - case "category_id_2": - category2BoardCategory = userCategoryBoards[i] - case "category_id_3": - category3BoardCategory = userCategoryBoards[i] - } - } - - assert.NotEmpty(t, category1BoardCategory) - assert.Equal(t, 2, len(category1BoardCategory.BoardMetadata)) - - assert.NotEmpty(t, category1BoardCategory) - assert.Equal(t, 1, len(category2BoardCategory.BoardMetadata)) - - assert.NotEmpty(t, category1BoardCategory) - assert.Equal(t, 0, len(category3BoardCategory.BoardMetadata)) - - t.Run("get empty category boards", func(t *testing.T) { - userCategoryBoards, err := store.GetUserCategoryBoards("nonexistent-user-id", "nonexistent-team-id") - assert.NoError(t, err) - assert.Empty(t, userCategoryBoards) - }) -} - -func testAddUpdateCategoryBoard(t *testing.T, store store.Store) { - // creating few boards and categories to later associoate with the category - _, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{ - Boards: []*model.Board{ - { - ID: "board_id_1", - TeamID: "team_id", - }, - { - ID: "board_id_2", - TeamID: "team_id", - }, - }, - }, "user_id") - assert.NoError(t, err) - - err = store.CreateCategory(model.Category{ - ID: "category_id", - Name: "Category", - UserID: "user_id", - TeamID: "team_id", - }) - assert.NoError(t, err) - - // adding a few boards to the category - err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_2"}) - assert.NoError(t, err) - - // verify inserted data - categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, "category_id", categoryBoards[0].ID) - assert.Equal(t, 2, len(categoryBoards[0].BoardMetadata)) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_1", Hidden: false}) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_2", Hidden: false}) - - // adding new boards to the same category - err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_3"}) - assert.NoError(t, err) - - // verify inserted data - categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, "category_id", categoryBoards[0].ID) - assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata)) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_3", Hidden: false}) - - // passing empty array - err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{}) - assert.NoError(t, err) - - // verify inserted data - categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, "category_id", categoryBoards[0].ID) - assert.Equal(t, 3, len(categoryBoards[0].BoardMetadata)) - - // passing duplicate data in input - err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_4", "board_id_4"}) - assert.NoError(t, err) - - // verify inserted data - categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, "category_id", categoryBoards[0].ID) - assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata)) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_4", Hidden: false}) - - // adding already added board - err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_2"}) - assert.NoError(t, err) - - // verify inserted data - categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, "category_id", categoryBoards[0].ID) - assert.Equal(t, 4, len(categoryBoards[0].BoardMetadata)) - - // passing already added board along with a new board - err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1", "board_id_5"}) - assert.NoError(t, err) - - // verify inserted data - categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, "category_id", categoryBoards[0].ID) - assert.Equal(t, 5, len(categoryBoards[0].BoardMetadata)) - assert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: "board_id_5", Hidden: false}) -} - -func testSetBoardVisibility(t *testing.T, store store.Store) { - _, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{ - Boards: []*model.Board{ - { - ID: "board_id_1", - TeamID: "team_id", - }, - }, - }, "user_id") - assert.NoError(t, err) - - err = store.CreateCategory(model.Category{ - ID: "category_id", - Name: "Category", - UserID: "user_id", - TeamID: "team_id", - }) - assert.NoError(t, err) - - // adding a few boards to the category - err = store.AddUpdateCategoryBoard("user_id", "category_id", []string{"board_id_1"}) - assert.NoError(t, err) - - err = store.SetBoardVisibility("user_id", "category_id", "board_id_1", true) - assert.NoError(t, err) - - // verify set visibility - categoryBoards, err := store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.Equal(t, 1, len(categoryBoards)) - assert.Equal(t, "category_id", categoryBoards[0].ID) - assert.Equal(t, 1, len(categoryBoards[0].BoardMetadata)) - assert.False(t, categoryBoards[0].BoardMetadata[0].Hidden) - - err = store.SetBoardVisibility("user_id", "category_id", "board_id_1", false) - assert.NoError(t, err) - - // verify set visibility - categoryBoards, err = store.GetUserCategoryBoards("user_id", "team_id") - assert.NoError(t, err) - assert.True(t, categoryBoards[0].BoardMetadata[0].Hidden) -} diff --git a/server/services/store/storetests/cloud.go b/server/services/store/storetests/cloud.go deleted file mode 100644 index 193ae07fa..000000000 --- a/server/services/store/storetests/cloud.go +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package storetests - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - storeservice "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" -) - -func StoreTestCloudStore(t *testing.T, setup func(t *testing.T) (storeservice.Store, func())) { - t.Run("GetUsedCardsCount", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetUsedCardsCount(t, store) - }) - t.Run("TestGetCardLimitTimestamp", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetCardLimitTimestamp(t, store) - }) - t.Run("TestUpdateCardLimitTimestamp", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUpdateCardLimitTimestamp(t, store) - }) -} - -func testGetUsedCardsCount(t *testing.T, store storeservice.Store) { - userID := "user-id" - - t.Run("should return zero when no cards have been created", func(t *testing.T) { - count, err := store.GetUsedCardsCount() - require.NoError(t, err) - require.Zero(t, count) - }) - - t.Run("should correctly return the cards of all boards", func(t *testing.T) { - // two boards - for _, boardID := range []string{"board1", "board2"} { - boardType := model.BoardTypeOpen - if boardID == "board2" { - boardType = model.BoardTypePrivate - } - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: boardType, - } - - _, err := store.InsertBoard(board, userID) - require.NoError(t, err) - } - - // board 1 has three cards - for _, cardID := range []string{"card1", "card2", "card3"} { - card := &model.Block{ - ID: cardID, - ParentID: "board1", - BoardID: "board1", - Type: model.TypeCard, - } - require.NoError(t, store.InsertBlock(card, userID)) - } - - // board 2 has two cards - for _, cardID := range []string{"card4", "card5"} { - card := &model.Block{ - ID: cardID, - ParentID: "board2", - BoardID: "board2", - Type: model.TypeCard, - } - require.NoError(t, store.InsertBlock(card, userID)) - } - - count, err := store.GetUsedCardsCount() - require.NoError(t, err) - require.Equal(t, 5, count) - }) - - t.Run("should not take into account content blocks", func(t *testing.T) { - // we add a couple of content blocks - text := &model.Block{ - ID: "text-id", - ParentID: "card1", - BoardID: "board1", - Type: model.TypeText, - } - require.NoError(t, store.InsertBlock(text, userID)) - - view := &model.Block{ - ID: "view-id", - ParentID: "board1", - BoardID: "board1", - Type: model.TypeView, - } - require.NoError(t, store.InsertBlock(view, userID)) - - // and count should not change - count, err := store.GetUsedCardsCount() - require.NoError(t, err) - require.Equal(t, 5, count) - }) - - t.Run("should not take into account cards belonging to templates", func(t *testing.T) { - // we add a template with cards - templateID := "template-id" - boardTemplate := &model.Block{ - ID: templateID, - BoardID: templateID, - Type: model.TypeBoard, - Fields: map[string]interface{}{ - "isTemplate": true, - }, - } - require.NoError(t, store.InsertBlock(boardTemplate, userID)) - - for _, cardID := range []string{"card6", "card7", "card8"} { - card := &model.Block{ - ID: cardID, - ParentID: templateID, - BoardID: templateID, - Type: model.TypeCard, - } - require.NoError(t, store.InsertBlock(card, userID)) - } - - // and count should still be the same - count, err := store.GetUsedCardsCount() - require.NoError(t, err) - require.Equal(t, 5, count) - }) - - t.Run("should not take into account deleted cards", func(t *testing.T) { - // we create a ninth card on the first board - card9 := &model.Block{ - ID: "card9", - ParentID: "board1", - BoardID: "board1", - Type: model.TypeCard, - DeleteAt: utils.GetMillis(), - } - require.NoError(t, store.InsertBlock(card9, userID)) - - // and count should still be the same - count, err := store.GetUsedCardsCount() - require.NoError(t, err) - require.Equal(t, 5, count) - }) - - t.Run("should not take into account cards from deleted boards", func(t *testing.T) { - require.NoError(t, store.DeleteBoard("board2", "user-id")) - - count, err := store.GetUsedCardsCount() - require.NoError(t, err) - require.Equal(t, 3, count) - }) -} - -func testGetCardLimitTimestamp(t *testing.T, store storeservice.Store) { - t.Run("should return 0 if there is no entry in the database", func(t *testing.T) { - rawValue, err := store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey) - require.NoError(t, err) - require.Equal(t, "", rawValue) - - cardLimitTimestamp, err := store.GetCardLimitTimestamp() - require.NoError(t, err) - require.Zero(t, cardLimitTimestamp) - }) - - t.Run("should return an int64 representation of the value", func(t *testing.T) { - require.NoError(t, store.SetSystemSetting(storeservice.CardLimitTimestampSystemKey, "1234")) - - cardLimitTimestamp, err := store.GetCardLimitTimestamp() - require.NoError(t, err) - require.Equal(t, int64(1234), cardLimitTimestamp) - }) - - t.Run("should return an invalid value error if the value is not a number", func(t *testing.T) { - require.NoError(t, store.SetSystemSetting(storeservice.CardLimitTimestampSystemKey, "abc")) - - cardLimitTimestamp, err := store.GetCardLimitTimestamp() - require.ErrorContains(t, err, "card limit value is invalid") - require.Zero(t, cardLimitTimestamp) - }) -} - -func testUpdateCardLimitTimestamp(t *testing.T, store storeservice.Store) { - userID := "user-id" - - // two boards - for _, boardID := range []string{"board1", "board2"} { - boardType := model.BoardTypeOpen - if boardID == "board2" { - boardType = model.BoardTypePrivate - } - - board := &model.Board{ - ID: boardID, - TeamID: testTeamID, - Type: boardType, - } - - _, err := store.InsertBoard(board, userID) - require.NoError(t, err) - } - - // board 1 has five cards - for _, cardID := range []string{"card1", "card2", "card3", "card4", "card5"} { - card := &model.Block{ - ID: cardID, - ParentID: "board1", - BoardID: "board1", - Type: model.TypeCard, - } - require.NoError(t, store.InsertBlock(card, userID)) - time.Sleep(10 * time.Millisecond) - } - - // board 2 has five cards - for _, cardID := range []string{"card6", "card7", "card8", "card9", "card10"} { - card := &model.Block{ - ID: cardID, - ParentID: "board2", - BoardID: "board2", - Type: model.TypeCard, - } - require.NoError(t, store.InsertBlock(card, userID)) - time.Sleep(10 * time.Millisecond) - } - - t.Run("should set the timestamp to zero if the card limit is zero", func(t *testing.T) { - cardLimitTimestamp, err := store.UpdateCardLimitTimestamp(0) - require.NoError(t, err) - require.Zero(t, cardLimitTimestamp) - - cardLimitTimestampStr, err := store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey) - require.NoError(t, err) - require.Equal(t, "0", cardLimitTimestampStr) - }) - - t.Run("should correctly modify the limit several times in a row", func(t *testing.T) { - cardLimitTimestamp, err := store.UpdateCardLimitTimestamp(0) - require.NoError(t, err) - require.Zero(t, cardLimitTimestamp) - - cardLimitTimestamp, err = store.UpdateCardLimitTimestamp(10) - require.NoError(t, err) - require.NotZero(t, cardLimitTimestamp) - - cardLimitTimestampStr, err := store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey) - require.NoError(t, err) - require.NotEqual(t, "0", cardLimitTimestampStr) - - cardLimitTimestamp, err = store.UpdateCardLimitTimestamp(0) - require.NoError(t, err) - require.Zero(t, cardLimitTimestamp) - - cardLimitTimestampStr, err = store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey) - require.NoError(t, err) - require.Equal(t, "0", cardLimitTimestampStr) - }) - - t.Run("should set the correct timestamp", func(t *testing.T) { - t.Run("limit 10", func(t *testing.T) { - // we fetch the first block - card1, err := store.GetBlock("card1") - require.NoError(t, err) - - // and assert that if the limit is 10, the stored - // timestamp corresponds to the card's update_at - cardLimitTimestamp, err := store.UpdateCardLimitTimestamp(10) - require.NoError(t, err) - require.Equal(t, card1.UpdateAt, cardLimitTimestamp) - }) - - t.Run("limit 5", func(t *testing.T) { - // if the limit is 5, the timestamp should be the one from - // the sixth card (the first five are older and out of the - card6, err := store.GetBlock("card6") - require.NoError(t, err) - - cardLimitTimestamp, err := store.UpdateCardLimitTimestamp(5) - require.NoError(t, err) - require.Equal(t, card6.UpdateAt, cardLimitTimestamp) - }) - - t.Run("limit should be zero if we have less cards than the limit", func(t *testing.T) { - cardLimitTimestamp, err := store.UpdateCardLimitTimestamp(100) - require.NoError(t, err) - require.Zero(t, cardLimitTimestamp) - }) - - t.Run("we update the first inserted card and assert that with limit 1 that's the limit that is set", func(t *testing.T) { - time.Sleep(10 * time.Millisecond) - card1, err := store.GetBlock("card1") - require.NoError(t, err) - - card1.Title = "New title" - require.NoError(t, store.InsertBlock(card1, userID)) - - newCard1, err := store.GetBlock("card1") - require.NoError(t, err) - - cardLimitTimestamp, err := store.UpdateCardLimitTimestamp(1) - require.NoError(t, err) - require.Equal(t, newCard1.UpdateAt, cardLimitTimestamp) - }) - - t.Run("limit should stop applying if we remove the last card", func(t *testing.T) { - initialCardLimitTimestamp, err := store.GetCardLimitTimestamp() - require.NoError(t, err) - require.NotZero(t, initialCardLimitTimestamp) - - time.Sleep(10 * time.Millisecond) - require.NoError(t, store.DeleteBlock("card1", userID)) - - cardLimitTimestamp, err := store.UpdateCardLimitTimestamp(10) - require.NoError(t, err) - require.Zero(t, cardLimitTimestamp) - }) - }) -} diff --git a/server/services/store/storetests/compliance.go b/server/services/store/storetests/compliance.go deleted file mode 100644 index d2762038e..000000000 --- a/server/services/store/storetests/compliance.go +++ /dev/null @@ -1,294 +0,0 @@ -package storetests - -import ( - "math" - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func StoreTestComplianceHistoryStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("GetBoardsForCompliance", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBoardsForCompliance(t, store) - }) - t.Run("GetBoardsComplianceHistory", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBoardsComplianceHistory(t, store) - }) - t.Run("GetBlocksComplianceHistory", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetBlocksComplianceHistory(t, store) - }) -} - -func testGetBoardsForCompliance(t *testing.T, store store.Store) { - team1 := testTeamID - team2 := utils.NewID(utils.IDTypeTeam) - - boardsAdded1 := createTestBoards(t, store, team1, testUserID, 10) - boardsAdded2 := createTestBoards(t, store, team2, testUserID, 7) - - deleteTestBoard(t, store, boardsAdded1[0].ID, testUserID) - deleteTestBoard(t, store, boardsAdded1[1].ID, testUserID) - boardsAdded1 = boardsAdded1[2:] - - t.Run("Invalid teamID", func(t *testing.T) { - opts := model.QueryBoardsForComplianceOptions{ - TeamID: utils.NewID(utils.IDTypeTeam), - } - - boards, hasMore, err := store.GetBoardsForCompliance(opts) - - assert.Empty(t, boards) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("All teams", func(t *testing.T) { - opts := model.QueryBoardsForComplianceOptions{} - - boards, hasMore, err := store.GetBoardsForCompliance(opts) - - assert.ElementsMatch(t, extractIDs(t, boards), extractIDs(t, boardsAdded1, boardsAdded2)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("Specific team", func(t *testing.T) { - opts := model.QueryBoardsForComplianceOptions{ - TeamID: team1, - } - - boards, hasMore, err := store.GetBoardsForCompliance(opts) - - assert.ElementsMatch(t, extractIDs(t, boards), extractIDs(t, boardsAdded1)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("Pagination", func(t *testing.T) { - opts := model.QueryBoardsForComplianceOptions{ - Page: 0, - PerPage: 3, - } - - reps := 0 - allBoards := make([]*model.Board, 0, 20) - - for { - boards, hasMore, err := store.GetBoardsForCompliance(opts) - require.NoError(t, err) - require.NotEmpty(t, boards) - allBoards = append(allBoards, boards...) - - if !hasMore { - break - } - opts.Page++ - reps++ - } - - assert.ElementsMatch(t, extractIDs(t, allBoards), extractIDs(t, boardsAdded1, boardsAdded2)) - }) -} - -func testGetBoardsComplianceHistory(t *testing.T, store store.Store) { - team1 := testTeamID - team2 := utils.NewID(utils.IDTypeTeam) - - boardsTeam1 := createTestBoards(t, store, team1, testUserID, 11) - boardsTeam2 := createTestBoards(t, store, team2, testUserID, 7) - boardsAdded := make([]*model.Board, 0) - boardsAdded = append(boardsAdded, boardsTeam1...) - boardsAdded = append(boardsAdded, boardsTeam2...) - - deleteTestBoard(t, store, boardsTeam1[0].ID, testUserID) - deleteTestBoard(t, store, boardsTeam1[1].ID, testUserID) - boardsDeleted := boardsTeam1[0:2] - boardsTeam1 = boardsTeam1[2:] - - t.Log("boardsTeam1: ", extractIDs(t, boardsTeam1)) - t.Log("boardsTeam2: ", extractIDs(t, boardsTeam2)) - t.Log("boardsAdded: ", extractIDs(t, boardsAdded)) - t.Log("boardsDeleted: ", extractIDs(t, boardsDeleted)) - - t.Run("Invalid teamID", func(t *testing.T) { - opts := model.QueryBoardsComplianceHistoryOptions{ - TeamID: utils.NewID(utils.IDTypeTeam), - } - - boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts) - - assert.Empty(t, boardHistories) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("All teams, include deleted", func(t *testing.T) { - opts := model.QueryBoardsComplianceHistoryOptions{ - IncludeDeleted: true, - } - - boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts) - - // boardHistories should contain a record for each board added, plus a record for the 2 deleted. - assert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsAdded, boardsDeleted)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("All teams, exclude deleted", func(t *testing.T) { - opts := model.QueryBoardsComplianceHistoryOptions{ - IncludeDeleted: false, - } - - boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts) - - // boardHistories should contain a record for each board added, minus the two deleted. - assert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsTeam1, boardsTeam2)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("Specific team", func(t *testing.T) { - opts := model.QueryBoardsComplianceHistoryOptions{ - TeamID: team1, - } - - boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts) - - assert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsTeam1)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("Pagination", func(t *testing.T) { - opts := model.QueryBoardsComplianceHistoryOptions{ - Page: 0, - PerPage: 3, - } - - reps := 0 - allHistories := make([]*model.BoardHistory, 0) - - for { - reps++ - boardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts) - require.NoError(t, err) - require.NotEmpty(t, boardHistories) - allHistories = append(allHistories, boardHistories...) - - if !hasMore { - break - } - opts.Page++ - } - - assert.ElementsMatch(t, extractIDs(t, allHistories), extractIDs(t, boardsTeam1, boardsTeam2)) - expectedCount := len(boardsTeam1) + len(boardsTeam2) - assert.Equal(t, math.Floor(float64(expectedCount/opts.PerPage)+1), float64(reps)) - }) -} - -func testGetBlocksComplianceHistory(t *testing.T, store store.Store) { - team1 := testTeamID - team2 := utils.NewID(utils.IDTypeTeam) - - boardsTeam1 := createTestBoards(t, store, team1, testUserID, 3) - boardsTeam2 := createTestBoards(t, store, team2, testUserID, 1) - - // add cards (13 in total) - cards1Team1 := createTestCards(t, store, testUserID, boardsTeam1[0].ID, 3) - cards2Team1 := createTestCards(t, store, testUserID, boardsTeam1[1].ID, 5) - cards3Team1 := createTestCards(t, store, testUserID, boardsTeam1[2].ID, 2) - cards1Team2 := createTestCards(t, store, testUserID, boardsTeam2[0].ID, 3) - - deleteTestBoard(t, store, boardsTeam1[0].ID, testUserID) - cardsDeleted := cards1Team1 - - t.Run("Invalid teamID", func(t *testing.T) { - opts := model.QueryBlocksComplianceHistoryOptions{ - TeamID: utils.NewID(utils.IDTypeTeam), - } - - boards, hasMore, err := store.GetBlocksComplianceHistory(opts) - - assert.Empty(t, boards) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("All teams, include deleted", func(t *testing.T) { - opts := model.QueryBlocksComplianceHistoryOptions{ - IncludeDeleted: true, - } - - blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts) - - // blockHistories should have records for all cards added, plus all cards deleted - assert.ElementsMatch(t, extractIDs(t, blockHistories, nil), - extractIDs(t, cards1Team1, cards2Team1, cards3Team1, cards1Team2, cardsDeleted)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("All teams, exclude deleted", func(t *testing.T) { - opts := model.QueryBlocksComplianceHistoryOptions{} - - blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts) - - // blockHistories should have records for all cards added that have not been deleted - assert.ElementsMatch(t, extractIDs(t, blockHistories, nil), - extractIDs(t, cards2Team1, cards3Team1, cards1Team2)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("Specific team", func(t *testing.T) { - opts := model.QueryBlocksComplianceHistoryOptions{ - TeamID: team1, - } - - blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts) - - assert.ElementsMatch(t, extractIDs(t, blockHistories), extractIDs(t, cards2Team1, cards3Team1)) - assert.False(t, hasMore) - assert.NoError(t, err) - }) - - t.Run("Pagination", func(t *testing.T) { - opts := model.QueryBlocksComplianceHistoryOptions{ - Page: 0, - PerPage: 3, - } - - reps := 0 - allHistories := make([]*model.BlockHistory, 0) - - for { - reps++ - blockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts) - require.NoError(t, err) - require.NotEmpty(t, blockHistories) - allHistories = append(allHistories, blockHistories...) - - if !hasMore { - break - } - opts.Page++ - } - - assert.ElementsMatch(t, extractIDs(t, allHistories), extractIDs(t, cards2Team1, cards3Team1, cards1Team2)) - - expectedCount := len(cards2Team1) + len(cards3Team1) + len(cards1Team2) - assert.Equal(t, math.Floor(float64(expectedCount/opts.PerPage)+1), float64(reps)) - }) -} diff --git a/server/services/store/storetests/data_retention.go b/server/services/store/storetests/data_retention.go deleted file mode 100644 index d003ae52a..000000000 --- a/server/services/store/storetests/data_retention.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -package storetests - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "github.com/stretchr/testify/require" -) - -const ( - boardID = "board-id-test" - categoryID = "category-id-test" -) - -func StoreTestDataRetention(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("RunDataRetention", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - - category := model.Category{ - ID: categoryID, - Name: "TestCategory", - UserID: testUserID, - TeamID: testTeamID, - } - err := store.CreateCategory(category) - require.NoError(t, err) - - testRunDataRetention(t, store, 0) - testRunDataRetention(t, store, 2) - testRunDataRetention(t, store, 10) - }) -} - -func LoadData(t *testing.T, store store.Store) { - validBoard := model.Board{ - ID: boardID, - IsTemplate: false, - ModifiedBy: testUserID, - TeamID: testTeamID, - } - board, err := store.InsertBoard(&validBoard, testUserID) - require.NoError(t, err) - - validBlock := &model.Block{ - ID: "id-test", - BoardID: board.ID, - ModifiedBy: testUserID, - } - - validBlock2 := &model.Block{ - ID: "id-test2", - BoardID: board.ID, - ModifiedBy: testUserID, - } - validBlock3 := &model.Block{ - ID: "id-test3", - BoardID: board.ID, - ModifiedBy: testUserID, - } - - validBlock4 := &model.Block{ - ID: "id-test4", - BoardID: board.ID, - ModifiedBy: testUserID, - } - - newBlocks := []*model.Block{validBlock, validBlock2, validBlock3, validBlock4} - - err = store.InsertBlocks(newBlocks, testUserID) - require.NoError(t, err) - - member := &model.BoardMember{ - UserID: testUserID, - BoardID: boardID, - SchemeAdmin: true, - } - _, err = store.SaveMember(member) - require.NoError(t, err) - - sharing := model.Sharing{ - ID: boardID, - Enabled: true, - Token: "testToken", - } - err = store.UpsertSharing(sharing) - require.NoError(t, err) - - err = store.AddUpdateCategoryBoard(testUserID, categoryID, []string{boardID}) - require.NoError(t, err) -} - -func testRunDataRetention(t *testing.T, store store.Store, batchSize int) { - LoadData(t, store) - - blocks, err := store.GetBlocksForBoard(boardID) - require.NoError(t, err) - require.Len(t, blocks, 4) - initialCount := len(blocks) - - t.Run("test no deletions", func(t *testing.T) { - deletions, err := store.RunDataRetention(utils.GetMillisForTime(time.Now().Add(-time.Hour*1)), int64(batchSize)) - require.NoError(t, err) - require.Equal(t, int64(0), deletions) - }) - - t.Run("test all deletions", func(t *testing.T) { - deletions, err := store.RunDataRetention(utils.GetMillisForTime(time.Now().Add(time.Hour*1)), int64(batchSize)) - require.NoError(t, err) - require.True(t, deletions > int64(initialCount)) - - // expect all blocks to be deleted. - blocks, errBlocks := store.GetBlocksForBoard(boardID) - require.NoError(t, errBlocks) - require.Equal(t, 0, len(blocks)) - - // GetMemberForBoard throws error on now rows found - member, err := store.GetMemberForBoard(boardID, testUserID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err), err) - require.Nil(t, member) - - // GetSharing throws error on now rows found - sharing, err := store.GetSharing(boardID) - require.Error(t, err) - require.True(t, model.IsErrNotFound(err), err) - require.Nil(t, sharing) - - category, err := store.GetUserCategoryBoards(boardID, testTeamID) - require.NoError(t, err) - require.Empty(t, category) - }) -} diff --git a/server/services/store/storetests/files.go b/server/services/store/storetests/files.go deleted file mode 100644 index 4a757c951..000000000 --- a/server/services/store/storetests/files.go +++ /dev/null @@ -1,48 +0,0 @@ -package storetests - -import ( - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - mmModel "github.com/mattermost/mattermost/server/public/model" - - "github.com/stretchr/testify/require" -) - -func StoreTestFileStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - sqlStore, tearDown := setup(t) - defer tearDown() - - t.Run("should save and retrieve fileinfo", func(t *testing.T) { - fileInfo := &mmModel.FileInfo{ - Id: "file_info_1", - CreateAt: utils.GetMillis(), - Name: "Dunder Mifflin Sales Report 2022", - Extension: ".sales", - Size: 112233, - DeleteAt: 0, - } - - err := sqlStore.SaveFileInfo(fileInfo) - require.NoError(t, err) - - retrievedFileInfo, err := sqlStore.GetFileInfo("file_info_1") - require.NoError(t, err) - require.Equal(t, "file_info_1", retrievedFileInfo.Id) - require.Equal(t, "Dunder Mifflin Sales Report 2022", retrievedFileInfo.Name) - require.Equal(t, ".sales", retrievedFileInfo.Extension) - require.Equal(t, int64(112233), retrievedFileInfo.Size) - require.Equal(t, int64(0), retrievedFileInfo.DeleteAt) - require.False(t, retrievedFileInfo.Archived) - }) - - t.Run("should return an error on not found", func(t *testing.T) { - fileInfo, err := sqlStore.GetFileInfo("nonexistent") - require.Error(t, err) - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, fileInfo) - }) -} diff --git a/server/services/store/storetests/helpers.go b/server/services/store/storetests/helpers.go deleted file mode 100644 index 569398794..000000000 --- a/server/services/store/storetests/helpers.go +++ /dev/null @@ -1,33 +0,0 @@ -package storetests - -import ( - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/stretchr/testify/require" -) - -func InsertBlocks(t *testing.T, s store.Store, blocks []*model.Block, userID string) { - for i := range blocks { - err := s.InsertBlock(blocks[i], userID) - require.NoError(t, err) - } -} - -func DeleteBlocks(t *testing.T, s store.Store, blocks []*model.Block, modifiedBy string) { - for _, block := range blocks { - err := s.DeleteBlock(block.ID, modifiedBy) - require.NoError(t, err) - } -} - -func ContainsBlockWithID(blocks []*model.Block, blockID string) bool { - for _, block := range blocks { - if block.ID == blockID { - return true - } - } - - return false -} diff --git a/server/services/store/storetests/notificationhints.go b/server/services/store/storetests/notificationhints.go deleted file mode 100644 index 216c02f17..000000000 --- a/server/services/store/storetests/notificationhints.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package storetests - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" -) - -func StoreTestNotificationHintsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("UpsertNotificationHint", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUpsertNotificationHint(t, store) - }) - - t.Run("DeleteNotificationHint", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDeleteNotificationHint(t, store) - }) - - t.Run("GetNotificationHint", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetNotificationHint(t, store) - }) - - t.Run("GetNextNotificationHint", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetNextNotificationHint(t, store) - }) -} - -func testUpsertNotificationHint(t *testing.T, store store.Store) { - t.Run("create notification hint", func(t *testing.T) { - hint := &model.NotificationHint{ - BlockType: model.TypeCard, - BlockID: utils.NewID(utils.IDTypeBlock), - ModifiedByID: utils.NewID(utils.IDTypeUser), - } - - hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) - require.NoError(t, err, "upsert notification hint should not error") - assert.Equal(t, hint.BlockID, hintNew.BlockID) - assert.NoError(t, hintNew.IsValid()) - }) - - t.Run("duplicate notification hint", func(t *testing.T) { - hint := &model.NotificationHint{ - BlockType: model.TypeCard, - BlockID: utils.NewID(utils.IDTypeBlock), - ModifiedByID: utils.NewID(utils.IDTypeUser), - } - hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) - require.NoError(t, err, "upsert notification hint should not error") - - // sleep a short time so the notify_at timestamps won't collide - time.Sleep(time.Millisecond * 20) - - hint = &model.NotificationHint{ - BlockType: model.TypeCard, - BlockID: hintNew.BlockID, - ModifiedByID: hintNew.ModifiedByID, - } - hintDup, err := store.UpsertNotificationHint(hint, time.Second*15) - - require.NoError(t, err, "upsert notification hint should not error") - // notify_at should be updated - assert.Greater(t, hintDup.NotifyAt, hintNew.NotifyAt) - }) - - t.Run("invalid notification hint", func(t *testing.T) { - hint := &model.NotificationHint{} - - _, err := store.UpsertNotificationHint(hint, time.Second*15) - assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") - - hint.BlockType = "board" - _, err = store.UpsertNotificationHint(hint, time.Second*15) - assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") - - _, err = store.UpsertNotificationHint(hint, time.Second*15) - assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") - - hint.ModifiedByID = utils.NewID(utils.IDTypeUser) - _, err = store.UpsertNotificationHint(hint, time.Second*15) - assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") - - hint.BlockID = utils.NewID(utils.IDTypeBlock) - hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) - assert.NoError(t, err, "valid notification hint should not error") - assert.NoError(t, hintNew.IsValid(), "created notification hint should be valid") - }) -} - -func testDeleteNotificationHint(t *testing.T, store store.Store) { - t.Run("delete notification hint", func(t *testing.T) { - hint := &model.NotificationHint{ - BlockType: model.TypeCard, - BlockID: utils.NewID(utils.IDTypeBlock), - ModifiedByID: utils.NewID(utils.IDTypeUser), - } - hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) - require.NoError(t, err, "create notification hint should not error") - - // check the notification hint exists - hint, err = store.GetNotificationHint(hintNew.BlockID) - require.NoError(t, err, "get notification hint should not error") - assert.Equal(t, hintNew.BlockID, hint.BlockID) - assert.Equal(t, hintNew.CreateAt, hint.CreateAt) - - err = store.DeleteNotificationHint(hintNew.BlockID) - require.NoError(t, err, "delete notification hint should not error") - - // check the notification hint was deleted - hint, err = store.GetNotificationHint(hintNew.BlockID) - require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") - assert.Nil(t, hint) - }) - - t.Run("delete non-existent notification hint", func(t *testing.T) { - err := store.DeleteNotificationHint("bogus") - require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") - }) -} - -func testGetNotificationHint(t *testing.T, store store.Store) { - t.Run("get notification hint", func(t *testing.T) { - hint := &model.NotificationHint{ - BlockType: model.TypeCard, - BlockID: utils.NewID(utils.IDTypeBlock), - ModifiedByID: utils.NewID(utils.IDTypeUser), - } - hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) - require.NoError(t, err, "create notification hint should not error") - - // make sure notification hint can be fetched - hint, err = store.GetNotificationHint(hintNew.BlockID) - require.NoError(t, err, "get notification hint should not error") - assert.Equal(t, hintNew, hint) - }) - - t.Run("get non-existent notification hint", func(t *testing.T) { - hint, err := store.GetNotificationHint("bogus") - require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") - assert.Nil(t, hint, "hint should be nil") - }) -} - -func testGetNextNotificationHint(t *testing.T, store store.Store) { - t.Run("get next notification hint", func(t *testing.T) { - const loops = 5 - ids := [5]string{} - modifiedBy := utils.NewID(utils.IDTypeUser) - - // create some hints with unique notifyAt - for i := 0; i < loops; i++ { - hint := &model.NotificationHint{ - BlockType: model.TypeCard, - BlockID: utils.NewID(utils.IDTypeBlock), - ModifiedByID: modifiedBy, - } - hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) - require.NoError(t, err, "create notification hint should not error") - - ids[i] = hintNew.BlockID - time.Sleep(time.Millisecond * 20) // ensure next timestamp is unique - } - - // check the hints come back in the right order - notifyAt := utils.GetMillisForTime(time.Now().Add(time.Millisecond * 50)) - for i := 0; i < loops; i++ { - hint, err := store.GetNextNotificationHint(false) - require.NoError(t, err, "get next notification hint should not error") - require.NotNil(t, hint, "get next notification hint should not return nil") - assert.Equal(t, ids[i], hint.BlockID) - assert.Less(t, notifyAt, hint.NotifyAt) - notifyAt = hint.NotifyAt - - err = store.DeleteNotificationHint(hint.BlockID) - require.NoError(t, err, "delete notification hint should not error") - } - }) - - t.Run("get next notification hint from empty table", func(t *testing.T) { - // empty the table - err := emptyNotificationHintTable(store) - require.NoError(t, err, "emptying notification hint table should not error") - - for { - hint, err2 := store.GetNextNotificationHint(false) - if model.IsErrNotFound(err2) { - break - } - require.NoError(t, err2, "get next notification hint should not error") - - err2 = store.DeleteNotificationHint(hint.BlockID) - require.NoError(t, err2, "delete notification hint should not error") - } - - _, err = store.GetNextNotificationHint(false) - require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") - }) - - t.Run("get next notification hint and remove", func(t *testing.T) { - // empty the table - err := emptyNotificationHintTable(store) - require.NoError(t, err, "emptying notification hint table should not error") - - hint := &model.NotificationHint{ - BlockType: model.TypeCard, - BlockID: utils.NewID(utils.IDTypeBlock), - ModifiedByID: utils.NewID(utils.IDTypeUser), - } - hintNew, err := store.UpsertNotificationHint(hint, time.Second*1) - require.NoError(t, err, "create notification hint should not error") - - hintDeleted, err := store.GetNextNotificationHint(true) - require.NoError(t, err, "get next notification hint should not error") - require.NotNil(t, hintDeleted, "get next notification hint should not return nil") - assert.Equal(t, hintNew.BlockID, hintDeleted.BlockID) - - // should be no hint left - _, err = store.GetNextNotificationHint(false) - require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") - }) -} - -func emptyNotificationHintTable(store store.Store) error { - for { - hint, err := store.GetNextNotificationHint(false) - if model.IsErrNotFound(err) { - break - } - - if err != nil { - return err - } - - err = store.DeleteNotificationHint(hint.BlockID) - if err != nil { - return err - } - } - return nil -} diff --git a/server/services/store/storetests/session.go b/server/services/store/storetests/session.go deleted file mode 100644 index 54dc5987a..000000000 --- a/server/services/store/storetests/session.go +++ /dev/null @@ -1,107 +0,0 @@ -package storetests - -import ( - "fmt" - "testing" - "time" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/stretchr/testify/require" -) - -func StoreTestSessionStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("CreateAndGetAndDeleteSession", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testCreateAndGetAndDeleteSession(t, store) - }) - - t.Run("GetActiveUserCount", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetActiveUserCount(t, store) - }) - - t.Run("UpdateSession", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUpdateSession(t, store) - }) -} - -func testCreateAndGetAndDeleteSession(t *testing.T, store store.Store) { - session := &model.Session{ - ID: "session-id", - Token: "token", - } - - t.Run("CreateAndGetSession", func(t *testing.T) { - err := store.CreateSession(session) - require.NoError(t, err) - - got, err := store.GetSession(session.Token, 60*60) - require.NoError(t, err) - require.Equal(t, session, got) - }) - - t.Run("Get nonexistent session", func(t *testing.T) { - got, err := store.GetSession("nonexistent-token", 60*60) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, got) - }) - - t.Run("DeleteAndGetSession", func(t *testing.T) { - err := store.DeleteSession(session.ID) - require.NoError(t, err) - - _, err = store.GetSession(session.Token, 60*60) - require.Error(t, err) - }) -} - -func testGetActiveUserCount(t *testing.T, store store.Store) { - t.Run("no active user", func(t *testing.T) { - count, err := store.GetActiveUserCount(60) - require.NoError(t, err) - require.Equal(t, 0, count) - }) - - t.Run("active user", func(t *testing.T) { - // gen random count active user session - count := int(time.Now().Unix() % 10) - for i := 0; i < count; i++ { - session := &model.Session{ - ID: fmt.Sprintf("id-%d", i), - UserID: fmt.Sprintf("user-id-%d", i), - Token: fmt.Sprintf("token-%d", i), - } - err := store.CreateSession(session) - require.NoError(t, err) - } - - got, err := store.GetActiveUserCount(60) - require.NoError(t, err) - require.Equal(t, count, got) - }) -} - -func testUpdateSession(t *testing.T, store store.Store) { - session := &model.Session{ - ID: "session-id", - Token: "token", - Props: map[string]interface{}{"field1": "A"}, - } - - err := store.CreateSession(session) - require.NoError(t, err) - - // update session - session.Props["field1"] = "B" - err = store.UpdateSession(session) - require.NoError(t, err) - - got, err := store.GetSession(session.Token, 60) - require.NoError(t, err) - require.Equal(t, session, got) -} diff --git a/server/services/store/storetests/sharing.go b/server/services/store/storetests/sharing.go deleted file mode 100644 index 27e70ea18..000000000 --- a/server/services/store/storetests/sharing.go +++ /dev/null @@ -1,60 +0,0 @@ -package storetests - -import ( - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/stretchr/testify/require" -) - -func StoreTestSharingStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("UpsertSharingAndGetSharing", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUpsertSharingAndGetSharing(t, store) - }) -} - -func testUpsertSharingAndGetSharing(t *testing.T, store store.Store) { - t.Run("Insert first sharing and get it", func(t *testing.T) { - sharing := model.Sharing{ - ID: "sharing-id", - Enabled: true, - Token: "token", - ModifiedBy: testUserID, - } - - err := store.UpsertSharing(sharing) - require.NoError(t, err) - newSharing, err := store.GetSharing("sharing-id") - require.NoError(t, err) - newSharing.UpdateAt = 0 - require.Equal(t, sharing, *newSharing) - }) - t.Run("Upsert the inserted sharing and get it", func(t *testing.T) { - sharing := model.Sharing{ - ID: "sharing-id", - Enabled: true, - Token: "token2", - ModifiedBy: "user-id2", - } - - newSharing, err := store.GetSharing("sharing-id") - require.NoError(t, err) - newSharing.UpdateAt = 0 - require.NotEqual(t, sharing, *newSharing) - - err = store.UpsertSharing(sharing) - require.NoError(t, err) - newSharing, err = store.GetSharing("sharing-id") - require.NoError(t, err) - newSharing.UpdateAt = 0 - require.Equal(t, sharing, *newSharing) - }) - t.Run("Get not existing sharing", func(t *testing.T) { - _, err := store.GetSharing("not-existing") - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - }) -} diff --git a/server/services/store/storetests/subscriptions.go b/server/services/store/storetests/subscriptions.go deleted file mode 100644 index e4a213242..000000000 --- a/server/services/store/storetests/subscriptions.go +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package storetests - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" -) - -//nolint:dupl -func StoreTestSubscriptionsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("CreateSubscription", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testCreateSubscription(t, store) - }) - - t.Run("DeleteSubscription", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testDeleteSubscription(t, store) - }) - - t.Run("UndeleteSubscription", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUndeleteSubscription(t, store) - }) - - t.Run("GetSubscription", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetSubscription(t, store) - }) - - t.Run("GetSubscriptions", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetSubscriptions(t, store) - }) - - t.Run("GetSubscribersForBlock", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetSubscribersForBlock(t, store) - }) -} - -func testCreateSubscription(t *testing.T, store store.Store) { - t.Run("create subscriptions", func(t *testing.T) { - users := createTestUsers(t, store, 10) - blocks := createTestBlocks(t, store, users[0].ID, 50) - - for i, user := range users { - for j := 0; j < i; j++ { - sub := &model.Subscription{ - BlockType: blocks[j].Type, - BlockID: blocks[j].ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - subNew, err := store.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - - assert.NotZero(t, subNew.NotifiedAt) - assert.NotZero(t, subNew.CreateAt) - assert.Zero(t, subNew.DeleteAt) - } - } - - // ensure each user has the right number of subscriptions - for i, user := range users { - subs, err := store.GetSubscriptions(user.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Len(t, subs, i) - } - }) - - t.Run("duplicate subscription", func(t *testing.T) { - admin := createTestUsers(t, store, 1)[0] - user := createTestUsers(t, store, 1)[0] - block := createTestBlocks(t, store, admin.ID, 1)[0] - - sub := &model.Subscription{ - BlockType: block.Type, - BlockID: block.ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - subNew, err := store.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - - sub = &model.Subscription{ - BlockType: block.Type, - BlockID: block.ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - - subDup, err := store.CreateSubscription(sub) - require.NoError(t, err, "create duplicate subscription should not error") - - assert.Equal(t, subNew.BlockID, subDup.BlockID) - assert.Equal(t, subNew.SubscriberID, subDup.SubscriberID) - }) - - t.Run("invalid subscription", func(t *testing.T) { - admin := createTestUsers(t, store, 1)[0] - user := createTestUsers(t, store, 1)[0] - block := createTestBlocks(t, store, admin.ID, 1)[0] - - sub := &model.Subscription{} - - _, err := store.CreateSubscription(sub) - assert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, "invalid subscription should error") - - sub.BlockType = block.Type - _, err = store.CreateSubscription(sub) - assert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, "invalid subscription should error") - - sub.BlockID = block.ID - _, err = store.CreateSubscription(sub) - assert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, "invalid subscription should error") - - sub.SubscriberType = "user" - _, err = store.CreateSubscription(sub) - assert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, "invalid subscription should error") - - sub.SubscriberID = user.ID - subNew, err := store.CreateSubscription(sub) - assert.NoError(t, err, "valid subscription should not error") - - assert.NoError(t, subNew.IsValid(), "created subscription should be valid") - }) -} - -func testDeleteSubscription(t *testing.T, s store.Store) { - t.Run("delete subscription", func(t *testing.T) { - user := createTestUsers(t, s, 1)[0] - block := createTestBlocks(t, s, user.ID, 1)[0] - - sub := &model.Subscription{ - BlockType: block.Type, - BlockID: block.ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - subNew, err := s.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - - // check the subscription exists - subs, err := s.GetSubscriptions(user.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Len(t, subs, 1) - assert.Equal(t, subNew.BlockID, subs[0].BlockID) - assert.Equal(t, subNew.SubscriberID, subs[0].SubscriberID) - - err = s.DeleteSubscription(block.ID, user.ID) - require.NoError(t, err, "delete subscription should not error") - - // check the subscription was deleted - subs, err = s.GetSubscriptions(user.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Empty(t, subs) - }) - - t.Run("delete non-existent subscription", func(t *testing.T) { - err := s.DeleteSubscription("bogus", "bogus") - require.Error(t, err, "delete non-existent subscription should error") - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - }) -} - -func testUndeleteSubscription(t *testing.T, s store.Store) { - t.Run("undelete subscription", func(t *testing.T) { - user := createTestUsers(t, s, 1)[0] - block := createTestBlocks(t, s, user.ID, 1)[0] - - sub := &model.Subscription{ - BlockType: block.Type, - BlockID: block.ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - subNew, err := s.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - - // check the subscription exists - subs, err := s.GetSubscriptions(user.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Len(t, subs, 1) - assert.Equal(t, subNew.BlockID, subs[0].BlockID) - assert.Equal(t, subNew.SubscriberID, subs[0].SubscriberID) - - err = s.DeleteSubscription(block.ID, user.ID) - require.NoError(t, err, "delete subscription should not error") - - // check the subscription was deleted - subs, err = s.GetSubscriptions(user.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Empty(t, subs) - - // re-create the subscription - subUndeleted, err := s.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - - // check the undeleted subscription exists - subs, err = s.GetSubscriptions(user.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Len(t, subs, 1) - assert.Equal(t, subUndeleted.BlockID, subs[0].BlockID) - assert.Equal(t, subUndeleted.SubscriberID, subs[0].SubscriberID) - }) -} - -func testGetSubscription(t *testing.T, s store.Store) { - t.Run("get subscription", func(t *testing.T) { - user := createTestUsers(t, s, 1)[0] - block := createTestBlocks(t, s, user.ID, 1)[0] - - sub := &model.Subscription{ - BlockType: block.Type, - BlockID: block.ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - subNew, err := s.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - - // make sure subscription can be fetched - sub, err = s.GetSubscription(block.ID, user.ID) - require.NoError(t, err, "get subscription should not error") - assert.Equal(t, subNew, sub) - }) - - t.Run("get non-existent subscription", func(t *testing.T) { - sub, err := s.GetSubscription("bogus", "bogus") - require.Error(t, err, "get non-existent subscription should error") - require.True(t, model.IsErrNotFound(err), "Should be ErrNotFound compatible error") - require.Nil(t, sub, "get subscription should return nil") - }) -} - -func testGetSubscriptions(t *testing.T, store store.Store) { - t.Run("get subscriptions", func(t *testing.T) { - author := createTestUsers(t, store, 1)[0] - user := createTestUsers(t, store, 1)[0] - blocks := createTestBlocks(t, store, author.ID, 50) - - for _, block := range blocks { - sub := &model.Subscription{ - BlockType: block.Type, - BlockID: block.ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - _, err := store.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - } - - // ensure user has the right number of subscriptions - subs, err := store.GetSubscriptions(user.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Len(t, subs, len(blocks)) - - // ensure author has no subscriptions - subs, err = store.GetSubscriptions(author.ID) - require.NoError(t, err, "get subscriptions should not error") - assert.Empty(t, subs) - }) - - t.Run("get subscriptions for invalid user", func(t *testing.T) { - subs, err := store.GetSubscriptions("bogus") - require.NoError(t, err, "get subscriptions should not error") - assert.Empty(t, subs) - }) -} - -func testGetSubscribersForBlock(t *testing.T, store store.Store) { - t.Run("get subscribers for block", func(t *testing.T) { - users := createTestUsers(t, store, 50) - blocks := createTestBlocks(t, store, users[0].ID, 2) - - for _, user := range users { - sub := &model.Subscription{ - BlockType: blocks[1].Type, - BlockID: blocks[1].ID, - SubscriberType: "user", - SubscriberID: user.ID, - } - _, err := store.CreateSubscription(sub) - require.NoError(t, err, "create subscription should not error") - } - - // make sure block[1] has the right number of users subscribed - subs, err := store.GetSubscribersForBlock(blocks[1].ID) - require.NoError(t, err, "get subscribers for block should not error") - assert.Len(t, subs, 50) - - count, err := store.GetSubscribersCountForBlock(blocks[1].ID) - require.NoError(t, err, "get subscribers for block should not error") - assert.Equal(t, 50, count) - - // make sure block[0] has zero users subscribed - subs, err = store.GetSubscribersForBlock(blocks[0].ID) - require.NoError(t, err, "get subscribers for block should not error") - assert.Empty(t, subs) - - count, err = store.GetSubscribersCountForBlock(blocks[0].ID) - require.NoError(t, err, "get subscribers for block should not error") - assert.Zero(t, count) - }) - - t.Run("get subscribers for invalid block", func(t *testing.T) { - subs, err := store.GetSubscribersForBlock("bogus") - require.NoError(t, err, "get subscribers for block should not error") - assert.Empty(t, subs) - }) -} diff --git a/server/services/store/storetests/system.go b/server/services/store/storetests/system.go deleted file mode 100644 index af18bdb94..000000000 --- a/server/services/store/storetests/system.go +++ /dev/null @@ -1,65 +0,0 @@ -package storetests - -import ( - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/stretchr/testify/require" -) - -// these system settings are created when running the data migrations, -// so they will be present after the tests setup. -var dataMigrationSystemSettings = map[string]string{ - "UniqueIDsMigrationComplete": "true", - "CategoryUuidIdMigrationComplete": "true", - "DeDuplicateCategoryBoardTableComplete": "true", -} - -func addBaseSettings(m map[string]string) map[string]string { - r := map[string]string{} - for k, v := range dataMigrationSystemSettings { - r[k] = v - } - for k, v := range m { - r[k] = v - } - return r -} - -func StoreTestSystemStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("SetGetSystemSettings", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testSetGetSystemSettings(t, store) - }) -} - -func testSetGetSystemSettings(t *testing.T, store store.Store) { - t.Run("Get empty settings", func(t *testing.T) { - settings, err := store.GetSystemSettings() - require.NoError(t, err) - require.Equal(t, dataMigrationSystemSettings, settings) - }) - - t.Run("Set, update and get multiple settings", func(t *testing.T) { - err := store.SetSystemSetting("test-1", "test-value-1") - require.NoError(t, err) - err = store.SetSystemSetting("test-2", "test-value-2") - require.NoError(t, err) - settings, err := store.GetSystemSettings() - require.NoError(t, err) - require.Equal(t, addBaseSettings(map[string]string{"test-1": "test-value-1", "test-2": "test-value-2"}), settings) - - err = store.SetSystemSetting("test-2", "test-value-updated-2") - require.NoError(t, err) - settings, err = store.GetSystemSettings() - require.NoError(t, err) - require.Equal(t, addBaseSettings(map[string]string{"test-1": "test-value-1", "test-2": "test-value-updated-2"}), settings) - }) - - t.Run("Get a single setting", func(t *testing.T) { - value, err := store.GetSystemSetting("test-1") - require.NoError(t, err) - require.Equal(t, "test-value-1", value) - }) -} diff --git a/server/services/store/storetests/teams.go b/server/services/store/storetests/teams.go deleted file mode 100644 index 9ccddc030..000000000 --- a/server/services/store/storetests/teams.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package storetests - -import ( - "fmt" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - - "testing" - - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-plugin-boards/server/services/store" -) - -func StoreTestTeamStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("GetTeam", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetTeam(t, store) - }) - - t.Run("UpsertTeamSignupToken", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUpsertTeamSignupToken(t, store) - }) - - t.Run("UpsertTeamSettings", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testUpsertTeamSettings(t, store) - }) - - t.Run("GetAllTeams", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetAllTeams(t, store) - }) -} - -func testGetTeam(t *testing.T, store store.Store) { - t.Run("Nonexistent team", func(t *testing.T) { - got, err := store.GetTeam("nonexistent-id") - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - require.Nil(t, got) - }) - - t.Run("Valid team", func(t *testing.T) { - teamID := "0" - team := &model.Team{ - ID: teamID, - SignupToken: utils.NewID(utils.IDTypeToken), - } - - err := store.UpsertTeamSignupToken(*team) - require.NoError(t, err) - - got, err := store.GetTeam(teamID) - require.NoError(t, err) - require.Equal(t, teamID, got.ID) - }) -} - -func testUpsertTeamSignupToken(t *testing.T, store store.Store) { - t.Run("Insert and update team with signup token", func(t *testing.T) { - teamID := "0" - team := &model.Team{ - ID: teamID, - SignupToken: utils.NewID(utils.IDTypeToken), - } - - // insert - err := store.UpsertTeamSignupToken(*team) - require.NoError(t, err) - - got, err := store.GetTeam(teamID) - require.NoError(t, err) - require.Equal(t, team.ID, got.ID) - require.Equal(t, team.SignupToken, got.SignupToken) - - // update signup token - team.SignupToken = utils.NewID(utils.IDTypeToken) - err = store.UpsertTeamSignupToken(*team) - require.NoError(t, err) - - got, err = store.GetTeam(teamID) - require.NoError(t, err) - require.Equal(t, team.ID, got.ID) - require.Equal(t, team.SignupToken, got.SignupToken) - }) -} - -func testUpsertTeamSettings(t *testing.T, store store.Store) { - t.Run("Insert and update team with settings", func(t *testing.T) { - teamID := "0" - team := &model.Team{ - ID: teamID, - Settings: map[string]interface{}{ - "field1": "A", - }, - } - - // insert - err := store.UpsertTeamSettings(*team) - require.NoError(t, err) - - got, err := store.GetTeam(teamID) - require.NoError(t, err) - require.Equal(t, team.ID, got.ID) - require.Equal(t, team.Settings, got.Settings) - - // update settings - team.Settings = map[string]interface{}{ - "field1": "B", - } - err = store.UpsertTeamSettings(*team) - require.NoError(t, err) - - got2, err := store.GetTeam(teamID) - require.NoError(t, err) - require.Equal(t, team.ID, got2.ID) - require.Equal(t, team.Settings, got2.Settings) - require.Equal(t, got.SignupToken, got2.SignupToken) - }) -} - -func testGetAllTeams(t *testing.T, store store.Store) { - t.Run("No teams response", func(t *testing.T) { - got, err := store.GetAllTeams() - require.NoError(t, err) - require.Empty(t, got) - }) - - t.Run("Insert multiple team and get all teams", func(t *testing.T) { - // insert - teamCount := 10 - for i := 0; i < teamCount; i++ { - teamID := fmt.Sprintf("%d", i) - team := &model.Team{ - ID: teamID, - SignupToken: utils.NewID(utils.IDTypeToken), - } - - err := store.UpsertTeamSignupToken(*team) - require.NoError(t, err) - } - - got, err := store.GetAllTeams() - require.NoError(t, err) - require.Len(t, got, teamCount) - }) -} diff --git a/server/services/store/storetests/users.go b/server/services/store/storetests/users.go deleted file mode 100644 index 177517fda..000000000 --- a/server/services/store/storetests/users.go +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package storetests - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" -) - -//nolint:dupl -func StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { - t.Run("GetUsersByTeam", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetUsersByTeam(t, store) - }) - - t.Run("CreateAndGetUser", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testCreateAndGetUser(t, store) - }) - - t.Run("GetUsersList", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testGetUsersList(t, store) - }) - - t.Run("CreateAndUpdateUser", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testCreateAndUpdateUser(t, store) - }) - - t.Run("CreateAndGetRegisteredUserCount", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testCreateAndGetRegisteredUserCount(t, store) - }) - - t.Run("TestPatchUserProps", func(t *testing.T) { - store, tearDown := setup(t) - defer tearDown() - testPatchUserProps(t, store) - }) -} - -func testGetUsersByTeam(t *testing.T, store store.Store) { - t.Run("GetTeamUsers", func(t *testing.T) { - users, err := store.GetUsersByTeam("team_1", "", false, false) - require.Equal(t, 0, len(users)) - require.NoError(t, err) - - userID := utils.NewID(utils.IDTypeUser) - - user, err := store.CreateUser(&model.User{ - ID: userID, - Username: "darth.vader", - }) - require.NoError(t, err) - require.NotNil(t, user) - require.Equal(t, userID, user.ID) - require.Equal(t, "darth.vader", user.Username) - - defer func() { - _, _ = store.UpdateUser(&model.User{ - ID: userID, - DeleteAt: utils.GetMillis(), - }) - }() - - users, err = store.GetUsersByTeam("team_1", "", false, false) - require.Equal(t, 1, len(users)) - require.Equal(t, "darth.vader", users[0].Username) - require.NoError(t, err) - }) -} - -func testCreateAndGetUser(t *testing.T, store store.Store) { - user := &model.User{ - ID: utils.NewID(utils.IDTypeUser), - Username: "damao", - Email: "mock@email.com", - } - - t.Run("CreateUser", func(t *testing.T) { - newUser, err := store.CreateUser(user) - require.NoError(t, err) - require.NotNil(t, newUser) - }) - - t.Run("GetUserByID", func(t *testing.T) { - got, err := store.GetUserByID(user.ID) - require.NoError(t, err) - require.Equal(t, user.ID, got.ID) - require.Equal(t, user.Username, got.Username) - require.Equal(t, user.Email, got.Email) - }) - - t.Run("GetUserByID nonexistent", func(t *testing.T) { - got, err := store.GetUserByID("nonexistent-id") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, got) - }) - - t.Run("GetUserByUsername", func(t *testing.T) { - got, err := store.GetUserByUsername(user.Username) - require.NoError(t, err) - require.Equal(t, user.ID, got.ID) - require.Equal(t, user.Username, got.Username) - require.Equal(t, user.Email, got.Email) - }) - - t.Run("GetUserByUsername nonexistent", func(t *testing.T) { - got, err := store.GetUserByID("nonexistent-username") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, got) - }) - - t.Run("GetUserByEmail", func(t *testing.T) { - got, err := store.GetUserByEmail(user.Email) - require.NoError(t, err) - require.Equal(t, user.ID, got.ID) - require.Equal(t, user.Username, got.Username) - require.Equal(t, user.Email, got.Email) - }) - - t.Run("GetUserByEmail nonexistent", func(t *testing.T) { - got, err := store.GetUserByID("nonexistent-email") - var nf *model.ErrNotFound - require.ErrorAs(t, err, &nf) - require.Nil(t, got) - }) -} - -func testGetUsersList(t *testing.T, store store.Store) { - for _, id := range []string{"user1", "user2"} { - user := &model.User{ - ID: id, - Username: fmt.Sprintf("%s-username", id), - Email: fmt.Sprintf("%s@sample.com", id), - } - newUser, err := store.CreateUser(user) - require.NoError(t, err) - require.NotNil(t, newUser) - } - - testCases := []struct { - Name string - UserIDs []string - ExpectedError bool - ExpectedIDs []string - }{ - { - Name: "all of the IDs are found", - UserIDs: []string{"user1", "user2"}, - ExpectedError: false, - ExpectedIDs: []string{"user1", "user2"}, - }, - { - Name: "some of the IDs are found", - UserIDs: []string{"user2", "non-existent"}, - ExpectedError: true, - ExpectedIDs: []string{"user2"}, - }, - { - Name: "none of the IDs are found", - UserIDs: []string{"non-existent-1", "non-existent-2"}, - ExpectedError: true, - ExpectedIDs: []string{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - users, err := store.GetUsersList(tc.UserIDs, false, false) - if tc.ExpectedError { - require.Error(t, err) - require.True(t, model.IsErrNotFound(err)) - } else { - require.NoError(t, err) - } - - userIDs := []string{} - for _, user := range users { - userIDs = append(userIDs, user.ID) - } - require.ElementsMatch(t, tc.ExpectedIDs, userIDs) - }) - } -} - -func testCreateAndUpdateUser(t *testing.T, store store.Store) { - user := &model.User{ - ID: utils.NewID(utils.IDTypeUser), - } - newUser, err := store.CreateUser(user) - require.NoError(t, err) - require.NotNil(t, newUser) - - t.Run("UpdateUser", func(t *testing.T) { - user.Username = "damao" - user.Email = "mock@email.com" - uUser, err := store.UpdateUser(user) - require.NoError(t, err) - require.NotNil(t, uUser) - require.Equal(t, user.Username, uUser.Username) - require.Equal(t, user.Email, uUser.Email) - - got, err := store.GetUserByID(user.ID) - require.NoError(t, err) - require.Equal(t, user.ID, got.ID) - require.Equal(t, user.Username, got.Username) - require.Equal(t, user.Email, got.Email) - }) - - t.Run("UpdateUserPassword", func(t *testing.T) { - newPassword := utils.NewID(utils.IDTypeNone) - err := store.UpdateUserPassword(user.Username, newPassword) - require.NoError(t, err) - - got, err := store.GetUserByUsername(user.Username) - require.NoError(t, err) - require.Equal(t, user.Username, got.Username) - require.Equal(t, newPassword, got.Password) - }) - - t.Run("UpdateUserPasswordByID", func(t *testing.T) { - newPassword := utils.NewID(utils.IDTypeNone) - err := store.UpdateUserPasswordByID(user.ID, newPassword) - require.NoError(t, err) - - got, err := store.GetUserByID(user.ID) - require.NoError(t, err) - require.Equal(t, user.ID, got.ID) - require.Equal(t, newPassword, got.Password) - }) -} - -func testCreateAndGetRegisteredUserCount(t *testing.T, store store.Store) { - randomN := int(time.Now().Unix() % 10) - for i := 0; i < randomN; i++ { - user, err := store.CreateUser(&model.User{ - ID: utils.NewID(utils.IDTypeUser), - }) - require.NoError(t, err) - require.NotNil(t, user) - } - - got, err := store.GetRegisteredUserCount() - require.NoError(t, err) - require.Equal(t, randomN, got) -} - -func testPatchUserProps(t *testing.T, store store.Store) { - user := &model.User{ - ID: utils.NewID(utils.IDTypeUser), - } - newUser, err := store.CreateUser(user) - require.NoError(t, err) - require.NotNil(t, newUser) - - key1 := "new_key_1" - key2 := "new_key_2" - key3 := "new_key_3" - - // Only update props - patch := model.UserPreferencesPatch{ - UpdatedFields: map[string]string{ - key1: "new_value_1", - key2: "new_value_2", - key3: "new_value_3", - }, - } - - userPreferences, err := store.PatchUserPreferences(user.ID, patch) - require.NoError(t, err) - require.Equal(t, 3, len(userPreferences)) - - for _, preference := range userPreferences { - switch preference.Name { - case key1: - require.Equal(t, "new_value_1", preference.Value) - case key2: - require.Equal(t, "new_value_2", preference.Value) - case key3: - require.Equal(t, "new_value_3", preference.Value) - } - } - - // Delete a prop - patch = model.UserPreferencesPatch{ - DeletedFields: []string{ - key1, - }, - } - - userPreferences, err = store.PatchUserPreferences(user.ID, patch) - require.NoError(t, err) - - for _, preference := range userPreferences { - switch preference.Name { - case key1: - t.Errorf("new_key_1 shouldn't exist in user preference as we just deleted it") - case key2: - require.Equal(t, "new_value_2", preference.Value) - case key3: - require.Equal(t, "new_value_3", preference.Value) - } - } - - // update and delete together - patch = model.UserPreferencesPatch{ - UpdatedFields: map[string]string{ - key3: "new_value_3_new_again", - }, - DeletedFields: []string{ - key2, - }, - } - userPreferences, err = store.PatchUserPreferences(user.ID, patch) - require.NoError(t, err) - - for _, preference := range userPreferences { - switch preference.Name { - case key1: - t.Errorf("new_key_1 shouldn't exist in user preference as we just deleted it") - case key2: - t.Errorf("new_key_2 shouldn't exist in user preference as we just deleted it") - case key3: - require.Equal(t, "new_value_3_new_again", preference.Value) - } - } -} diff --git a/server/services/store/storetests/util.go b/server/services/store/storetests/util.go deleted file mode 100644 index 7959485d0..000000000 --- a/server/services/store/storetests/util.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package storetests - -import ( - "fmt" - "sort" - "testing" - - "github.com/mattermost/mattermost-plugin-boards/server/model" - "github.com/mattermost/mattermost-plugin-boards/server/services/store" - "github.com/mattermost/mattermost-plugin-boards/server/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func createTestUsers(t *testing.T, store store.Store, num int) []*model.User { - var users []*model.User - for i := 0; i < num; i++ { - user := &model.User{ - ID: utils.NewID(utils.IDTypeUser), - Username: fmt.Sprintf("mooncake.%d", i), - Email: fmt.Sprintf("mooncake.%d@example.com", i), - } - newUser, err := store.CreateUser(user) - require.NoError(t, err) - require.NotNil(t, newUser) - - users = append(users, user) - } - return users -} - -func createTestBlocks(t *testing.T, store store.Store, userID string, num int) []*model.Block { - var blocks []*model.Block - for i := 0; i < num; i++ { - block := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: utils.NewID(utils.IDTypeBoard), - Type: model.TypeCard, - CreatedBy: userID, - } - err := store.InsertBlock(block, userID) - require.NoError(t, err) - - blocks = append(blocks, block) - } - return blocks -} - -func createTestBlocksForCard(t *testing.T, store store.Store, cardID string, num int) []*model.Block { - card, err := store.GetBlock(cardID) - require.NoError(t, err) - assert.EqualValues(t, model.TypeCard, card.Type) - - var blocks []*model.Block - for i := 0; i < num; i++ { - block := &model.Block{ - ID: utils.NewID(utils.IDTypeBlock), - BoardID: card.BoardID, - Type: model.TypeText, - CreatedBy: card.CreatedBy, - ParentID: card.ID, - Title: fmt.Sprintf("text %d", i), - } - err := store.InsertBlock(block, card.CreatedBy) - require.NoError(t, err) - - blocks = append(blocks, block) - } - return blocks -} - -//nolint:unparam -func createTestCards(t *testing.T, store store.Store, userID string, boardID string, num int) []*model.Block { - var blocks []*model.Block - for i := 0; i < num; i++ { - block := &model.Block{ - ID: utils.NewID(utils.IDTypeCard), - BoardID: boardID, - ParentID: boardID, - Type: model.TypeCard, - CreatedBy: userID, - Title: fmt.Sprintf("card %d", i), - } - err := store.InsertBlock(block, userID) - require.NoError(t, err) - - blocks = append(blocks, block) - } - return blocks -} - -//nolint:unparam -func createTestBoards(t *testing.T, store store.Store, teamID string, userID string, num int) []*model.Board { - var boards []*model.Board - for i := 0; i < num; i++ { - board := &model.Board{ - ID: utils.NewID(utils.IDTypeBoard), - TeamID: teamID, - Type: "O", - CreatedBy: userID, - Title: fmt.Sprintf("board %d", i), - } - boardNew, err := store.InsertBoard(board, userID) - require.NoError(t, err) - - boards = append(boards, boardNew) - } - return boards -} - -//nolint:unparam -func deleteTestBoard(t *testing.T, store store.Store, boardID string, userID string) { - err := store.DeleteBoard(boardID, userID) - require.NoError(t, err) -} - -// extractIDs is a test helper that extracts a sorted slice of IDs from slices of various struct types. -// Might have used generics here except that would require implementing a `GetID` method on each type. -func extractIDs(t *testing.T, arr ...any) []string { - ids := make([]string, 0) - - for _, item := range arr { - if item == nil { - continue - } - - switch tarr := item.(type) { - case []*model.Board: - for _, b := range tarr { - if b != nil { - ids = append(ids, b.ID) - } - } - case []*model.BoardHistory: - for _, bh := range tarr { - ids = append(ids, bh.ID) - } - case []*model.Block: - for _, b := range tarr { - if b != nil { - ids = append(ids, b.ID) - } - } - case []*model.BlockHistory: - for _, bh := range tarr { - ids = append(ids, bh.ID) - } - default: - t.Errorf("unsupported type %T extracting board ID", item) - } - } - - // sort the ids to make it easier to compare lists of ids visually. - sort.Strings(ids) - return ids -} diff --git a/server/ws/server.go b/server/ws/server.go index 39ec010e6..16d2b432f 100644 --- a/server/ws/server.go +++ b/server/ws/server.go @@ -49,7 +49,6 @@ type Server struct { listenersByBlock map[string][]*websocketSession mu sync.RWMutex auth *auth.Auth - singleUserToken string isMattermostAuth bool logger mlog.LoggerIFace store Store @@ -68,7 +67,7 @@ func (wss *websocketSession) isAuthenticated() bool { } // NewServer creates a new Server. -func NewServer(auth *auth.Auth, singleUserToken string, isMattermostAuth bool, logger mlog.LoggerIFace, store Store) *Server { +func NewServer(auth *auth.Auth, logger mlog.LoggerIFace, store Store) *Server { return &Server{ listeners: make(map[*websocketSession]bool), listenersByTeam: make(map[string][]*websocketSession), @@ -79,8 +78,7 @@ func NewServer(auth *auth.Auth, singleUserToken string, isMattermostAuth bool, l }, }, auth: auth, - singleUserToken: singleUserToken, - isMattermostAuth: isMattermostAuth, + isMattermostAuth: true, logger: logger, store: store, } @@ -212,22 +210,10 @@ func (ws *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) { mlog.String("teamID", command.TeamID), mlog.Stringer("client", wsSession.conn.RemoteAddr()), ) - - // if single user mode, check that the userID is valid and - // assume that the user has permission if so - if len(ws.singleUserToken) != 0 { - if wsSession.userID != model.SingleUser { - continue - } - - // if not in single user mode validate that the session - // has permissions to the team - } else { - ws.logger.Debug("Not single user mode") - if !ws.auth.DoesUserHaveTeamAccess(wsSession.userID, command.TeamID) { - ws.logger.Error("WS user doesn't have team access", mlog.String("teamID", command.TeamID), mlog.String("userID", wsSession.userID)) - continue - } + ws.logger.Debug("Not single user mode") + if !ws.auth.DoesUserHaveTeamAccess(wsSession.userID, command.TeamID) { + ws.logger.Error("WS user doesn't have team access", mlog.String("teamID", command.TeamID), mlog.String("userID", wsSession.userID)) + continue } ws.subscribeListenerToTeam(wsSession, command.TeamID) @@ -416,20 +402,7 @@ func (ws *Server) removeListenerFromBlock(listener *websocketSession, blockID st } func (ws *Server) getUserIDForToken(token string) string { - if len(ws.singleUserToken) > 0 { - if token == ws.singleUserToken { - return model.SingleUser - } else { - return "" - } - } - - session, err := ws.auth.GetSession(token) - if session == nil || err != nil { - return "" - } - - return session.UserID + return "" } func (ws *Server) authenticateListener(wsSession *websocketSession, token string) { diff --git a/server/ws/server_test.go b/server/ws/server_test.go index d8a1d89a6..07bbb27d0 100644 --- a/server/ws/server_test.go +++ b/server/ws/server_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/mattermost/mattermost-plugin-boards/server/auth" - "github.com/mattermost/mattermost-plugin-boards/server/model" "github.com/mattermost/mattermost/server/public/shared/mlog" @@ -14,7 +13,7 @@ import ( ) func TestTeamSubscription(t *testing.T) { - server := NewServer(&auth.Auth{}, "token", false, &mlog.Logger{}, nil) + server := NewServer(&auth.Auth{}, &mlog.Logger{}, nil) session := &websocketSession{ conn: &websocket.Conn{}, mu: sync.Mutex{}, @@ -146,7 +145,7 @@ func TestTeamSubscription(t *testing.T) { } func TestBlocksSubscription(t *testing.T) { - server := NewServer(&auth.Auth{}, "token", false, &mlog.Logger{}, nil) + server := NewServer(&auth.Auth{}, &mlog.Logger{}, nil) session := &websocketSession{ conn: &websocket.Conn{}, mu: sync.Mutex{}, @@ -263,21 +262,3 @@ func TestBlocksSubscription(t *testing.T) { require.Empty(t, server.listenersByBlock[blockID3]) }) } - -func TestGetUserIDForTokenInSingleUserMode(t *testing.T) { - singleUserToken := "single-user-token" - server := NewServer(&auth.Auth{}, "token", false, &mlog.Logger{}, nil) - server.singleUserToken = singleUserToken - - t.Run("Should return nothing if the token is empty", func(t *testing.T) { - require.Empty(t, server.getUserIDForToken("")) - }) - - t.Run("Should return nothing if the token is invalid", func(t *testing.T) { - require.Empty(t, server.getUserIDForToken("invalid-token")) - }) - - t.Run("Should return the single user ID if the token is correct", func(t *testing.T) { - require.Equal(t, model.SingleUser, server.getUserIDForToken(singleUserToken)) - }) -} diff --git a/webapp/__mocks__/fileMock.js b/webapp/__mocks__/fileMock.js new file mode 100644 index 000000000..fe29b3a27 --- /dev/null +++ b/webapp/__mocks__/fileMock.js @@ -0,0 +1,3 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +module.exports = 'test-file-stub'; diff --git a/webapp/__mocks__/styleMock.js b/webapp/__mocks__/styleMock.js new file mode 100644 index 000000000..a8ab4d252 --- /dev/null +++ b/webapp/__mocks__/styleMock.js @@ -0,0 +1,3 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +module.exports = {}; diff --git a/webapp/src/components/__snapshots__/addContentMenuItem.test.tsx.snap b/webapp/src/components/__snapshots__/addContentMenuItem.test.tsx.snap new file mode 100644 index 000000000..60bac1973 --- /dev/null +++ b/webapp/src/components/__snapshots__/addContentMenuItem.test.tsx.snap @@ -0,0 +1,163 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/addContentMenuItem return a checkbox menu item 1`] = ` +
+ +`; + +exports[`components/addContentMenuItem return a divider menu item 1`] = ` +
+ +`; + +exports[`components/addContentMenuItem return a text menu item 1`] = ` +
+ +`; + +exports[`components/addContentMenuItem return an error and empty element from unknown type 1`] = `
`; + +exports[`components/addContentMenuItem return an image menu item 1`] = ` +
+ +`; diff --git a/webapp/src/components/__snapshots__/blockIconSelector.test.tsx.snap b/webapp/src/components/__snapshots__/blockIconSelector.test.tsx.snap new file mode 100644 index 000000000..eab155068 --- /dev/null +++ b/webapp/src/components/__snapshots__/blockIconSelector.test.tsx.snap @@ -0,0 +1,199 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/blockIconSelector return an icon correctly 1`] = ` +
+
+ +
+
+`; + +exports[`components/blockIconSelector return menu on click 1`] = ` +
+
+