From 001a4448ca5fb0018eeb442915b473b121c04bf3 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Wed, 6 Jan 2016 21:09:05 -0600 Subject: PLT-1429 adding sql storage for slash commands --- api/command.go | 1260 +++++++++++++++++++-------------------- api/command_test.go | 400 ++++++------- model/command.go | 121 +++- model/command_test.go | 90 ++- store/sql_command_store.go | 174 ++++++ store/sql_command_store_test.go | 155 +++++ store/sql_store.go | 8 + store/store.go | 10 + 8 files changed, 1359 insertions(+), 859 deletions(-) create mode 100644 store/sql_command_store.go create mode 100644 store/sql_command_store_test.go diff --git a/api/command.go b/api/command.go index db57f0bae..cff30cdbb 100644 --- a/api/command.go +++ b/api/command.go @@ -4,643 +4,643 @@ package api import ( - "io" - "net/http" - "path" - "strconv" - "strings" - "time" + // "io" + // "net/http" + // "path" + // "strconv" + // "strings" + // "time" l4g "code.google.com/p/log4go" "github.com/gorilla/mux" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" + // "github.com/mattermost/platform/model" + // "github.com/mattermost/platform/utils" ) -type commandHandler func(c *Context, command *model.Command) bool - -var ( - cmds = map[string]string{ - "logoutCommand": "/logout", - "joinCommand": "/join", - "loadTestCommand": "/loadtest", - "echoCommand": "/echo", - "shrugCommand": "/shrug", - "meCommand": "/me", - } - commands = []commandHandler{ - logoutCommand, - joinCommand, - loadTestCommand, - echoCommand, - shrugCommand, - meCommand, - } - commandNotImplementedErr = model.NewAppError("checkCommand", "Command not implemented", "") -) -var echoSem chan bool +// type commandHandler func(c *Context, command *model.Command) bool + +// var ( +// cmds = map[string]string{ +// "logoutCommand": "/logout", +// "joinCommand": "/join", +// "loadTestCommand": "/loadtest", +// "echoCommand": "/echo", +// "shrugCommand": "/shrug", +// "meCommand": "/me", +// } +// commands = []commandHandler{ +// logoutCommand, +// joinCommand, +// loadTestCommand, +// echoCommand, +// shrugCommand, +// meCommand, +// } +// commandNotImplementedErr = model.NewAppError("checkCommand", "Command not implemented", "") +// ) +// var echoSem chan bool func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") - r.Handle("/command", ApiUserRequired(command)).Methods("POST") -} - -func command(c *Context, w http.ResponseWriter, r *http.Request) { - - props := model.MapFromJson(r.Body) - - command := &model.Command{ - Command: strings.TrimSpace(props["command"]), - ChannelId: strings.TrimSpace(props["channelId"]), - Suggest: props["suggest"] == "true", - Suggestions: make([]*model.SuggestCommand, 0, 128), - } - - checkCommand(c, command) - if c.Err != nil { - if c.Err != commandNotImplementedErr { - return - } else { - c.Err = nil - command.Response = model.RESP_NOT_IMPLEMENTED - w.Write([]byte(command.ToJson())) - return - } - } else { - w.Write([]byte(command.ToJson())) - } -} - -func checkCommand(c *Context, command *model.Command) bool { - - if len(command.Command) == 0 || strings.Index(command.Command, "/") != 0 { - c.Err = model.NewAppError("checkCommand", "Command must start with /", "") - return false - } - - if len(command.ChannelId) > 0 { - cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId) - - if !c.HasPermissionsToChannel(cchan, "checkCommand") { - return true - } - } - - if !command.Suggest { - implemented := false - for _, cmd := range cmds { - bounds := len(cmd) - if len(command.Command) < bounds { - continue - } - if command.Command[:bounds] == cmd { - implemented = true - } - } - if !implemented { - c.Err = commandNotImplementedErr - return false - } - } - - for _, v := range commands { - - if v(c, command) || c.Err != nil { - return true - } - } - - return false -} - -func logoutCommand(c *Context, command *model.Command) bool { - - cmd := cmds["logoutCommand"] - - if strings.Index(command.Command, cmd) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) - - if !command.Suggest { - command.GotoLocation = "/logout" - command.Response = model.RESP_EXECUTED - return true - } - - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) - } - - return false -} - -func echoCommand(c *Context, command *model.Command) bool { - cmd := cmds["echoCommand"] - maxThreads := 100 - - if !command.Suggest && strings.Index(command.Command, cmd) == 0 { - parameters := strings.SplitN(command.Command, " ", 2) - if len(parameters) != 2 || len(parameters[1]) == 0 { - return false - } - message := strings.Trim(parameters[1], " ") - delay := 0 - if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { - if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { - delay = checkDelay - } - message = message[1:endMsg] - } else if strings.Index(message, " ") > -1 { - delayIdx := strings.LastIndex(message, " ") - delayStr := strings.Trim(message[delayIdx:], " ") - - if checkDelay, err := strconv.Atoi(delayStr); err == nil { - delay = checkDelay - message = message[:delayIdx] - } - } - - if delay > 10000 { - c.Err = model.NewAppError("echoCommand", "Delays must be under 10000 seconds", "") - return false - } - - if echoSem == nil { - // We want one additional thread allowed so we never reach channel lockup - echoSem = make(chan bool, maxThreads+1) - } - - if len(echoSem) >= maxThreads { - c.Err = model.NewAppError("echoCommand", "High volume of echo request, cannot process request", "") - return false - } - - echoSem <- true - go func() { - defer func() { <-echoSem }() - post := &model.Post{} - post.ChannelId = command.ChannelId - post.Message = message - - time.Sleep(time.Duration(delay) * time.Second) - - if _, err := CreatePost(c, post, true); err != nil { - l4g.Error("Unable to create /echo post, err=%v", err) - } - }() - - command.Response = model.RESP_EXECUTED - return true - - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo back text from your account, /echo \"message\" [delay in seconds]"}) - } - - return false -} - -func meCommand(c *Context, command *model.Command) bool { - cmd := cmds["meCommand"] - - if !command.Suggest && strings.Index(command.Command, cmd) == 0 { - message := "" - - parameters := strings.SplitN(command.Command, " ", 2) - if len(parameters) > 1 { - message += "*" + parameters[1] + "*" - } - - post := &model.Post{} - post.Message = message - post.ChannelId = command.ChannelId - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error("Unable to create /me post post, err=%v", err) - return false - } - command.Response = model.RESP_EXECUTED - return true - - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Do an action, /me [message]"}) - } - - return false + // r.Handle("/command", ApiUserRequired(command)).Methods("POST") } -func shrugCommand(c *Context, command *model.Command) bool { - cmd := cmds["shrugCommand"] - - if !command.Suggest && strings.Index(command.Command, cmd) == 0 { - message := `¯\\\_(ツ)_/¯` - - parameters := strings.SplitN(command.Command, " ", 2) - if len(parameters) > 1 { - message += " " + parameters[1] - } - - post := &model.Post{} - post.Message = message - post.ChannelId = command.ChannelId - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error("Unable to create /shrug post post, err=%v", err) - return false - } - command.Response = model.RESP_EXECUTED - return true - - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Adds ¯\\_(ツ)_/¯ to your message, /shrug [message]"}) - } - - return false -} - -func joinCommand(c *Context, command *model.Command) bool { - - // looks for "/join channel-name" - cmd := cmds["joinCommand"] - - if strings.Index(command.Command, cmd) == 0 { - - parts := strings.Split(command.Command, " ") - - startsWith := "" - - if len(parts) == 2 { - startsWith = parts[1] - } - - if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { - c.Err = result.Err - return false - } else { - channels := result.Data.(*model.ChannelList) - - for _, v := range channels.Channels { - - if v.Name == startsWith && !command.Suggest { - - if v.Type == model.CHANNEL_DIRECT { - return false - } - - JoinChannel(c, v.Id, "") - - if c.Err != nil { - return false - } - - command.GotoLocation = c.GetTeamURL() + "/channels/" + v.Name - command.Response = model.RESP_EXECUTED - return true - } - - if len(startsWith) == 0 || strings.Index(v.Name, startsWith) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd + " " + v.Name, Description: "Join the open channel"}) - } - } - } - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Join an open channel"}) - } - - return false -} - -func loadTestCommand(c *Context, command *model.Command) bool { - cmd := cmds["loadTestCommand"] - - // This command is only available when EnableTesting is true - if !utils.Cfg.ServiceSettings.EnableTesting { - return false - } - - if strings.Index(command.Command, cmd) == 0 { - if loadTestSetupCommand(c, command) { - return true - } - if loadTestUsersCommand(c, command) { - return true - } - if loadTestChannelsCommand(c, command) { - return true - } - if loadTestPostsCommand(c, command) { - return true - } - if loadTestUrlCommand(c, command) { - return true - } - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Debug Load Testing"}) - } - - return false -} - -func parseRange(command string, cmd string) (utils.Range, bool) { - tokens := strings.Fields(strings.TrimPrefix(command, cmd)) - var begin int - var end int - var err1 error - var err2 error - switch { - case len(tokens) == 1: - begin, err1 = strconv.Atoi(tokens[0]) - end = begin - if err1 != nil { - return utils.Range{0, 0}, false - } - case len(tokens) >= 2: - begin, err1 = strconv.Atoi(tokens[0]) - end, err2 = strconv.Atoi(tokens[1]) - if err1 != nil || err2 != nil { - return utils.Range{0, 0}, false - } - default: - return utils.Range{0, 0}, false - } - return utils.Range{begin, end}, true -} - -func contains(items []string, token string) bool { - for _, elem := range items { - if elem == token { - return true - } - } - return false -} - -func loadTestSetupCommand(c *Context, command *model.Command) bool { - cmd := cmds["loadTestCommand"] + " setup" - - if strings.Index(command.Command, cmd) == 0 && !command.Suggest { - tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) - doTeams := contains(tokens, "teams") - doFuzz := contains(tokens, "fuzz") - - numArgs := 0 - if doTeams { - numArgs++ - } - if doFuzz { - numArgs++ - } - - var numTeams int - var numChannels int - var numUsers int - var numPosts int - - // Defaults - numTeams = 10 - numChannels = 10 - numUsers = 10 - numPosts = 10 - - if doTeams { - if (len(tokens) - numArgs) >= 4 { - numTeams, _ = strconv.Atoi(tokens[numArgs+0]) - numChannels, _ = strconv.Atoi(tokens[numArgs+1]) - numUsers, _ = strconv.Atoi(tokens[numArgs+2]) - numPosts, _ = strconv.Atoi(tokens[numArgs+3]) - } - } else { - if (len(tokens) - numArgs) >= 3 { - numChannels, _ = strconv.Atoi(tokens[numArgs+0]) - numUsers, _ = strconv.Atoi(tokens[numArgs+1]) - numPosts, _ = strconv.Atoi(tokens[numArgs+2]) - } - } - client := model.NewClient(c.GetSiteURL()) - - if doTeams { - if err := CreateBasicUser(client); err != nil { - l4g.Error("Failed to create testing environment") - return true - } - client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) - environment, err := CreateTestEnvironmentWithTeams( - client, - utils.Range{numTeams, numTeams}, - utils.Range{numChannels, numChannels}, - utils.Range{numUsers, numUsers}, - utils.Range{numPosts, numPosts}, - doFuzz) - if err != true { - l4g.Error("Failed to create testing environment") - return true - } else { - l4g.Info("Testing environment created") - for i := 0; i < len(environment.Teams); i++ { - l4g.Info("Team Created: " + environment.Teams[i].Name) - l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) - } - } - } else { - client.MockSession(c.Session.Token) - CreateTestEnvironmentInTeam( - client, - c.Session.TeamId, - utils.Range{numChannels, numChannels}, - utils.Range{numUsers, numUsers}, - utils.Range{numPosts, numPosts}, - doFuzz) - } - return true - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{ - Suggestion: cmd, - Description: "Creates a testing environment in current team. [teams] [fuzz] "}) - } - - return false -} - -func loadTestUsersCommand(c *Context, command *model.Command) bool { - cmd1 := cmds["loadTestCommand"] + " users" - cmd2 := cmds["loadTestCommand"] + " users fuzz" - - if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { - cmd := cmd1 - doFuzz := false - if strings.Index(command.Command, cmd2) == 0 { - doFuzz = true - cmd = cmd2 - } - usersr, err := parseRange(command.Command, cmd) - if err == false { - usersr = utils.Range{10, 15} - } - client := model.NewClient(c.GetSiteURL()) - userCreator := NewAutoUserCreator(client, c.Session.TeamId) - userCreator.Fuzzy = doFuzz - userCreator.CreateTestUsers(usersr) - return true - } else if strings.Index(cmd1, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random users to current team "}) - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team "}) - } else if strings.Index(cmd2, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team "}) - } - - return false -} - -func loadTestChannelsCommand(c *Context, command *model.Command) bool { - cmd1 := cmds["loadTestCommand"] + " channels" - cmd2 := cmds["loadTestCommand"] + " channels fuzz" - - if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { - cmd := cmd1 - doFuzz := false - if strings.Index(command.Command, cmd2) == 0 { - doFuzz = true - cmd = cmd2 - } - channelsr, err := parseRange(command.Command, cmd) - if err == false { - channelsr = utils.Range{20, 30} - } - client := model.NewClient(c.GetSiteURL()) - client.MockSession(c.Session.Token) - channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) - channelCreator.Fuzzy = doFuzz - channelCreator.CreateTestChannels(channelsr) - return true - } else if strings.Index(cmd1, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random channels to current team "}) - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team "}) - } else if strings.Index(cmd2, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team "}) - } - - return false -} - -func loadTestPostsCommand(c *Context, command *model.Command) bool { - cmd1 := cmds["loadTestCommand"] + " posts" - cmd2 := cmds["loadTestCommand"] + " posts fuzz" - - if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { - cmd := cmd1 - doFuzz := false - if strings.Index(command.Command, cmd2) == 0 { - cmd = cmd2 - doFuzz = true - } - - postsr, err := parseRange(command.Command, cmd) - if err == false { - postsr = utils.Range{20, 30} - } - - tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) - rimages := utils.Range{0, 0} - if len(tokens) >= 3 { - if numImages, err := strconv.Atoi(tokens[2]); err == nil { - rimages = utils.Range{numImages, numImages} - } - } - - var usernames []string - if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil { - profileUsers := result.Data.(map[string]*model.User) - usernames = make([]string, len(profileUsers)) - i := 0 - for _, userprof := range profileUsers { - usernames[i] = userprof.Username - i++ - } - } - - client := model.NewClient(c.GetSiteURL()) - client.MockSession(c.Session.Token) - testPoster := NewAutoPostCreator(client, command.ChannelId) - testPoster.Fuzzy = doFuzz - testPoster.Users = usernames - - numImages := utils.RandIntFromRange(rimages) - numPosts := utils.RandIntFromRange(postsr) - for i := 0; i < numPosts; i++ { - testPoster.HasImage = (i < numImages) - testPoster.CreateRandomPost() - } - return true - } else if strings.Index(cmd1, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add some random posts to current channel "}) - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel "}) - } else if strings.Index(cmd2, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel "}) - } - - return false -} - -func loadTestUrlCommand(c *Context, command *model.Command) bool { - cmd := cmds["loadTestCommand"] + " url" - - if strings.Index(command.Command, cmd) == 0 && !command.Suggest { - url := "" - - parameters := strings.SplitN(command.Command, " ", 3) - if len(parameters) != 3 { - c.Err = model.NewAppError("loadTestUrlCommand", "Command must contain a url", "") - return true - } else { - url = parameters[2] - } - - // provide a shortcut to easily access tests stored in doc/developer/tests - if !strings.HasPrefix(url, "http") { - url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url - - if path.Ext(url) == "" { - url += ".md" - } - } - - var contents io.ReadCloser - if r, err := http.Get(url); err != nil { - c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", err.Error()) - return false - } else if r.StatusCode > 400 { - c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", r.Status) - return false - } else { - contents = r.Body - } - - bytes := make([]byte, 4000) - - // break contents into 4000 byte posts - for { - length, err := contents.Read(bytes) - if err != nil && err != io.EOF { - c.Err = model.NewAppError("loadTestUrlCommand", "Encountered error reading file", err.Error()) - return false - } - - if length == 0 { - break - } - - post := &model.Post{} - post.Message = string(bytes[:length]) - post.ChannelId = command.ChannelId - - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error("Unable to create post, err=%v", err) - return false - } - } - - command.Response = model.RESP_EXECUTED - - return true - } else if strings.Index(cmd, command.Command) == 0 && strings.Index(command.Command, "/loadtest posts") != 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Add a post containing the text from a given url to current channel "}) - } - - return false -} +// func command(c *Context, w http.ResponseWriter, r *http.Request) { + +// props := model.MapFromJson(r.Body) + +// command := &model.Command{ +// Command: strings.TrimSpace(props["command"]), +// ChannelId: strings.TrimSpace(props["channelId"]), +// Suggest: props["suggest"] == "true", +// Suggestions: make([]*model.SuggestCommand, 0, 128), +// } + +// checkCommand(c, command) +// if c.Err != nil { +// if c.Err != commandNotImplementedErr { +// return +// } else { +// c.Err = nil +// command.Response = model.RESP_NOT_IMPLEMENTED +// w.Write([]byte(command.ToJson())) +// return +// } +// } else { +// w.Write([]byte(command.ToJson())) +// } +// } + +// func checkCommand(c *Context, command *model.Command) bool { + +// if len(command.Command) == 0 || strings.Index(command.Command, "/") != 0 { +// c.Err = model.NewAppError("checkCommand", "Command must start with /", "") +// return false +// } + +// if len(command.ChannelId) > 0 { +// cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId) + +// if !c.HasPermissionsToChannel(cchan, "checkCommand") { +// return true +// } +// } + +// if !command.Suggest { +// implemented := false +// for _, cmd := range cmds { +// bounds := len(cmd) +// if len(command.Command) < bounds { +// continue +// } +// if command.Command[:bounds] == cmd { +// implemented = true +// } +// } +// if !implemented { +// c.Err = commandNotImplementedErr +// return false +// } +// } + +// for _, v := range commands { + +// if v(c, command) || c.Err != nil { +// return true +// } +// } + +// return false +// } + +// func logoutCommand(c *Context, command *model.Command) bool { + +// cmd := cmds["logoutCommand"] + +// if strings.Index(command.Command, cmd) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) + +// if !command.Suggest { +// command.GotoLocation = "/logout" +// command.Response = model.RESP_EXECUTED +// return true +// } + +// } else if strings.Index(cmd, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) +// } + +// return false +// } + +// func echoCommand(c *Context, command *model.Command) bool { +// cmd := cmds["echoCommand"] +// maxThreads := 100 + +// if !command.Suggest && strings.Index(command.Command, cmd) == 0 { +// parameters := strings.SplitN(command.Command, " ", 2) +// if len(parameters) != 2 || len(parameters[1]) == 0 { +// return false +// } +// message := strings.Trim(parameters[1], " ") +// delay := 0 +// if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { +// if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { +// delay = checkDelay +// } +// message = message[1:endMsg] +// } else if strings.Index(message, " ") > -1 { +// delayIdx := strings.LastIndex(message, " ") +// delayStr := strings.Trim(message[delayIdx:], " ") + +// if checkDelay, err := strconv.Atoi(delayStr); err == nil { +// delay = checkDelay +// message = message[:delayIdx] +// } +// } + +// if delay > 10000 { +// c.Err = model.NewAppError("echoCommand", "Delays must be under 10000 seconds", "") +// return false +// } + +// if echoSem == nil { +// // We want one additional thread allowed so we never reach channel lockup +// echoSem = make(chan bool, maxThreads+1) +// } + +// if len(echoSem) >= maxThreads { +// c.Err = model.NewAppError("echoCommand", "High volume of echo request, cannot process request", "") +// return false +// } + +// echoSem <- true +// go func() { +// defer func() { <-echoSem }() +// post := &model.Post{} +// post.ChannelId = command.ChannelId +// post.Message = message + +// time.Sleep(time.Duration(delay) * time.Second) + +// if _, err := CreatePost(c, post, true); err != nil { +// l4g.Error("Unable to create /echo post, err=%v", err) +// } +// }() + +// command.Response = model.RESP_EXECUTED +// return true + +// } else if strings.Index(cmd, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo back text from your account, /echo \"message\" [delay in seconds]"}) +// } + +// return false +// } + +// func meCommand(c *Context, command *model.Command) bool { +// cmd := cmds["meCommand"] + +// if !command.Suggest && strings.Index(command.Command, cmd) == 0 { +// message := "" + +// parameters := strings.SplitN(command.Command, " ", 2) +// if len(parameters) > 1 { +// message += "*" + parameters[1] + "*" +// } + +// post := &model.Post{} +// post.Message = message +// post.ChannelId = command.ChannelId +// if _, err := CreatePost(c, post, false); err != nil { +// l4g.Error("Unable to create /me post post, err=%v", err) +// return false +// } +// command.Response = model.RESP_EXECUTED +// return true + +// } else if strings.Index(cmd, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Do an action, /me [message]"}) +// } + +// return false +// } + +// func shrugCommand(c *Context, command *model.Command) bool { +// cmd := cmds["shrugCommand"] + +// if !command.Suggest && strings.Index(command.Command, cmd) == 0 { +// message := `¯\\\_(ツ)_/¯` + +// parameters := strings.SplitN(command.Command, " ", 2) +// if len(parameters) > 1 { +// message += " " + parameters[1] +// } + +// post := &model.Post{} +// post.Message = message +// post.ChannelId = command.ChannelId +// if _, err := CreatePost(c, post, false); err != nil { +// l4g.Error("Unable to create /shrug post post, err=%v", err) +// return false +// } +// command.Response = model.RESP_EXECUTED +// return true + +// } else if strings.Index(cmd, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Adds ¯\\_(ツ)_/¯ to your message, /shrug [message]"}) +// } + +// return false +// } + +// func joinCommand(c *Context, command *model.Command) bool { + +// // looks for "/join channel-name" +// cmd := cmds["joinCommand"] + +// if strings.Index(command.Command, cmd) == 0 { + +// parts := strings.Split(command.Command, " ") + +// startsWith := "" + +// if len(parts) == 2 { +// startsWith = parts[1] +// } + +// if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { +// c.Err = result.Err +// return false +// } else { +// channels := result.Data.(*model.ChannelList) + +// for _, v := range channels.Channels { + +// if v.Name == startsWith && !command.Suggest { + +// if v.Type == model.CHANNEL_DIRECT { +// return false +// } + +// JoinChannel(c, v.Id, "") + +// if c.Err != nil { +// return false +// } + +// command.GotoLocation = c.GetTeamURL() + "/channels/" + v.Name +// command.Response = model.RESP_EXECUTED +// return true +// } + +// if len(startsWith) == 0 || strings.Index(v.Name, startsWith) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd + " " + v.Name, Description: "Join the open channel"}) +// } +// } +// } +// } else if strings.Index(cmd, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Join an open channel"}) +// } + +// return false +// } + +// func loadTestCommand(c *Context, command *model.Command) bool { +// cmd := cmds["loadTestCommand"] + +// // This command is only available when EnableTesting is true +// if !utils.Cfg.ServiceSettings.EnableTesting { +// return false +// } + +// if strings.Index(command.Command, cmd) == 0 { +// if loadTestSetupCommand(c, command) { +// return true +// } +// if loadTestUsersCommand(c, command) { +// return true +// } +// if loadTestChannelsCommand(c, command) { +// return true +// } +// if loadTestPostsCommand(c, command) { +// return true +// } +// if loadTestUrlCommand(c, command) { +// return true +// } +// } else if strings.Index(cmd, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Debug Load Testing"}) +// } + +// return false +// } + +// func parseRange(command string, cmd string) (utils.Range, bool) { +// tokens := strings.Fields(strings.TrimPrefix(command, cmd)) +// var begin int +// var end int +// var err1 error +// var err2 error +// switch { +// case len(tokens) == 1: +// begin, err1 = strconv.Atoi(tokens[0]) +// end = begin +// if err1 != nil { +// return utils.Range{0, 0}, false +// } +// case len(tokens) >= 2: +// begin, err1 = strconv.Atoi(tokens[0]) +// end, err2 = strconv.Atoi(tokens[1]) +// if err1 != nil || err2 != nil { +// return utils.Range{0, 0}, false +// } +// default: +// return utils.Range{0, 0}, false +// } +// return utils.Range{begin, end}, true +// } + +// func contains(items []string, token string) bool { +// for _, elem := range items { +// if elem == token { +// return true +// } +// } +// return false +// } + +// func loadTestSetupCommand(c *Context, command *model.Command) bool { +// cmd := cmds["loadTestCommand"] + " setup" + +// if strings.Index(command.Command, cmd) == 0 && !command.Suggest { +// tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) +// doTeams := contains(tokens, "teams") +// doFuzz := contains(tokens, "fuzz") + +// numArgs := 0 +// if doTeams { +// numArgs++ +// } +// if doFuzz { +// numArgs++ +// } + +// var numTeams int +// var numChannels int +// var numUsers int +// var numPosts int + +// // Defaults +// numTeams = 10 +// numChannels = 10 +// numUsers = 10 +// numPosts = 10 + +// if doTeams { +// if (len(tokens) - numArgs) >= 4 { +// numTeams, _ = strconv.Atoi(tokens[numArgs+0]) +// numChannels, _ = strconv.Atoi(tokens[numArgs+1]) +// numUsers, _ = strconv.Atoi(tokens[numArgs+2]) +// numPosts, _ = strconv.Atoi(tokens[numArgs+3]) +// } +// } else { +// if (len(tokens) - numArgs) >= 3 { +// numChannels, _ = strconv.Atoi(tokens[numArgs+0]) +// numUsers, _ = strconv.Atoi(tokens[numArgs+1]) +// numPosts, _ = strconv.Atoi(tokens[numArgs+2]) +// } +// } +// client := model.NewClient(c.GetSiteURL()) + +// if doTeams { +// if err := CreateBasicUser(client); err != nil { +// l4g.Error("Failed to create testing environment") +// return true +// } +// client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) +// environment, err := CreateTestEnvironmentWithTeams( +// client, +// utils.Range{numTeams, numTeams}, +// utils.Range{numChannels, numChannels}, +// utils.Range{numUsers, numUsers}, +// utils.Range{numPosts, numPosts}, +// doFuzz) +// if err != true { +// l4g.Error("Failed to create testing environment") +// return true +// } else { +// l4g.Info("Testing environment created") +// for i := 0; i < len(environment.Teams); i++ { +// l4g.Info("Team Created: " + environment.Teams[i].Name) +// l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) +// } +// } +// } else { +// client.MockSession(c.Session.Token) +// CreateTestEnvironmentInTeam( +// client, +// c.Session.TeamId, +// utils.Range{numChannels, numChannels}, +// utils.Range{numUsers, numUsers}, +// utils.Range{numPosts, numPosts}, +// doFuzz) +// } +// return true +// } else if strings.Index(cmd, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{ +// Suggestion: cmd, +// Description: "Creates a testing environment in current team. [teams] [fuzz] "}) +// } + +// return false +// } + +// func loadTestUsersCommand(c *Context, command *model.Command) bool { +// cmd1 := cmds["loadTestCommand"] + " users" +// cmd2 := cmds["loadTestCommand"] + " users fuzz" + +// if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { +// cmd := cmd1 +// doFuzz := false +// if strings.Index(command.Command, cmd2) == 0 { +// doFuzz = true +// cmd = cmd2 +// } +// usersr, err := parseRange(command.Command, cmd) +// if err == false { +// usersr = utils.Range{10, 15} +// } +// client := model.NewClient(c.GetSiteURL()) +// userCreator := NewAutoUserCreator(client, c.Session.TeamId) +// userCreator.Fuzzy = doFuzz +// userCreator.CreateTestUsers(usersr) +// return true +// } else if strings.Index(cmd1, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random users to current team "}) +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team "}) +// } else if strings.Index(cmd2, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team "}) +// } + +// return false +// } + +// func loadTestChannelsCommand(c *Context, command *model.Command) bool { +// cmd1 := cmds["loadTestCommand"] + " channels" +// cmd2 := cmds["loadTestCommand"] + " channels fuzz" + +// if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { +// cmd := cmd1 +// doFuzz := false +// if strings.Index(command.Command, cmd2) == 0 { +// doFuzz = true +// cmd = cmd2 +// } +// channelsr, err := parseRange(command.Command, cmd) +// if err == false { +// channelsr = utils.Range{20, 30} +// } +// client := model.NewClient(c.GetSiteURL()) +// client.MockSession(c.Session.Token) +// channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) +// channelCreator.Fuzzy = doFuzz +// channelCreator.CreateTestChannels(channelsr) +// return true +// } else if strings.Index(cmd1, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random channels to current team "}) +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team "}) +// } else if strings.Index(cmd2, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team "}) +// } + +// return false +// } + +// func loadTestPostsCommand(c *Context, command *model.Command) bool { +// cmd1 := cmds["loadTestCommand"] + " posts" +// cmd2 := cmds["loadTestCommand"] + " posts fuzz" + +// if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { +// cmd := cmd1 +// doFuzz := false +// if strings.Index(command.Command, cmd2) == 0 { +// cmd = cmd2 +// doFuzz = true +// } + +// postsr, err := parseRange(command.Command, cmd) +// if err == false { +// postsr = utils.Range{20, 30} +// } + +// tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) +// rimages := utils.Range{0, 0} +// if len(tokens) >= 3 { +// if numImages, err := strconv.Atoi(tokens[2]); err == nil { +// rimages = utils.Range{numImages, numImages} +// } +// } + +// var usernames []string +// if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil { +// profileUsers := result.Data.(map[string]*model.User) +// usernames = make([]string, len(profileUsers)) +// i := 0 +// for _, userprof := range profileUsers { +// usernames[i] = userprof.Username +// i++ +// } +// } + +// client := model.NewClient(c.GetSiteURL()) +// client.MockSession(c.Session.Token) +// testPoster := NewAutoPostCreator(client, command.ChannelId) +// testPoster.Fuzzy = doFuzz +// testPoster.Users = usernames + +// numImages := utils.RandIntFromRange(rimages) +// numPosts := utils.RandIntFromRange(postsr) +// for i := 0; i < numPosts; i++ { +// testPoster.HasImage = (i < numImages) +// testPoster.CreateRandomPost() +// } +// return true +// } else if strings.Index(cmd1, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add some random posts to current channel "}) +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel "}) +// } else if strings.Index(cmd2, command.Command) == 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel "}) +// } + +// return false +// } + +// func loadTestUrlCommand(c *Context, command *model.Command) bool { +// cmd := cmds["loadTestCommand"] + " url" + +// if strings.Index(command.Command, cmd) == 0 && !command.Suggest { +// url := "" + +// parameters := strings.SplitN(command.Command, " ", 3) +// if len(parameters) != 3 { +// c.Err = model.NewAppError("loadTestUrlCommand", "Command must contain a url", "") +// return true +// } else { +// url = parameters[2] +// } + +// // provide a shortcut to easily access tests stored in doc/developer/tests +// if !strings.HasPrefix(url, "http") { +// url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url + +// if path.Ext(url) == "" { +// url += ".md" +// } +// } + +// var contents io.ReadCloser +// if r, err := http.Get(url); err != nil { +// c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", err.Error()) +// return false +// } else if r.StatusCode > 400 { +// c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", r.Status) +// return false +// } else { +// contents = r.Body +// } + +// bytes := make([]byte, 4000) + +// // break contents into 4000 byte posts +// for { +// length, err := contents.Read(bytes) +// if err != nil && err != io.EOF { +// c.Err = model.NewAppError("loadTestUrlCommand", "Encountered error reading file", err.Error()) +// return false +// } + +// if length == 0 { +// break +// } + +// post := &model.Post{} +// post.Message = string(bytes[:length]) +// post.ChannelId = command.ChannelId + +// if _, err := CreatePost(c, post, false); err != nil { +// l4g.Error("Unable to create post, err=%v", err) +// return false +// } +// } + +// command.Response = model.RESP_EXECUTED + +// return true +// } else if strings.Index(cmd, command.Command) == 0 && strings.Index(command.Command, "/loadtest posts") != 0 { +// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Add a post containing the text from a given url to current channel "}) +// } + +// return false +// } diff --git a/api/command_test.go b/api/command_test.go index f38cf1397..8b996b9eb 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -3,240 +3,240 @@ package api -import ( - "strings" - "testing" - "time" +// import ( +// "strings" +// "testing" +// "time" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" - "github.com/mattermost/platform/utils" -) +// "github.com/mattermost/platform/model" +// "github.com/mattermost/platform/store" +// "github.com/mattermost/platform/utils" +// ) -func TestSuggestRootCommands(t *testing.T) { - Setup() +// func TestSuggestRootCommands(t *testing.T) { +// Setup() - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) +// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} +// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) +// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} +// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) +// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Name, user1.Email, "pwd") +// Client.LoginByEmail(team.Name, user1.Email, "pwd") - if _, err := Client.Command("", "", true); err == nil { - t.Fatal("Should fail") - } +// if _, err := Client.Command("", "", true); err == nil { +// t.Fatal("Should fail") +// } - rs1 := Client.Must(Client.Command("", "/", true)).Data.(*model.Command) +// rs1 := Client.Must(Client.Command("", "/", true)).Data.(*model.Command) - hasLogout := false - for _, v := range rs1.Suggestions { - if v.Suggestion == "/logout" { - hasLogout = true - } - } +// hasLogout := false +// for _, v := range rs1.Suggestions { +// if v.Suggestion == "/logout" { +// hasLogout = true +// } +// } - if !hasLogout { - t.Log(rs1.Suggestions) - t.Fatal("should have logout cmd") - } +// if !hasLogout { +// t.Log(rs1.Suggestions) +// t.Fatal("should have logout cmd") +// } - rs2 := Client.Must(Client.Command("", "/log", true)).Data.(*model.Command) +// rs2 := Client.Must(Client.Command("", "/log", true)).Data.(*model.Command) - if rs2.Suggestions[0].Suggestion != "/logout" { - t.Fatal("should have logout cmd") - } +// if rs2.Suggestions[0].Suggestion != "/logout" { +// t.Fatal("should have logout cmd") +// } - rs3 := Client.Must(Client.Command("", "/joi", true)).Data.(*model.Command) +// rs3 := Client.Must(Client.Command("", "/joi", true)).Data.(*model.Command) - if rs3.Suggestions[0].Suggestion != "/join" { - t.Fatal("should have join cmd") - } - - rs4 := Client.Must(Client.Command("", "/ech", true)).Data.(*model.Command) +// if rs3.Suggestions[0].Suggestion != "/join" { +// t.Fatal("should have join cmd") +// } + +// rs4 := Client.Must(Client.Command("", "/ech", true)).Data.(*model.Command) - if rs4.Suggestions[0].Suggestion != "/echo" { - t.Fatal("should have echo cmd") - } -} +// if rs4.Suggestions[0].Suggestion != "/echo" { +// t.Fatal("should have echo cmd") +// } +// } -func TestLogoutCommands(t *testing.T) { - Setup() +// func TestLogoutCommands(t *testing.T) { +// Setup() - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) +// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} +// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) +// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} +// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) +// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Name, user1.Email, "pwd") +// Client.LoginByEmail(team.Name, user1.Email, "pwd") - rs1 := Client.Must(Client.Command("", "/logout", false)).Data.(*model.Command) - if rs1.GotoLocation != "/logout" { - t.Fatal("failed to logout") - } -} +// rs1 := Client.Must(Client.Command("", "/logout", false)).Data.(*model.Command) +// if rs1.GotoLocation != "/logout" { +// t.Fatal("failed to logout") +// } +// } -func TestJoinCommands(t *testing.T) { - Setup() +// func TestJoinCommands(t *testing.T) { +// Setup() - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) +// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} +// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) +// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} +// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) +// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Name, user1.Email, "pwd") +// Client.LoginByEmail(team.Name, user1.Email, "pwd") - channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - Client.Must(Client.LeaveChannel(channel1.Id)) +// channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} +// channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) +// Client.Must(Client.LeaveChannel(channel1.Id)) - channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - Client.Must(Client.LeaveChannel(channel2.Id)) +// channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} +// channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) +// Client.Must(Client.LeaveChannel(channel2.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) +// user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} +// user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) +// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - data := make(map[string]string) - data["user_id"] = user2.Id - channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel) +// data := make(map[string]string) +// data["user_id"] = user2.Id +// channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel) - rs1 := Client.Must(Client.Command("", "/join aa", true)).Data.(*model.Command) - if rs1.Suggestions[0].Suggestion != "/join "+channel1.Name { - t.Fatal("should have join cmd") - } +// rs1 := Client.Must(Client.Command("", "/join aa", true)).Data.(*model.Command) +// if rs1.Suggestions[0].Suggestion != "/join "+channel1.Name { +// t.Fatal("should have join cmd") +// } - rs2 := Client.Must(Client.Command("", "/join bb", true)).Data.(*model.Command) - if rs2.Suggestions[0].Suggestion != "/join "+channel2.Name { - t.Fatal("should have join cmd") - } +// rs2 := Client.Must(Client.Command("", "/join bb", true)).Data.(*model.Command) +// if rs2.Suggestions[0].Suggestion != "/join "+channel2.Name { +// t.Fatal("should have join cmd") +// } - rs3 := Client.Must(Client.Command("", "/join", true)).Data.(*model.Command) - if len(rs3.Suggestions) != 2 { - t.Fatal("should have 2 join cmd") - } +// rs3 := Client.Must(Client.Command("", "/join", true)).Data.(*model.Command) +// if len(rs3.Suggestions) != 2 { +// t.Fatal("should have 2 join cmd") +// } - rs4 := Client.Must(Client.Command("", "/join ", true)).Data.(*model.Command) - if len(rs4.Suggestions) != 2 { - t.Fatal("should have 2 join cmd") - } +// rs4 := Client.Must(Client.Command("", "/join ", true)).Data.(*model.Command) +// if len(rs4.Suggestions) != 2 { +// t.Fatal("should have 2 join cmd") +// } - rs5 := Client.Must(Client.Command("", "/join "+channel2.Name, false)).Data.(*model.Command) - if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { - t.Fatal("failed to join channel") - } +// rs5 := Client.Must(Client.Command("", "/join "+channel2.Name, false)).Data.(*model.Command) +// if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { +// t.Fatal("failed to join channel") +// } - rs6 := Client.Must(Client.Command("", "/join "+channel3.Name, false)).Data.(*model.Command) - if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { - t.Fatal("should not have joined direct message channel") - } +// rs6 := Client.Must(Client.Command("", "/join "+channel3.Name, false)).Data.(*model.Command) +// if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { +// t.Fatal("should not have joined direct message channel") +// } - c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) +// c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - if len(c1.Channels) != 4 { // 4 because of town-square, off-topic and direct - t.Fatal("didn't join channel") - } +// if len(c1.Channels) != 4 { // 4 because of town-square, off-topic and direct +// t.Fatal("didn't join channel") +// } - found := false - for _, c := range c1.Channels { - if c.Name == channel2.Name { - found = true - break - } - } - if !found { - t.Fatal("didn't join channel") - } -} - -func TestEchoCommand(t *testing.T) { - Setup() - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) +// found := false +// for _, c := range c1.Channels { +// if c.Name == channel2.Name { +// found = true +// break +// } +// } +// if !found { +// t.Fatal("didn't join channel") +// } +// } + +// func TestEchoCommand(t *testing.T) { +// Setup() + +// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} +// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - - Client.LoginByEmail(team.Name, user1.Email, "pwd") - - channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - echoTestString := "/echo test" - - r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.Command) - if r1.Response != model.RESP_EXECUTED { - t.Fatal("Echo command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) - if len(p1.Order) != 1 { - t.Fatal("Echo command failed to send") - } -} - -func TestLoadTestUrlCommand(t *testing.T) { - Setup() - - // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json - enableTesting := utils.Cfg.ServiceSettings.EnableTesting - defer func() { - utils.Cfg.ServiceSettings.EnableTesting = enableTesting - }() - - utils.Cfg.ServiceSettings.EnableTesting = true - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user.Id)) - - Client.LoginByEmail(team.Name, user.Email, "pwd") - - channel := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) - - command := "/loadtest url " - if _, err := Client.Command(channel.Id, command, false); err == nil { - t.Fatal("/loadtest url with no url should've failed") - } - - command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" - if _, err := Client.Command(channel.Id, command, false); err == nil { - t.Fatal("/loadtest url with invalid url should've failed") - } - - command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md" - if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { - t.Fatal("/loadtest url for README.md should've executed") - } - - command = "/loadtest url test-emoticons.md" - if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { - t.Fatal("/loadtest url for test-emoticons.md should've executed") - } - - command = "/loadtest url test-emoticons" - if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { - t.Fatal("/loadtest url for test-emoticons should've executed") - } - - posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList) - // note that this may make more than 3 posts if files are too long to fit in an individual post - if len(posts.Order) < 3 { - t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") - } -} +// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} +// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) +// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + +// Client.LoginByEmail(team.Name, user1.Email, "pwd") + +// channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} +// channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + +// echoTestString := "/echo test" + +// r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.Command) +// if r1.Response != model.RESP_EXECUTED { +// t.Fatal("Echo command failed to execute") +// } + +// time.Sleep(100 * time.Millisecond) + +// p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) +// if len(p1.Order) != 1 { +// t.Fatal("Echo command failed to send") +// } +// } + +// func TestLoadTestUrlCommand(t *testing.T) { +// Setup() + +// // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json +// enableTesting := utils.Cfg.ServiceSettings.EnableTesting +// defer func() { +// utils.Cfg.ServiceSettings.EnableTesting = enableTesting +// }() + +// utils.Cfg.ServiceSettings.EnableTesting = true + +// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} +// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + +// user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} +// user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) +// store.Must(Srv.Store.User().VerifyEmail(user.Id)) + +// Client.LoginByEmail(team.Name, user.Email, "pwd") + +// channel := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} +// channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + +// command := "/loadtest url " +// if _, err := Client.Command(channel.Id, command, false); err == nil { +// t.Fatal("/loadtest url with no url should've failed") +// } + +// command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" +// if _, err := Client.Command(channel.Id, command, false); err == nil { +// t.Fatal("/loadtest url with invalid url should've failed") +// } + +// command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md" +// if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { +// t.Fatal("/loadtest url for README.md should've executed") +// } + +// command = "/loadtest url test-emoticons.md" +// if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { +// t.Fatal("/loadtest url for test-emoticons.md should've executed") +// } + +// command = "/loadtest url test-emoticons" +// if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { +// t.Fatal("/loadtest url for test-emoticons should've executed") +// } + +// posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList) +// // note that this may make more than 3 posts if files are too long to fit in an individual post +// if len(posts.Order) < 3 { +// t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") +// } +// } diff --git a/model/command.go b/model/command.go index 5aec5f534..253021896 100644 --- a/model/command.go +++ b/model/command.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package model @@ -9,28 +9,27 @@ import ( ) const ( - RESP_EXECUTED = "executed" - RESP_NOT_IMPLEMENTED = "not implemented" + COMMAND_METHOD_POST = "P" + COMMAND_METHOD_GET = "G" ) type Command struct { - Command string `json:"command"` - Response string `json:"response"` - GotoLocation string `json:"goto_location"` - ChannelId string `json:"channel_id"` - Suggest bool `json:"-"` - Suggestions []*SuggestCommand `json:"suggestions"` -} - -func (o *Command) AddSuggestion(suggest *SuggestCommand) { - - if o.Suggest { - if o.Suggestions == nil { - o.Suggestions = make([]*SuggestCommand, 0, 128) - } - - o.Suggestions = append(o.Suggestions, suggest) - } + Id string `json:"id"` + Token string `json:"token"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + CreatorId string `json:"creator_id"` + TeamId string `json:"team_id"` + Trigger string `json:"trigger"` + Method string `json:"method"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + AutoComplete bool `json:"auto_complete"` + AutoCompleteDesc string `json:"auto_complete_desc"` + AutoCompleteHint string `json:"auto_complete_hint"` + DisplayName string `json:"display_name"` + URL string `json:"url"` } func (o *Command) ToJson() string { @@ -52,3 +51,85 @@ func CommandFromJson(data io.Reader) *Command { return nil } } + +func CommandListToJson(l []*Command) string { + b, err := json.Marshal(l) + if err != nil { + return "" + } else { + return string(b) + } +} + +func CommandListFromJson(data io.Reader) []*Command { + decoder := json.NewDecoder(data) + var o []*Command + err := decoder.Decode(&o) + if err == nil { + return o + } else { + return nil + } +} + +func (o *Command) IsValid() *AppError { + + if len(o.Id) != 26 { + return NewAppError("Command.IsValid", "Invalid Id", "") + } + + if len(o.Token) != 26 { + return NewAppError("Command.IsValid", "Invalid token", "") + } + + if o.CreateAt == 0 { + return NewAppError("Command.IsValid", "Create at must be a valid time", "id="+o.Id) + } + + if o.UpdateAt == 0 { + return NewAppError("Command.IsValid", "Update at must be a valid time", "id="+o.Id) + } + + if len(o.CreatorId) != 26 { + return NewAppError("Command.IsValid", "Invalid user id", "") + } + + if len(o.TeamId) != 26 { + return NewAppError("Command.IsValid", "Invalid team id", "") + } + + if len(o.Trigger) > 1024 { + return NewAppError("Command.IsValid", "Invalid trigger", "") + } + + if len(o.URL) == 0 || len(o.URL) > 1024 { + return NewAppError("Command.IsValid", "Invalid url", "") + } + + if !IsValidHttpUrl(o.URL) { + return NewAppError("Command.IsValid", "Invalid URL. Must be a valid URL and start with http:// or https://", "") + } + + if !(o.Method == COMMAND_METHOD_GET || o.Method == COMMAND_METHOD_POST) { + return NewAppError("Command.IsValid", "Invalid Method", "") + } + + return nil +} + +func (o *Command) PreSave() { + if o.Id == "" { + o.Id = NewId() + } + + if o.Token == "" { + o.Token = NewId() + } + + o.CreateAt = GetMillis() + o.UpdateAt = o.CreateAt +} + +func (o *Command) PreUpdate() { + o.UpdateAt = GetMillis() +} diff --git a/model/command_test.go b/model/command_test.go index 61302ea10..0581625d9 100644 --- a/model/command_test.go +++ b/model/command_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package model @@ -9,17 +9,89 @@ import ( ) func TestCommandJson(t *testing.T) { + o := Command{Id: NewId()} + json := o.ToJson() + ro := CommandFromJson(strings.NewReader(json)) - command := &Command{Command: NewId(), Suggest: true} - command.AddSuggestion(&SuggestCommand{Suggestion: NewId()}) - json := command.ToJson() - result := CommandFromJson(strings.NewReader(json)) - - if command.Command != result.Command { + if o.Id != ro.Id { t.Fatal("Ids do not match") } +} - if command.Suggestions[0].Suggestion != result.Suggestions[0].Suggestion { - t.Fatal("Ids do not match") +func TestCommandIsValid(t *testing.T) { + o := Command{} + + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.Id = NewId() + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.CreateAt = GetMillis() + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.UpdateAt = GetMillis() + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.CreatorId = "123" + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.CreatorId = NewId() + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.Token = "123" + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") } + + o.Token = NewId() + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.TeamId = "123" + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.TeamId = NewId() + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.URL = "nowhere.com/" + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.URL = "http://nowhere.com/" + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.Method = COMMAND_METHOD_GET + if err := o.IsValid(); err != nil { + t.Fatal(err) + } +} + +func TestCommandPreSave(t *testing.T) { + o := Command{} + o.PreSave() +} + +func TestCommandPreUpdate(t *testing.T) { + o := Command{} + o.PreUpdate() } diff --git a/store/sql_command_store.go b/store/sql_command_store.go new file mode 100644 index 000000000..cb817d8f8 --- /dev/null +++ b/store/sql_command_store.go @@ -0,0 +1,174 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "github.com/mattermost/platform/model" +) + +type SqlCommandStore struct { + *SqlStore +} + +func NewSqlCommandStore(sqlStore *SqlStore) CommandStore { + s := &SqlCommandStore{sqlStore} + + for _, db := range sqlStore.GetAllConns() { + tableo := db.AddTableWithName(model.Command{}, "Commands").SetKeys(false, "Id") + tableo.ColMap("Id").SetMaxSize(26) + tableo.ColMap("Token").SetMaxSize(26) + tableo.ColMap("CreatorId").SetMaxSize(26) + tableo.ColMap("TeamId").SetMaxSize(26) + tableo.ColMap("Trigger").SetMaxSize(128) + tableo.ColMap("URL").SetMaxSize(1024) + tableo.ColMap("Method").SetMaxSize(1) + tableo.ColMap("Username").SetMaxSize(64) + tableo.ColMap("IconURL").SetMaxSize(1024) + tableo.ColMap("AutoCompleteDesc").SetMaxSize(1024) + tableo.ColMap("AutoCompleteHint").SetMaxSize(1024) + tableo.ColMap("DisplayName").SetMaxSize(64) + } + + return s +} + +func (s SqlCommandStore) UpgradeSchemaIfNeeded() { +} + +func (s SqlCommandStore) CreateIndexesIfNotExists() { + s.CreateIndexIfNotExists("idx_command_team_id", "Commands", "TeamId") +} + +func (s SqlCommandStore) Save(command *model.Command) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + if len(command.Id) > 0 { + result.Err = model.NewAppError("SqlCommandStore.Save", + "You cannot overwrite an existing Command", "id="+command.Id) + storeChannel <- result + close(storeChannel) + return + } + + command.PreSave() + if result.Err = command.IsValid(); result.Err != nil { + storeChannel <- result + close(storeChannel) + return + } + + if err := s.GetMaster().Insert(command); err != nil { + result.Err = model.NewAppError("SqlCommandStore.Save", "We couldn't save the Command", "id="+command.Id+", "+err.Error()) + } else { + result.Data = command + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlCommandStore) Get(id string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var command model.Command + + if err := s.GetReplica().SelectOne(&command, "SELECT * FROM Commands WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id}); err != nil { + result.Err = model.NewAppError("SqlCommandStore.Get", "We couldn't get the command", "id="+id+", err="+err.Error()) + } + + result.Data = &command + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlCommandStore) GetByTeam(teamId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var commands []*model.Command + + if _, err := s.GetReplica().Select(&commands, "SELECT * FROM Commands WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil { + result.Err = model.NewAppError("SqlCommandStore.GetByTeam", "We couldn't get the commands", "teamId="+teamId+", err="+err.Error()) + } + + result.Data = commands + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlCommandStore) Delete(commandId string, time int64) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + _, err := s.GetMaster().Exec("Update Commands SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :Id", map[string]interface{}{"DeleteAt": time, "UpdateAt": time, "Id": commandId}) + if err != nil { + result.Err = model.NewAppError("SqlCommandStore.Delete", "We couldn't delete the command", "id="+commandId+", err="+err.Error()) + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlCommandStore) PermanentDeleteByUser(userId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + _, err := s.GetMaster().Exec("DELETE FROM Commands WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId}) + if err != nil { + result.Err = model.NewAppError("SqlCommandStore.DeleteByUser", "We couldn't delete the command", "id="+userId+", err="+err.Error()) + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlCommandStore) Update(hook *model.Command) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + hook.UpdateAt = model.GetMillis() + + if _, err := s.GetMaster().Update(hook); err != nil { + result.Err = model.NewAppError("SqlCommandStore.Update", "We couldn't update the command", "id="+hook.Id+", "+err.Error()) + } else { + result.Data = hook + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/sql_command_store_test.go b/store/sql_command_store_test.go new file mode 100644 index 000000000..b4610d4aa --- /dev/null +++ b/store/sql_command_store_test.go @@ -0,0 +1,155 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "github.com/mattermost/platform/model" + "testing" +) + +func TestCommandStoreSave(t *testing.T) { + Setup() + + o1 := model.Command{} + o1.CreatorId = model.NewId() + o1.Method = model.COMMAND_METHOD_POST + o1.TeamId = model.NewId() + o1.URL = "http://nowhere.com/" + + if err := (<-store.Command().Save(&o1)).Err; err != nil { + t.Fatal("couldn't save item", err) + } + + if err := (<-store.Command().Save(&o1)).Err; err == nil { + t.Fatal("shouldn't be able to update from save") + } +} + +func TestCommandStoreGet(t *testing.T) { + Setup() + + o1 := &model.Command{} + o1.CreatorId = model.NewId() + o1.Method = model.COMMAND_METHOD_POST + o1.TeamId = model.NewId() + o1.URL = "http://nowhere.com/" + + o1 = (<-store.Command().Save(o1)).Data.(*model.Command) + + if r1 := <-store.Command().Get(o1.Id); r1.Err != nil { + t.Fatal(r1.Err) + } else { + if r1.Data.(*model.Command).CreateAt != o1.CreateAt { + t.Fatal("invalid returned command") + } + } + + if err := (<-store.Command().Get("123")).Err; err == nil { + t.Fatal("Missing id should have failed") + } +} + +func TestCommandStoreGetByTeam(t *testing.T) { + Setup() + + o1 := &model.Command{} + o1.CreatorId = model.NewId() + o1.Method = model.COMMAND_METHOD_POST + o1.TeamId = model.NewId() + o1.URL = "http://nowhere.com/" + + o1 = (<-store.Command().Save(o1)).Data.(*model.Command) + + if r1 := <-store.Command().GetByTeam(o1.TeamId); r1.Err != nil { + t.Fatal(r1.Err) + } else { + if r1.Data.([]*model.Command)[0].CreateAt != o1.CreateAt { + t.Fatal("invalid returned command") + } + } + + if result := <-store.Command().GetByTeam("123"); result.Err != nil { + t.Fatal(result.Err) + } else { + if len(result.Data.([]*model.Command)) != 0 { + t.Fatal("no commands should have returned") + } + } +} + +func TestCommandStoreDelete(t *testing.T) { + Setup() + + o1 := &model.Command{} + o1.CreatorId = model.NewId() + o1.Method = model.COMMAND_METHOD_POST + o1.TeamId = model.NewId() + o1.URL = "http://nowhere.com/" + + o1 = (<-store.Command().Save(o1)).Data.(*model.Command) + + if r1 := <-store.Command().Get(o1.Id); r1.Err != nil { + t.Fatal(r1.Err) + } else { + if r1.Data.(*model.Command).CreateAt != o1.CreateAt { + t.Fatal("invalid returned command") + } + } + + if r2 := <-store.Command().Delete(o1.Id, model.GetMillis()); r2.Err != nil { + t.Fatal(r2.Err) + } + + if r3 := (<-store.Command().Get(o1.Id)); r3.Err == nil { + t.Log(r3.Data) + t.Fatal("Missing id should have failed") + } +} + +func TestCommandStoreDeleteByUser(t *testing.T) { + Setup() + + o1 := &model.Command{} + o1.CreatorId = model.NewId() + o1.Method = model.COMMAND_METHOD_POST + o1.TeamId = model.NewId() + o1.URL = "http://nowhere.com/" + + o1 = (<-store.Command().Save(o1)).Data.(*model.Command) + + if r1 := <-store.Command().Get(o1.Id); r1.Err != nil { + t.Fatal(r1.Err) + } else { + if r1.Data.(*model.Command).CreateAt != o1.CreateAt { + t.Fatal("invalid returned command") + } + } + + if r2 := <-store.Command().PermanentDeleteByUser(o1.CreatorId); r2.Err != nil { + t.Fatal(r2.Err) + } + + if r3 := (<-store.Command().Get(o1.Id)); r3.Err == nil { + t.Log(r3.Data) + t.Fatal("Missing id should have failed") + } +} + +func TestCommandStoreUpdate(t *testing.T) { + Setup() + + o1 := &model.Command{} + o1.CreatorId = model.NewId() + o1.Method = model.COMMAND_METHOD_POST + o1.TeamId = model.NewId() + o1.URL = "http://nowhere.com/" + + o1 = (<-store.Command().Save(o1)).Data.(*model.Command) + + o1.Token = model.NewId() + + if r2 := <-store.Command().Update(o1); r2.Err != nil { + t.Fatal(r2.Err) + } +} diff --git a/store/sql_store.go b/store/sql_store.go index d17a3e8c3..30a464586 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -47,6 +47,7 @@ type SqlStore struct { oauth OAuthStore system SystemStore webhook WebhookStore + command CommandStore preference PreferenceStore } @@ -119,6 +120,7 @@ func NewSqlStore() Store { sqlStore.oauth = NewSqlOAuthStore(sqlStore) sqlStore.system = NewSqlSystemStore(sqlStore) sqlStore.webhook = NewSqlWebhookStore(sqlStore) + sqlStore.command = NewSqlCommandStore(sqlStore) sqlStore.preference = NewSqlPreferenceStore(sqlStore) err := sqlStore.master.CreateTablesIfNotExists() @@ -135,6 +137,7 @@ func NewSqlStore() Store { sqlStore.oauth.(*SqlOAuthStore).UpgradeSchemaIfNeeded() sqlStore.system.(*SqlSystemStore).UpgradeSchemaIfNeeded() sqlStore.webhook.(*SqlWebhookStore).UpgradeSchemaIfNeeded() + sqlStore.command.(*SqlCommandStore).UpgradeSchemaIfNeeded() sqlStore.preference.(*SqlPreferenceStore).UpgradeSchemaIfNeeded() sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists() @@ -146,6 +149,7 @@ func NewSqlStore() Store { sqlStore.oauth.(*SqlOAuthStore).CreateIndexesIfNotExists() sqlStore.system.(*SqlSystemStore).CreateIndexesIfNotExists() sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists() + sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists() sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists() sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures() @@ -530,6 +534,10 @@ func (ss SqlStore) Webhook() WebhookStore { return ss.webhook } +func (ss SqlStore) Command() CommandStore { + return ss.command +} + func (ss SqlStore) Preference() PreferenceStore { return ss.preference } diff --git a/store/store.go b/store/store.go index 8e03c8ee7..b19bba826 100644 --- a/store/store.go +++ b/store/store.go @@ -37,6 +37,7 @@ type Store interface { OAuth() OAuthStore System() SystemStore Webhook() WebhookStore + Command() CommandStore Preference() PreferenceStore MarkSystemRanUnitTests() Close() @@ -182,6 +183,15 @@ type WebhookStore interface { UpdateOutgoing(hook *model.OutgoingWebhook) StoreChannel } +type CommandStore interface { + Save(webhook *model.Command) StoreChannel + Get(id string) StoreChannel + GetByTeam(teamId string) StoreChannel + Delete(commandId string, time int64) StoreChannel + PermanentDeleteByUser(userId string) StoreChannel + Update(hook *model.Command) StoreChannel +} + type PreferenceStore interface { Save(preferences *model.Preferences) StoreChannel Get(userId string, category string, name string) StoreChannel -- cgit v1.2.3-1-g7c22 From 3fba8e42b140c1189bf3c06882cce5e2231e63da Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 8 Jan 2016 12:41:26 -0600 Subject: partial fix for UI --- api/command.go | 178 +++++++++++++++++++++++++++++------ api/command_echo.go | 81 ++++++++++++++++ api/command_echo_test.go | 42 +++++++++ api/command_test.go | 130 +++++++++++++++++++++++-- api/user.go | 4 + config/config.json | 41 ++++++-- docker/dev/config_docker.json | 2 + docker/local/config_docker.json | 2 + model/client.go | 20 +++- model/command_response.go | 40 ++++++++ model/command_response_test.go | 19 ++++ model/config.go | 44 +++++---- model/version.go | 10 +- utils/config.go | 2 + web/react/components/create_post.jsx | 7 +- web/react/utils/async_client.jsx | 20 ++-- web/react/utils/client.jsx | 16 +++- 17 files changed, 579 insertions(+), 79 deletions(-) create mode 100644 api/command_echo.go create mode 100644 api/command_echo_test.go create mode 100644 model/command_response.go create mode 100644 model/command_response_test.go diff --git a/api/command.go b/api/command.go index cff30cdbb..1e67453fb 100644 --- a/api/command.go +++ b/api/command.go @@ -4,45 +4,169 @@ package api import ( - // "io" - // "net/http" + //"io" + "net/http" // "path" // "strconv" - // "strings" + "strings" // "time" l4g "code.google.com/p/log4go" "github.com/gorilla/mux" - // "github.com/mattermost/platform/model" - // "github.com/mattermost/platform/utils" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" ) -// type commandHandler func(c *Context, command *model.Command) bool +type CommandProvider interface { + GetCommand() *model.Command + DoCommand(c *Context, channelId string, message string) *model.CommandResponse +} -// var ( -// cmds = map[string]string{ -// "logoutCommand": "/logout", -// "joinCommand": "/join", -// "loadTestCommand": "/loadtest", -// "echoCommand": "/echo", -// "shrugCommand": "/shrug", -// "meCommand": "/me", -// } -// commands = []commandHandler{ -// logoutCommand, -// joinCommand, -// loadTestCommand, -// echoCommand, -// shrugCommand, -// meCommand, -// } -// commandNotImplementedErr = model.NewAppError("checkCommand", "Command not implemented", "") -// ) -// var echoSem chan bool +var commandProviders = make(map[string]CommandProvider) + +func RegisterCommandProvider(newProvider CommandProvider) { + commandProviders[newProvider.GetCommand().Trigger] = newProvider +} + +func GetCommandProvidersProvider(name string) CommandProvider { + provider, ok := commandProviders[name] + if ok { + return provider + } + + return nil +} func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") - // r.Handle("/command", ApiUserRequired(command)).Methods("POST") + + sr := r.PathPrefix("/commands").Subrouter() + + sr.Handle("/execute", ApiUserRequired(execute)).Methods("POST") + sr.Handle("/list", ApiUserRequired(listCommands)).Methods("POST") + + sr.Handle("/create", ApiUserRequired(create)).Methods("POST") + sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET") + // sr.Handle("/regen_token", ApiUserRequired(regenOutgoingHookToken)).Methods("POST") + // sr.Handle("/delete", ApiUserRequired(deleteOutgoingHook)).Methods("POST") +} + +func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { + commands := make([]*model.Command, 0, 32) + for _, value := range commandProviders { + cpy := *value.GetCommand() + cpy.Token = "" + cpy.CreatorId = "" + cpy.Method = "" + cpy.URL = "" + cpy.Username = "" + cpy.IconURL = "" + commands = append(commands, &cpy) + } + + w.Write([]byte(model.CommandListToJson(commands))) +} + +func execute(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + command := strings.TrimSpace(props["command"]) + channelId := strings.TrimSpace(props["channelId"]) + + if len(command) <= 1 || strings.Index(command, "/") != 0 { + c.Err = model.NewAppError("command", "Command must start with /", "") + return + } + + if len(channelId) > 0 { + cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) + + if !c.HasPermissionsToChannel(cchan, "checkCommand") { + return + } + } + + parts := strings.Split(command, " ") + trigger := parts[0][1:] + provider := GetCommandProvidersProvider(trigger) + + if provider != nil { + message := strings.Join(parts[1:], " ") + response := provider.DoCommand(c, channelId, message) + + if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL { + post := &model.Post{} + post.ChannelId = channelId + post.Message = response.Text + if _, err := CreatePost(c, post, true); err != nil { + c.Err = model.NewAppError("command", "An error while saving the command response to the channel", "") + } + } + + w.Write([]byte(response.ToJson())) + } else { + c.Err = model.NewAppError("command", "Command with a trigger of '"+trigger+"' not found", "") + } +} + +func create(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + c.LogAudit("attempt") + + cmd := model.CommandFromJson(r.Body) + + if cmd == nil { + c.SetInvalidParam("createCommand", "command") + return + } + + cmd.CreatorId = c.Session.UserId + cmd.TeamId = c.Session.TeamId + + if result := <-Srv.Store.Command().Save(cmd); result.Err != nil { + c.Err = result.Err + return + } else { + c.LogAudit("success") + rcmd := result.Data.(*model.Command) + w.Write([]byte(rcmd.ToJson())) + } +} + +func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil { + c.Err = result.Err + return + } else { + cmds := result.Data.([]*model.Command) + w.Write([]byte(model.CommandListToJson(cmds))) + } } // func command(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api/command_echo.go b/api/command_echo.go new file mode 100644 index 000000000..5d34578c8 --- /dev/null +++ b/api/command_echo.go @@ -0,0 +1,81 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "strconv" + "strings" + "time" + + l4g "code.google.com/p/log4go" + "github.com/mattermost/platform/model" +) + +var echoSem chan bool + +type EchoProvider struct { +} + +func init() { + RegisterCommandProvider(&EchoProvider{}) +} + +func (me *EchoProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "echo", + AutoComplete: true, + AutoCompleteDesc: "Echo back text from your account", + AutoCompleteHint: "\"message\" [delay in seconds]", + DisplayName: "echo", + } +} + +func (me *EchoProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + maxThreads := 100 + + delay := 0 + if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { + if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { + delay = checkDelay + } + message = message[1:endMsg] + } else if strings.Index(message, " ") > -1 { + delayIdx := strings.LastIndex(message, " ") + delayStr := strings.Trim(message[delayIdx:], " ") + + if checkDelay, err := strconv.Atoi(delayStr); err == nil { + delay = checkDelay + message = message[:delayIdx] + } + } + + if delay > 10000 { + return &model.CommandResponse{Text: "Delays must be under 10000 seconds", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if echoSem == nil { + // We want one additional thread allowed so we never reach channel lockup + echoSem = make(chan bool, maxThreads+1) + } + + if len(echoSem) >= maxThreads { + return &model.CommandResponse{Text: "High volume of echo request, cannot process request", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + echoSem <- true + go func() { + defer func() { <-echoSem }() + post := &model.Post{} + post.ChannelId = channelId + post.Message = message + + time.Sleep(time.Duration(delay) * time.Second) + + if _, err := CreatePost(c, post, true); err != nil { + l4g.Error("Unable to create /echo post, err=%v", err) + } + }() + + return &model.CommandResponse{} +} diff --git a/api/command_echo_test.go b/api/command_echo_test.go new file mode 100644 index 000000000..40a0bb4b9 --- /dev/null +++ b/api/command_echo_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestEchoCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + echoTestString := "/echo test" + + r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Echo command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Echo command failed to send") + } +} diff --git a/api/command_test.go b/api/command_test.go index 8b996b9eb..8e0c2580e 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -3,15 +3,127 @@ package api -// import ( -// "strings" -// "testing" -// "time" - -// "github.com/mattermost/platform/model" -// "github.com/mattermost/platform/store" -// "github.com/mattermost/platform/utils" -// ) +import ( + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" +) + +func TestListCommands(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + if results, err := Client.ListCommands(); err != nil { + t.Fatal(err) + } else { + commands := results.Data.([]*model.Command) + foundEcho := false + + for _, command := range commands { + if command.Trigger == "echo" { + foundEcho = true + } + } + + if !foundEcho { + t.Fatal("Couldn't find echo command") + } + } +} + +func TestCreateCommand(t *testing.T) { + Setup() + *utils.Cfg.ServiceSettings.EnableCommands = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + + if _, err := Client.CreateCommand(cmd); err == nil { + t.Fatal("should have failed because not admin") + } + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + var rcmd *model.Command + if result, err := Client.CreateCommand(cmd); err != nil { + t.Fatal(err) + } else { + rcmd = result.Data.(*model.Command) + } + + if rcmd.CreatorId != user.Id { + t.Fatal("user ids didn't match") + } + + if rcmd.TeamId != team.Id { + t.Fatal("team ids didn't match") + } + + cmd = &model.Command{CreatorId: "123", TeamId: "456", URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + if result, err := Client.CreateCommand(cmd); err != nil { + t.Fatal(err) + } else { + if result.Data.(*model.Command).CreatorId != user.Id { + t.Fatal("bad user id wasn't overwritten") + } + if result.Data.(*model.Command).TeamId != team.Id { + t.Fatal("bad team id wasn't overwritten") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} + +func TestListTeamCommands(t *testing.T) { + Setup() + *utils.Cfg.ServiceSettings.EnableCommands = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command) + + if result, err := Client.ListTeamCommands(); err != nil { + t.Fatal(err) + } else { + cmds := result.Data.([]*model.Command) + + if len(hooks) != 1 { + t.Fatal("incorrect number of cmd") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} // func TestSuggestRootCommands(t *testing.T) { // Setup() diff --git a/api/user.go b/api/user.go index d4c7fcaf5..494296fb5 100644 --- a/api/user.go +++ b/api/user.go @@ -1435,6 +1435,10 @@ func PermanentDeleteUser(c *Context, user *model.User) *model.AppError { return result.Err } + if result := <-Srv.Store.Command().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + if result := <-Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil { return result.Err } diff --git a/config/config.json b/config/config.json index c43db1e50..b3822692e 100644 --- a/config/config.json +++ b/config/config.json @@ -5,17 +5,19 @@ "SegmentDeveloperKey": "", "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": false, - "EnableOutgoingWebhooks": false, - "EnablePostUsernameOverride": false, - "EnablePostIconOverride": false, + "EnableIncomingWebhooks": true, + "EnableOutgoingWebhooks": true, + "EnableCommands": true, + "EnableOnlyAdminIntegrations": true, + "EnablePostUsernameOverride": true, + "EnablePostIconOverride": true, "EnableTesting": false, "EnableDeveloper": false, "EnableSecurityFixAlert": true, - "SessionLengthWebInDays" : 30, - "SessionLengthMobileInDays" : 30, - "SessionLengthSSOInDays" : 30, - "SessionCacheInMinutes" : 10 + "SessionLengthWebInDays": 30, + "SessionLengthMobileInDays": 30, + "SessionLengthSSOInDays": 30, + "SessionCacheInMinutes": 10 }, "TeamSettings": { "SiteName": "Mattermost", @@ -107,5 +109,28 @@ "AuthEndpoint": "", "TokenEndpoint": "", "UserApiEndpoint": "" + }, + "GoogleSettings": { + "Enable": false, + "Secret": "", + "Id": "", + "Scope": "", + "AuthEndpoint": "", + "TokenEndpoint": "", + "UserApiEndpoint": "" + }, + "LdapSettings": { + "Enable": false, + "LdapServer": null, + "LdapPort": 389, + "BaseDN": null, + "BindUsername": null, + "BindPassword": null, + "FirstNameAttribute": null, + "LastNameAttribute": null, + "EmailAttribute": null, + "UsernameAttribute": null, + "IdAttribute": null, + "QueryTimeout": 60 } } \ No newline at end of file diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json index 1aa2ee843..cbe617d3a 100644 --- a/docker/dev/config_docker.json +++ b/docker/dev/config_docker.json @@ -7,6 +7,8 @@ "EnableOAuthServiceProvider": false, "EnableIncomingWebhooks": false, "EnableOutgoingWebhooks": false, + "EnableCommands": false, + "EnableOnlyAdminIntegrations": false, "EnablePostUsernameOverride": false, "EnablePostIconOverride": false, "EnableTesting": false, diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json index 1aa2ee843..cbe617d3a 100644 --- a/docker/local/config_docker.json +++ b/docker/local/config_docker.json @@ -7,6 +7,8 @@ "EnableOAuthServiceProvider": false, "EnableIncomingWebhooks": false, "EnableOutgoingWebhooks": false, + "EnableCommands": false, + "EnableOnlyAdminIntegrations": false, "EnablePostUsernameOverride": false, "EnablePostIconOverride": false, "EnableTesting": false, diff --git a/model/client.go b/model/client.go index f1773f3c7..83d1d316c 100644 --- a/model/client.go +++ b/model/client.go @@ -372,7 +372,25 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul m["command"] = command m["channelId"] = channelId m["suggest"] = strconv.FormatBool(suggest) - if r, err := c.DoApiPost("/command", MapToJson(m)); err != nil { + if r, err := c.DoApiPost("/commands/execute", MapToJson(m)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandResponseFromJson(r.Body)}, nil + } +} + +func (c *Client) ListCommands() (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/list", ""); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil + } +} + +func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/create", cmd.ToJson()); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), diff --git a/model/command_response.go b/model/command_response.go new file mode 100644 index 000000000..001384864 --- /dev/null +++ b/model/command_response.go @@ -0,0 +1,40 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + COMMAND_RESPONSE_TYPE_IN_CHANNEL = "in_channel" + COMMAND_RESPONSE_TYPE_EPHEMERAL = "ephemeral" +) + +type CommandResponse struct { + ResponseType string `json:"response_type"` + Text string `json:"text"` + Attachments interface{} `json:"attachments"` +} + +func (o *CommandResponse) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + +func CommandResponseFromJson(data io.Reader) *CommandResponse { + decoder := json.NewDecoder(data) + var o CommandResponse + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} diff --git a/model/command_response_test.go b/model/command_response_test.go new file mode 100644 index 000000000..7aa3e984b --- /dev/null +++ b/model/command_response_test.go @@ -0,0 +1,19 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "strings" + "testing" +) + +func TestCommandResponseJson(t *testing.T) { + o := CommandResponse{Text: "test"} + json := o.ToJson() + ro := CommandResponseFromJson(strings.NewReader(json)) + + if o.Text != ro.Text { + t.Fatal("Ids do not match") + } +} diff --git a/model/config.go b/model/config.go index ed56ed0c7..7d9ff41f1 100644 --- a/model/config.go +++ b/model/config.go @@ -24,22 +24,24 @@ const ( ) type ServiceSettings struct { - ListenAddress string - MaximumLoginAttempts int - SegmentDeveloperKey string - GoogleDeveloperKey string - EnableOAuthServiceProvider bool - EnableIncomingWebhooks bool - EnableOutgoingWebhooks bool - EnablePostUsernameOverride bool - EnablePostIconOverride bool - EnableTesting bool - EnableDeveloper *bool - EnableSecurityFixAlert *bool - SessionLengthWebInDays *int - SessionLengthMobileInDays *int - SessionLengthSSOInDays *int - SessionCacheInMinutes *int + ListenAddress string + MaximumLoginAttempts int + SegmentDeveloperKey string + GoogleDeveloperKey string + EnableOAuthServiceProvider bool + EnableIncomingWebhooks bool + EnableOutgoingWebhooks bool + EnableCommands *bool + EnableOnlyAdminIntegrations *bool + EnablePostUsernameOverride bool + EnablePostIconOverride bool + EnableTesting bool + EnableDeveloper *bool + EnableSecurityFixAlert *bool + SessionLengthWebInDays *int + SessionLengthMobileInDays *int + SessionLengthSSOInDays *int + SessionCacheInMinutes *int } type SSOSettings struct { @@ -330,6 +332,16 @@ func (o *Config) SetDefaults() { o.ServiceSettings.SessionCacheInMinutes = new(int) *o.ServiceSettings.SessionCacheInMinutes = 10 } + + if o.ServiceSettings.EnableCommands == nil { + o.ServiceSettings.EnableCommands = new(bool) + *o.ServiceSettings.EnableCommands = false + } + + if o.ServiceSettings.EnableOnlyAdminIntegrations == nil { + o.ServiceSettings.EnableOnlyAdminIntegrations = new(bool) + *o.ServiceSettings.EnableOnlyAdminIntegrations = true + } } func (o *Config) IsValid() *AppError { diff --git a/model/version.go b/model/version.go index 142ddb371..e6faf137c 100644 --- a/model/version.go +++ b/model/version.go @@ -24,10 +24,10 @@ var versions = []string{ } var CurrentVersion string = versions[0] -var BuildNumber = "_BUILD_NUMBER_" -var BuildDate = "_BUILD_DATE_" -var BuildHash = "_BUILD_HASH_" -var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_" +var BuildNumber = "dev" +var BuildDate = "Fri Jan 8 14:19:26 UTC 2016" +var BuildHash = "001a4448ca5fb0018eeb442915b473b121c04bf3" +var BuildEnterpriseReady = "false" func SplitVersion(version string) (int64, int64, int64) { parts := strings.Split(version, ".") @@ -73,7 +73,7 @@ func GetPreviousVersion(currentVersion string) (int64, int64) { } func IsOfficalBuild() bool { - return BuildNumber != "_BUILD_NUMBER_" + return BuildNumber != "dev" } func IsCurrentVersion(versionToCheck string) bool { diff --git a/utils/config.go b/utils/config.go index 18bd15241..1e6b58ced 100644 --- a/utils/config.go +++ b/utils/config.go @@ -194,6 +194,8 @@ func getClientConfig(c *model.Config) map[string]string { props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey props["EnableIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableIncomingWebhooks) props["EnableOutgoingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableOutgoingWebhooks) + props["EnableCommands"] = strconv.FormatBool(*c.ServiceSettings.EnableCommands) + props["EnableOnlyAdminIntegrations"] = strconv.FormatBool(*c.ServiceSettings.EnableOnlyAdminIntegrations) props["EnablePostUsernameOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostUsernameOverride) props["EnablePostIconOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostIconOverride) props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper) diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index e901b272a..72bf83e43 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -134,15 +134,10 @@ export default class CreatePost extends React.Component { post.message, false, (data) => { - if (data.response === 'not implemented') { - this.sendMessage(post); - return; - } - PostStore.storeDraft(data.channel_id, null); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); - if (data.goto_location.length > 0) { + if (data.goto_location && data.goto_location.length > 0) { window.location.href = data.goto_location; } }, diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index f218270da..78be646ac 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -747,20 +747,28 @@ export function savePreferences(preferences, success, error) { } export function getSuggestedCommands(command, suggestionId, component) { - client.executeCommand( - '', - command, - true, + client.listCommands( (data) => { + var matches = []; + data.forEach((cmd) => { + if (('/' + cmd.trigger).indexOf(command) === 0) { + matches.push({ + suggestion: '/' + cmd.trigger + ' ' + cmd.auto_complete_hint, + description: cmd.auto_complete_desc + }); + } + }); + // pull out the suggested commands from the returned data - const terms = data.suggestions.map((suggestion) => suggestion.suggestion); + //const terms = matches.map((suggestion) => suggestion.trigger); + const terms = matches.map((suggestion) => suggestion.suggestion); AppDispatcher.handleServerAction({ type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, id: suggestionId, matchedPretext: command, terms, - items: data.suggestions, + items: matches, component }); }, diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index e1c331aff..56c8c4b3e 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -839,7 +839,7 @@ export function getChannelExtraInfo(id, success, error) { export function executeCommand(channelId, command, suggest, success, error) { $.ajax({ - url: '/api/v1/command', + url: '/api/v1/commands/execute', dataType: 'json', contentType: 'application/json', type: 'POST', @@ -852,6 +852,20 @@ export function executeCommand(channelId, command, suggest, success, error) { }); } +export function listCommands(success, error) { + $.ajax({ + url: '/api/v1/commands/list', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + success, + error: function onError(xhr, status, err) { + var e = handleError('listCommands', xhr, status, err); + error(e); + } + }); +} + export function getPostsPage(channelId, offset, limit, success, error, complete) { $.ajax({ cache: false, -- cgit v1.2.3-1-g7c22 From e1f4cc4bb004a0a0d4bb6d68ff328233f9f72aa0 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 8 Jan 2016 22:57:38 -0600 Subject: Adding web service methods --- api/command.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++----- api/command_test.go | 75 ++++++++++++++++++++++++++- api/webhook.go | 58 ++++++++++++++++++++- model/client.go | 29 ++++++++++- model/command.go | 9 ++++ model/utils.go | 6 ++- 6 files changed, 303 insertions(+), 18 deletions(-) diff --git a/api/command.go b/api/command.go index 1e67453fb..d74643e15 100644 --- a/api/command.go +++ b/api/command.go @@ -37,37 +37,59 @@ func GetCommandProvidersProvider(name string) CommandProvider { return nil } +// cmds = map[string]string{ +// "logoutCommand": "/logout", +// "joinCommand": "/join", +// "loadTestCommand": "/loadtest", +// "echoCommand": "/echo", +// "shrugCommand": "/shrug", +// "meCommand": "/me", +// } + func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") sr := r.PathPrefix("/commands").Subrouter() - sr.Handle("/execute", ApiUserRequired(execute)).Methods("POST") - sr.Handle("/list", ApiUserRequired(listCommands)).Methods("POST") + sr.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST") + sr.Handle("/list", ApiUserRequired(listCommands)).Methods("GET") - sr.Handle("/create", ApiUserRequired(create)).Methods("POST") + sr.Handle("/create", ApiUserRequired(createCommand)).Methods("POST") sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET") - // sr.Handle("/regen_token", ApiUserRequired(regenOutgoingHookToken)).Methods("POST") - // sr.Handle("/delete", ApiUserRequired(deleteOutgoingHook)).Methods("POST") + sr.Handle("/regen_token", ApiUserRequired(regenCommandToken)).Methods("POST") + sr.Handle("/delete", ApiUserRequired(deleteCommand)).Methods("POST") } func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { commands := make([]*model.Command, 0, 32) + seen := make(map[string]bool) for _, value := range commandProviders { cpy := *value.GetCommand() - cpy.Token = "" - cpy.CreatorId = "" - cpy.Method = "" - cpy.URL = "" - cpy.Username = "" - cpy.IconURL = "" - commands = append(commands, &cpy) + if cpy.AutoComplete && !seen[cpy.Id] { + cpy.Sanatize() + seen[cpy.Trigger] = true + commands = append(commands, &cpy) + } + } + + if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil { + c.Err = result.Err + return + } else { + teamCmds := result.Data.([]*model.Command) + for _, cmd := range teamCmds { + if cmd.AutoComplete && !seen[cmd.Id] { + cmd.Sanatize() + seen[cmd.Trigger] = true + commands = append(commands, cmd) + } + } } w.Write([]byte(model.CommandListToJson(commands))) } -func execute(c *Context, w http.ResponseWriter, r *http.Request) { +func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) command := strings.TrimSpace(props["command"]) channelId := strings.TrimSpace(props["channelId"]) @@ -108,7 +130,7 @@ func execute(c *Context, w http.ResponseWriter, r *http.Request) { } } -func create(c *Context, w http.ResponseWriter, r *http.Request) { +func createCommand(c *Context, w http.ResponseWriter, r *http.Request) { if !*utils.Cfg.ServiceSettings.EnableCommands { c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") c.Err.StatusCode = http.StatusNotImplemented @@ -169,6 +191,100 @@ func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { } } +func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + c.LogAudit("attempt") + + props := model.MapFromJson(r.Body) + + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("regenCommandToken", "id") + return + } + + var cmd *model.Command + if result := <-Srv.Store.Command().Get(id); result.Err != nil { + c.Err = result.Err + return + } else { + cmd = result.Data.(*model.Command) + + if c.Session.TeamId != cmd.TeamId && c.Session.UserId != cmd.CreatorId && !c.IsTeamAdmin() { + c.LogAudit("fail - inappropriate permissions") + c.Err = model.NewAppError("regenToken", "Inappropriate permissions to regenerate command token", "user_id="+c.Session.UserId) + return + } + } + + cmd.Token = model.NewId() + + if result := <-Srv.Store.Command().Update(cmd); result.Err != nil { + c.Err = result.Err + return + } else { + w.Write([]byte(result.Data.(*model.Command).ToJson())) + } +} + +func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + c.LogAudit("attempt") + + props := model.MapFromJson(r.Body) + + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("deleteCommand", "id") + return + } + + if result := <-Srv.Store.Command().Get(id); result.Err != nil { + c.Err = result.Err + return + } else { + if c.Session.TeamId != result.Data.(*model.Command).TeamId && c.Session.UserId != result.Data.(*model.Command).CreatorId && !c.IsTeamAdmin() { + c.LogAudit("fail - inappropriate permissions") + c.Err = model.NewAppError("deleteCommand", "Inappropriate permissions to delete command", "user_id="+c.Session.UserId) + return + } + } + + if err := (<-Srv.Store.Command().Delete(id, model.GetMillis())).Err; err != nil { + c.Err = err + return + } + + c.LogAudit("success") + w.Write([]byte(model.MapToJson(props))) +} + // func command(c *Context, w http.ResponseWriter, r *http.Request) { // props := model.MapFromJson(r.Body) diff --git a/api/command_test.go b/api/command_test.go index 8e0c2580e..b5c0558b8 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -107,6 +107,10 @@ func TestListTeamCommands(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} @@ -117,7 +121,7 @@ func TestListTeamCommands(t *testing.T) { } else { cmds := result.Data.([]*model.Command) - if len(hooks) != 1 { + if len(cmds) != 1 { t.Fatal("incorrect number of cmd") } } @@ -125,6 +129,75 @@ func TestListTeamCommands(t *testing.T) { *utils.Cfg.ServiceSettings.EnableCommands = false } +func TestRegenToken(t *testing.T) { + Setup() + *utils.Cfg.ServiceSettings.EnableCommands = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) + + data := make(map[string]string) + data["id"] = cmd.Id + + if result, err := Client.RegenCommandToken(data); err != nil { + t.Fatal(err) + } else { + if result.Data.(*model.Command).Token == cmd.Token { + t.Fatal("regen didn't work properly") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} + +func TestDeleteCommand(t *testing.T) { + Setup() + *utils.Cfg.ServiceSettings.EnableCommands = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) + + data := make(map[string]string) + data["id"] = cmd.Id + + if _, err := Client.DeleteCommand(data); err != nil { + t.Fatal(err) + } + + cmds := Client.Must(Client.ListTeamCommands()).Data.([]*model.Command) + if len(cmds) != 0 { + t.Fatal("delete didn't work properly") + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} + // func TestSuggestRootCommands(t *testing.T) { // Setup() diff --git a/api/webhook.go b/api/webhook.go index 34c308879..de3d567ec 100644 --- a/api/webhook.go +++ b/api/webhook.go @@ -32,6 +32,14 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") hook := model.IncomingWebhookFromJson(r.Body) @@ -79,6 +87,14 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") props := model.MapFromJson(r.Body) @@ -116,6 +132,14 @@ func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + if result := <-Srv.Store.Webhook().GetIncomingByUser(c.Session.UserId); result.Err != nil { c.Err = result.Err return @@ -132,6 +156,14 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") hook := model.OutgoingWebhookFromJson(r.Body) @@ -188,6 +220,14 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + if result := <-Srv.Store.Webhook().GetOutgoingByCreator(c.Session.UserId); result.Err != nil { c.Err = result.Err return @@ -204,6 +244,14 @@ func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") props := model.MapFromJson(r.Body) @@ -241,6 +289,14 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") props := model.MapFromJson(r.Body) @@ -258,7 +314,7 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) } else { hook = result.Data.(*model.OutgoingWebhook) - if c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() { + if c.Session.TeamId != hook.TeamId && c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() { c.LogAudit("fail - inappropriate permissions") c.Err = model.NewAppError("regenOutgoingHookToken", "Inappropriate permissions to regenerate outcoming webhook token", "user_id="+c.Session.UserId) return diff --git a/model/client.go b/model/client.go index 83d1d316c..3a645e175 100644 --- a/model/client.go +++ b/model/client.go @@ -381,7 +381,16 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul } func (c *Client) ListCommands() (*Result, *AppError) { - if r, err := c.DoApiPost("/commands/list", ""); err != nil { + if r, err := c.DoApiGet("/commands/list", "", ""); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil + } +} + +func (c *Client) ListTeamCommands() (*Result, *AppError) { + if r, err := c.DoApiGet("/commands/list_team_commands", "", ""); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), @@ -398,6 +407,24 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { } } +func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/regen_token", MapToJson(data)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil + } +} + +func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/delete", MapToJson(data)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil + } +} + func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil { return nil, err diff --git a/model/command.go b/model/command.go index 253021896..c917a46ea 100644 --- a/model/command.go +++ b/model/command.go @@ -133,3 +133,12 @@ func (o *Command) PreSave() { func (o *Command) PreUpdate() { o.UpdateAt = GetMillis() } + +func (o *Command) Sanatize() { + o.Token = "" + o.CreatorId = "" + o.Method = "" + o.URL = "" + o.Username = "" + o.IconURL = "" +} diff --git a/model/utils.go b/model/utils.go index 617c95efd..301e36f59 100644 --- a/model/utils.go +++ b/model/utils.go @@ -54,7 +54,11 @@ func AppErrorFromJson(data io.Reader) *AppError { if err == nil { return &er } else { - return NewAppError("AppErrorFromJson", "could not decode", err.Error()) + buf := new(bytes.Buffer) + buf.ReadFrom(data) + s := buf.String() + + return NewAppError("AppErrorFromJson", "could not decode", err.Error()+" "+s) } } -- cgit v1.2.3-1-g7c22 From c3930cb609203a07a5af2850715a009dc5d3893b Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 8 Jan 2016 23:02:59 -0600 Subject: Fixing unit tests --- api/webhook_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/webhook_test.go b/api/webhook_test.go index 85117ec18..345987863 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -21,6 +21,10 @@ func TestCreateIncomingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -84,6 +88,10 @@ func TestListIncomingHooks(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -122,6 +130,10 @@ func TestDeleteIncomingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -162,6 +174,10 @@ func TestCreateOutgoingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -225,6 +241,10 @@ func TestListOutgoingHooks(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -263,6 +283,10 @@ func TestDeleteOutgoingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -303,6 +327,10 @@ func TestRegenOutgoingHookToken(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} -- cgit v1.2.3-1-g7c22 From 25538df397f46d03b99a4b492bcef7cf68859a8a Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 8 Jan 2016 23:13:05 -0600 Subject: Fixing unit tests --- web/web_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/web_test.go b/web/web_test.go index 8d40810b5..abe5ab2f1 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -193,6 +193,10 @@ func TestIncomingWebhook(t *testing.T) { user = ApiClient.Must(ApiClient.CreateUser(user, "")).Data.(*model.User) store.Must(api.Srv.Store.User().VerifyEmail(user.Id)) + c := &api.Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + api.UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) ApiClient.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} -- cgit v1.2.3-1-g7c22 From b1a7c1acf139efbb5312b4aa939bd94155e6a9e6 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Sat, 9 Jan 2016 08:54:07 -0600 Subject: adding different commands --- api/command.go | 272 ++------------------------------------------- api/command_echo_test.go | 2 +- api/command_join.go | 54 +++++++++ api/command_join_test.go | 71 ++++++++++++ api/command_logout.go | 29 +++++ api/command_logout_test.go | 32 ++++++ api/command_me.go | 29 +++++ api/command_me_test.go | 47 ++++++++ api/command_shrug.go | 29 +++++ api/command_shrug_test.go | 47 ++++++++ api/command_test.go | 173 ---------------------------- model/command_response.go | 1 + 12 files changed, 347 insertions(+), 439 deletions(-) create mode 100644 api/command_join.go create mode 100644 api/command_join_test.go create mode 100644 api/command_logout.go create mode 100644 api/command_logout_test.go create mode 100644 api/command_me.go create mode 100644 api/command_me_test.go create mode 100644 api/command_shrug.go create mode 100644 api/command_shrug_test.go diff --git a/api/command.go b/api/command.go index d74643e15..1161cc81a 100644 --- a/api/command.go +++ b/api/command.go @@ -4,12 +4,8 @@ package api import ( - //"io" "net/http" - // "path" - // "strconv" "strings" - // "time" l4g "code.google.com/p/log4go" "github.com/gorilla/mux" @@ -122,6 +118,13 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { if _, err := CreatePost(c, post, true); err != nil { c.Err = model.NewAppError("command", "An error while saving the command response to the channel", "") } + } else if response.ResponseType == model.COMMAND_RESPONSE_TYPE_EPHEMERAL { + post := &model.Post{} + post.ChannelId = channelId + post.Message = "TODO_EPHEMERAL: " + response.Text + if _, err := CreatePost(c, post, true); err != nil { + c.Err = model.NewAppError("command", "An error while saving the command response to the channel", "") + } } w.Write([]byte(response.ToJson())) @@ -285,267 +288,6 @@ func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(props))) } -// func command(c *Context, w http.ResponseWriter, r *http.Request) { - -// props := model.MapFromJson(r.Body) - -// command := &model.Command{ -// Command: strings.TrimSpace(props["command"]), -// ChannelId: strings.TrimSpace(props["channelId"]), -// Suggest: props["suggest"] == "true", -// Suggestions: make([]*model.SuggestCommand, 0, 128), -// } - -// checkCommand(c, command) -// if c.Err != nil { -// if c.Err != commandNotImplementedErr { -// return -// } else { -// c.Err = nil -// command.Response = model.RESP_NOT_IMPLEMENTED -// w.Write([]byte(command.ToJson())) -// return -// } -// } else { -// w.Write([]byte(command.ToJson())) -// } -// } - -// func checkCommand(c *Context, command *model.Command) bool { - -// if len(command.Command) == 0 || strings.Index(command.Command, "/") != 0 { -// c.Err = model.NewAppError("checkCommand", "Command must start with /", "") -// return false -// } - -// if len(command.ChannelId) > 0 { -// cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId) - -// if !c.HasPermissionsToChannel(cchan, "checkCommand") { -// return true -// } -// } - -// if !command.Suggest { -// implemented := false -// for _, cmd := range cmds { -// bounds := len(cmd) -// if len(command.Command) < bounds { -// continue -// } -// if command.Command[:bounds] == cmd { -// implemented = true -// } -// } -// if !implemented { -// c.Err = commandNotImplementedErr -// return false -// } -// } - -// for _, v := range commands { - -// if v(c, command) || c.Err != nil { -// return true -// } -// } - -// return false -// } - -// func logoutCommand(c *Context, command *model.Command) bool { - -// cmd := cmds["logoutCommand"] - -// if strings.Index(command.Command, cmd) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) - -// if !command.Suggest { -// command.GotoLocation = "/logout" -// command.Response = model.RESP_EXECUTED -// return true -// } - -// } else if strings.Index(cmd, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) -// } - -// return false -// } - -// func echoCommand(c *Context, command *model.Command) bool { -// cmd := cmds["echoCommand"] -// maxThreads := 100 - -// if !command.Suggest && strings.Index(command.Command, cmd) == 0 { -// parameters := strings.SplitN(command.Command, " ", 2) -// if len(parameters) != 2 || len(parameters[1]) == 0 { -// return false -// } -// message := strings.Trim(parameters[1], " ") -// delay := 0 -// if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { -// if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { -// delay = checkDelay -// } -// message = message[1:endMsg] -// } else if strings.Index(message, " ") > -1 { -// delayIdx := strings.LastIndex(message, " ") -// delayStr := strings.Trim(message[delayIdx:], " ") - -// if checkDelay, err := strconv.Atoi(delayStr); err == nil { -// delay = checkDelay -// message = message[:delayIdx] -// } -// } - -// if delay > 10000 { -// c.Err = model.NewAppError("echoCommand", "Delays must be under 10000 seconds", "") -// return false -// } - -// if echoSem == nil { -// // We want one additional thread allowed so we never reach channel lockup -// echoSem = make(chan bool, maxThreads+1) -// } - -// if len(echoSem) >= maxThreads { -// c.Err = model.NewAppError("echoCommand", "High volume of echo request, cannot process request", "") -// return false -// } - -// echoSem <- true -// go func() { -// defer func() { <-echoSem }() -// post := &model.Post{} -// post.ChannelId = command.ChannelId -// post.Message = message - -// time.Sleep(time.Duration(delay) * time.Second) - -// if _, err := CreatePost(c, post, true); err != nil { -// l4g.Error("Unable to create /echo post, err=%v", err) -// } -// }() - -// command.Response = model.RESP_EXECUTED -// return true - -// } else if strings.Index(cmd, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo back text from your account, /echo \"message\" [delay in seconds]"}) -// } - -// return false -// } - -// func meCommand(c *Context, command *model.Command) bool { -// cmd := cmds["meCommand"] - -// if !command.Suggest && strings.Index(command.Command, cmd) == 0 { -// message := "" - -// parameters := strings.SplitN(command.Command, " ", 2) -// if len(parameters) > 1 { -// message += "*" + parameters[1] + "*" -// } - -// post := &model.Post{} -// post.Message = message -// post.ChannelId = command.ChannelId -// if _, err := CreatePost(c, post, false); err != nil { -// l4g.Error("Unable to create /me post post, err=%v", err) -// return false -// } -// command.Response = model.RESP_EXECUTED -// return true - -// } else if strings.Index(cmd, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Do an action, /me [message]"}) -// } - -// return false -// } - -// func shrugCommand(c *Context, command *model.Command) bool { -// cmd := cmds["shrugCommand"] - -// if !command.Suggest && strings.Index(command.Command, cmd) == 0 { -// message := `¯\\\_(ツ)_/¯` - -// parameters := strings.SplitN(command.Command, " ", 2) -// if len(parameters) > 1 { -// message += " " + parameters[1] -// } - -// post := &model.Post{} -// post.Message = message -// post.ChannelId = command.ChannelId -// if _, err := CreatePost(c, post, false); err != nil { -// l4g.Error("Unable to create /shrug post post, err=%v", err) -// return false -// } -// command.Response = model.RESP_EXECUTED -// return true - -// } else if strings.Index(cmd, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Adds ¯\\_(ツ)_/¯ to your message, /shrug [message]"}) -// } - -// return false -// } - -// func joinCommand(c *Context, command *model.Command) bool { - -// // looks for "/join channel-name" -// cmd := cmds["joinCommand"] - -// if strings.Index(command.Command, cmd) == 0 { - -// parts := strings.Split(command.Command, " ") - -// startsWith := "" - -// if len(parts) == 2 { -// startsWith = parts[1] -// } - -// if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { -// c.Err = result.Err -// return false -// } else { -// channels := result.Data.(*model.ChannelList) - -// for _, v := range channels.Channels { - -// if v.Name == startsWith && !command.Suggest { - -// if v.Type == model.CHANNEL_DIRECT { -// return false -// } - -// JoinChannel(c, v.Id, "") - -// if c.Err != nil { -// return false -// } - -// command.GotoLocation = c.GetTeamURL() + "/channels/" + v.Name -// command.Response = model.RESP_EXECUTED -// return true -// } - -// if len(startsWith) == 0 || strings.Index(v.Name, startsWith) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd + " " + v.Name, Description: "Join the open channel"}) -// } -// } -// } -// } else if strings.Index(cmd, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Join an open channel"}) -// } - -// return false -// } - // func loadTestCommand(c *Context, command *model.Command) bool { // cmd := cmds["loadTestCommand"] diff --git a/api/command_echo_test.go b/api/command_echo_test.go index 40a0bb4b9..3bfaa0279 100644 --- a/api/command_echo_test.go +++ b/api/command_echo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/command_join.go b/api/command_join.go new file mode 100644 index 000000000..67c1c1ad1 --- /dev/null +++ b/api/command_join.go @@ -0,0 +1,54 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type JoinProvider struct { +} + +func init() { + RegisterCommandProvider(&JoinProvider{}) +} + +func (me *JoinProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "join", + AutoComplete: true, + AutoCompleteDesc: "Join the open channel", + AutoCompleteHint: "[channel-name]", + DisplayName: "join", + } +} + +func (me *JoinProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { + return &model.CommandResponse{Text: "An error occured while listing channels.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + channels := result.Data.(*model.ChannelList) + + for _, v := range channels.Channels { + + if v.Name == message { + + if v.Type == model.CHANNEL_DIRECT { + return &model.CommandResponse{Text: "An error occured while joining the channel.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + JoinChannel(c, v.Id, "") + + if c.Err != nil { + c.Err = nil + return &model.CommandResponse{Text: "An error occured while joining the channel.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + v.Name, Text: "Joined channel.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: "We couldn't find the channel"} +} diff --git a/api/command_join_test.go b/api/command_join_test.go new file mode 100644 index 000000000..7260915a6 --- /dev/null +++ b/api/command_join_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "strings" + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestJoinCommands(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel0 = Client.Must(Client.CreateChannel(channel0)).Data.(*model.Channel) + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + Client.Must(Client.LeaveChannel(channel1.Id)) + + channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) + Client.Must(Client.LeaveChannel(channel2.Id)) + + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + data := make(map[string]string) + data["user_id"] = user2.Id + channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel) + + rs5 := Client.Must(Client.Command(channel0.Id, "/join "+channel2.Name, false)).Data.(*model.CommandResponse) + if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { + t.Fatal("failed to join channel") + } + + rs6 := Client.Must(Client.Command(channel0.Id, "/join "+channel3.Name, false)).Data.(*model.CommandResponse) + if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { + t.Fatal("should not have joined direct message channel") + } + + c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) + + if len(c1.Channels) != 5 { // 4 because of town-square, off-topic and direct + t.Fatal("didn't join channel") + } + + found := false + for _, c := range c1.Channels { + if c.Name == channel2.Name { + found = true + break + } + } + if !found { + t.Fatal("didn't join channel") + } +} diff --git a/api/command_logout.go b/api/command_logout.go new file mode 100644 index 000000000..01e81aaf0 --- /dev/null +++ b/api/command_logout.go @@ -0,0 +1,29 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type LogoutProvider struct { +} + +func init() { + RegisterCommandProvider(&LogoutProvider{}) +} + +func (me *LogoutProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "logout", + AutoComplete: true, + AutoCompleteDesc: "Logout", + AutoCompleteHint: "", + DisplayName: "logout", + } +} + +func (me *LogoutProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + return &model.CommandResponse{GotoLocation: "/logout", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: "Logging out..."} +} diff --git a/api/command_logout_test.go b/api/command_logout_test.go new file mode 100644 index 000000000..4c8f3e017 --- /dev/null +++ b/api/command_logout_test.go @@ -0,0 +1,32 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestLogoutCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + rs1 := Client.Must(Client.Command(channel1.Id, "/logout", false)).Data.(*model.CommandResponse) + if rs1.GotoLocation != "/logout" { + t.Fatal("failed to logout") + } +} diff --git a/api/command_me.go b/api/command_me.go new file mode 100644 index 000000000..f0154fe53 --- /dev/null +++ b/api/command_me.go @@ -0,0 +1,29 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type MeProvider struct { +} + +func init() { + RegisterCommandProvider(&MeProvider{}) +} + +func (me *MeProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "me", + AutoComplete: true, + AutoCompleteDesc: "Do an action", + AutoCompleteHint: "[message]", + DisplayName: "me", + } +} + +func (me *MeProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: "*" + message + "*"} +} diff --git a/api/command_me_test.go b/api/command_me_test.go new file mode 100644 index 000000000..d55a15b2c --- /dev/null +++ b/api/command_me_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestMeCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + testString := "/me hello" + + r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `*hello*` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug reponse") + } + } +} diff --git a/api/command_shrug.go b/api/command_shrug.go new file mode 100644 index 000000000..e34f80110 --- /dev/null +++ b/api/command_shrug.go @@ -0,0 +1,29 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type ShrugProvider struct { +} + +func init() { + RegisterCommandProvider(&ShrugProvider{}) +} + +func (me *ShrugProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "shrug", + AutoComplete: true, + AutoCompleteDesc: `Adds ¯\_(ツ)_/¯ to your message`, + AutoCompleteHint: "[message]", + DisplayName: "shrug", + } +} + +func (me *ShrugProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: `¯\_(ツ)_/¯`} +} diff --git a/api/command_shrug_test.go b/api/command_shrug_test.go new file mode 100644 index 000000000..95ef9f115 --- /dev/null +++ b/api/command_shrug_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestShrugCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + testString := "/shrug" + + r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `¯\_(ツ)_/¯` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug reponse") + } + } +} diff --git a/api/command_test.go b/api/command_test.go index b5c0558b8..1583ac5bb 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -198,179 +198,6 @@ func TestDeleteCommand(t *testing.T) { *utils.Cfg.ServiceSettings.EnableCommands = false } -// func TestSuggestRootCommands(t *testing.T) { -// Setup() - -// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} -// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - -// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} -// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) -// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - -// Client.LoginByEmail(team.Name, user1.Email, "pwd") - -// if _, err := Client.Command("", "", true); err == nil { -// t.Fatal("Should fail") -// } - -// rs1 := Client.Must(Client.Command("", "/", true)).Data.(*model.Command) - -// hasLogout := false -// for _, v := range rs1.Suggestions { -// if v.Suggestion == "/logout" { -// hasLogout = true -// } -// } - -// if !hasLogout { -// t.Log(rs1.Suggestions) -// t.Fatal("should have logout cmd") -// } - -// rs2 := Client.Must(Client.Command("", "/log", true)).Data.(*model.Command) - -// if rs2.Suggestions[0].Suggestion != "/logout" { -// t.Fatal("should have logout cmd") -// } - -// rs3 := Client.Must(Client.Command("", "/joi", true)).Data.(*model.Command) - -// if rs3.Suggestions[0].Suggestion != "/join" { -// t.Fatal("should have join cmd") -// } - -// rs4 := Client.Must(Client.Command("", "/ech", true)).Data.(*model.Command) - -// if rs4.Suggestions[0].Suggestion != "/echo" { -// t.Fatal("should have echo cmd") -// } -// } - -// func TestLogoutCommands(t *testing.T) { -// Setup() - -// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} -// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - -// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} -// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) -// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - -// Client.LoginByEmail(team.Name, user1.Email, "pwd") - -// rs1 := Client.Must(Client.Command("", "/logout", false)).Data.(*model.Command) -// if rs1.GotoLocation != "/logout" { -// t.Fatal("failed to logout") -// } -// } - -// func TestJoinCommands(t *testing.T) { -// Setup() - -// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} -// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - -// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} -// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) -// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - -// Client.LoginByEmail(team.Name, user1.Email, "pwd") - -// channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} -// channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) -// Client.Must(Client.LeaveChannel(channel1.Id)) - -// channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} -// channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) -// Client.Must(Client.LeaveChannel(channel2.Id)) - -// user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} -// user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) -// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - -// data := make(map[string]string) -// data["user_id"] = user2.Id -// channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel) - -// rs1 := Client.Must(Client.Command("", "/join aa", true)).Data.(*model.Command) -// if rs1.Suggestions[0].Suggestion != "/join "+channel1.Name { -// t.Fatal("should have join cmd") -// } - -// rs2 := Client.Must(Client.Command("", "/join bb", true)).Data.(*model.Command) -// if rs2.Suggestions[0].Suggestion != "/join "+channel2.Name { -// t.Fatal("should have join cmd") -// } - -// rs3 := Client.Must(Client.Command("", "/join", true)).Data.(*model.Command) -// if len(rs3.Suggestions) != 2 { -// t.Fatal("should have 2 join cmd") -// } - -// rs4 := Client.Must(Client.Command("", "/join ", true)).Data.(*model.Command) -// if len(rs4.Suggestions) != 2 { -// t.Fatal("should have 2 join cmd") -// } - -// rs5 := Client.Must(Client.Command("", "/join "+channel2.Name, false)).Data.(*model.Command) -// if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { -// t.Fatal("failed to join channel") -// } - -// rs6 := Client.Must(Client.Command("", "/join "+channel3.Name, false)).Data.(*model.Command) -// if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { -// t.Fatal("should not have joined direct message channel") -// } - -// c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - -// if len(c1.Channels) != 4 { // 4 because of town-square, off-topic and direct -// t.Fatal("didn't join channel") -// } - -// found := false -// for _, c := range c1.Channels { -// if c.Name == channel2.Name { -// found = true -// break -// } -// } -// if !found { -// t.Fatal("didn't join channel") -// } -// } - -// func TestEchoCommand(t *testing.T) { -// Setup() - -// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} -// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - -// user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} -// user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) -// store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - -// Client.LoginByEmail(team.Name, user1.Email, "pwd") - -// channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} -// channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - -// echoTestString := "/echo test" - -// r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.Command) -// if r1.Response != model.RESP_EXECUTED { -// t.Fatal("Echo command failed to execute") -// } - -// time.Sleep(100 * time.Millisecond) - -// p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) -// if len(p1.Order) != 1 { -// t.Fatal("Echo command failed to send") -// } -// } - // func TestLoadTestUrlCommand(t *testing.T) { // Setup() diff --git a/model/command_response.go b/model/command_response.go index 001384864..9314f38ef 100644 --- a/model/command_response.go +++ b/model/command_response.go @@ -16,6 +16,7 @@ const ( type CommandResponse struct { ResponseType string `json:"response_type"` Text string `json:"text"` + GotoLocation string `json:"goto_location"` Attachments interface{} `json:"attachments"` } -- cgit v1.2.3-1-g7c22 From 3edcf960a0502fbeb3e4b46e87ecb958646eeb39 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Sat, 9 Jan 2016 09:22:14 -0600 Subject: Fixing unit tests --- api/command.go | 9 --------- api/command_shrug.go | 7 ++++++- api/command_shrug_test.go | 2 +- web/react/utils/client.jsx | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/api/command.go b/api/command.go index 1161cc81a..2147196fa 100644 --- a/api/command.go +++ b/api/command.go @@ -33,15 +33,6 @@ func GetCommandProvidersProvider(name string) CommandProvider { return nil } -// cmds = map[string]string{ -// "logoutCommand": "/logout", -// "joinCommand": "/join", -// "loadTestCommand": "/loadtest", -// "echoCommand": "/echo", -// "shrugCommand": "/shrug", -// "meCommand": "/me", -// } - func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") diff --git a/api/command_shrug.go b/api/command_shrug.go index e34f80110..c49bd46ae 100644 --- a/api/command_shrug.go +++ b/api/command_shrug.go @@ -25,5 +25,10 @@ func (me *ShrugProvider) GetCommand() *model.Command { } func (me *ShrugProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: `¯\_(ツ)_/¯`} + rmsg := `¯\\\_(ツ)\_/¯` + if len(message) > 0 { + rmsg = message + " " + rmsg + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: rmsg} } diff --git a/api/command_shrug_test.go b/api/command_shrug_test.go index 95ef9f115..92cecf664 100644 --- a/api/command_shrug_test.go +++ b/api/command_shrug_test.go @@ -39,7 +39,7 @@ func TestShrugCommand(t *testing.T) { if len(p1.Order) != 1 { t.Fatal("Command failed to send") } else { - if p1.Posts[p1.Order[0]].Message != `¯\_(ツ)_/¯` { + if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` { t.Log(p1.Posts[p1.Order[0]].Message) t.Fatal("invalid shrug reponse") } diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 56c8c4b3e..39629b529 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -857,7 +857,7 @@ export function listCommands(success, error) { url: '/api/v1/commands/list', dataType: 'json', contentType: 'application/json', - type: 'POST', + type: 'GET', success, error: function onError(xhr, status, err) { var e = handleError('listCommands', xhr, status, err); -- cgit v1.2.3-1-g7c22 From a70d5504091b93773e3cc0290be32ae51660bf30 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Sun, 10 Jan 2016 01:13:51 -0600 Subject: Adding loading testing commands --- api/command.go | 339 ---------------------------------------- api/command_loadtest.go | 357 +++++++++++++++++++++++++++++++++++++++++++ api/command_loadtest_test.go | 220 ++++++++++++++++++++++++++ api/command_logout_test.go | 2 +- api/command_test.go | 55 ------- 5 files changed, 578 insertions(+), 395 deletions(-) create mode 100644 api/command_loadtest.go create mode 100644 api/command_loadtest_test.go diff --git a/api/command.go b/api/command.go index 2147196fa..8de109846 100644 --- a/api/command.go +++ b/api/command.go @@ -278,342 +278,3 @@ func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("success") w.Write([]byte(model.MapToJson(props))) } - -// func loadTestCommand(c *Context, command *model.Command) bool { -// cmd := cmds["loadTestCommand"] - -// // This command is only available when EnableTesting is true -// if !utils.Cfg.ServiceSettings.EnableTesting { -// return false -// } - -// if strings.Index(command.Command, cmd) == 0 { -// if loadTestSetupCommand(c, command) { -// return true -// } -// if loadTestUsersCommand(c, command) { -// return true -// } -// if loadTestChannelsCommand(c, command) { -// return true -// } -// if loadTestPostsCommand(c, command) { -// return true -// } -// if loadTestUrlCommand(c, command) { -// return true -// } -// } else if strings.Index(cmd, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Debug Load Testing"}) -// } - -// return false -// } - -// func parseRange(command string, cmd string) (utils.Range, bool) { -// tokens := strings.Fields(strings.TrimPrefix(command, cmd)) -// var begin int -// var end int -// var err1 error -// var err2 error -// switch { -// case len(tokens) == 1: -// begin, err1 = strconv.Atoi(tokens[0]) -// end = begin -// if err1 != nil { -// return utils.Range{0, 0}, false -// } -// case len(tokens) >= 2: -// begin, err1 = strconv.Atoi(tokens[0]) -// end, err2 = strconv.Atoi(tokens[1]) -// if err1 != nil || err2 != nil { -// return utils.Range{0, 0}, false -// } -// default: -// return utils.Range{0, 0}, false -// } -// return utils.Range{begin, end}, true -// } - -// func contains(items []string, token string) bool { -// for _, elem := range items { -// if elem == token { -// return true -// } -// } -// return false -// } - -// func loadTestSetupCommand(c *Context, command *model.Command) bool { -// cmd := cmds["loadTestCommand"] + " setup" - -// if strings.Index(command.Command, cmd) == 0 && !command.Suggest { -// tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) -// doTeams := contains(tokens, "teams") -// doFuzz := contains(tokens, "fuzz") - -// numArgs := 0 -// if doTeams { -// numArgs++ -// } -// if doFuzz { -// numArgs++ -// } - -// var numTeams int -// var numChannels int -// var numUsers int -// var numPosts int - -// // Defaults -// numTeams = 10 -// numChannels = 10 -// numUsers = 10 -// numPosts = 10 - -// if doTeams { -// if (len(tokens) - numArgs) >= 4 { -// numTeams, _ = strconv.Atoi(tokens[numArgs+0]) -// numChannels, _ = strconv.Atoi(tokens[numArgs+1]) -// numUsers, _ = strconv.Atoi(tokens[numArgs+2]) -// numPosts, _ = strconv.Atoi(tokens[numArgs+3]) -// } -// } else { -// if (len(tokens) - numArgs) >= 3 { -// numChannels, _ = strconv.Atoi(tokens[numArgs+0]) -// numUsers, _ = strconv.Atoi(tokens[numArgs+1]) -// numPosts, _ = strconv.Atoi(tokens[numArgs+2]) -// } -// } -// client := model.NewClient(c.GetSiteURL()) - -// if doTeams { -// if err := CreateBasicUser(client); err != nil { -// l4g.Error("Failed to create testing environment") -// return true -// } -// client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) -// environment, err := CreateTestEnvironmentWithTeams( -// client, -// utils.Range{numTeams, numTeams}, -// utils.Range{numChannels, numChannels}, -// utils.Range{numUsers, numUsers}, -// utils.Range{numPosts, numPosts}, -// doFuzz) -// if err != true { -// l4g.Error("Failed to create testing environment") -// return true -// } else { -// l4g.Info("Testing environment created") -// for i := 0; i < len(environment.Teams); i++ { -// l4g.Info("Team Created: " + environment.Teams[i].Name) -// l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) -// } -// } -// } else { -// client.MockSession(c.Session.Token) -// CreateTestEnvironmentInTeam( -// client, -// c.Session.TeamId, -// utils.Range{numChannels, numChannels}, -// utils.Range{numUsers, numUsers}, -// utils.Range{numPosts, numPosts}, -// doFuzz) -// } -// return true -// } else if strings.Index(cmd, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{ -// Suggestion: cmd, -// Description: "Creates a testing environment in current team. [teams] [fuzz] "}) -// } - -// return false -// } - -// func loadTestUsersCommand(c *Context, command *model.Command) bool { -// cmd1 := cmds["loadTestCommand"] + " users" -// cmd2 := cmds["loadTestCommand"] + " users fuzz" - -// if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { -// cmd := cmd1 -// doFuzz := false -// if strings.Index(command.Command, cmd2) == 0 { -// doFuzz = true -// cmd = cmd2 -// } -// usersr, err := parseRange(command.Command, cmd) -// if err == false { -// usersr = utils.Range{10, 15} -// } -// client := model.NewClient(c.GetSiteURL()) -// userCreator := NewAutoUserCreator(client, c.Session.TeamId) -// userCreator.Fuzzy = doFuzz -// userCreator.CreateTestUsers(usersr) -// return true -// } else if strings.Index(cmd1, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random users to current team "}) -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team "}) -// } else if strings.Index(cmd2, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team "}) -// } - -// return false -// } - -// func loadTestChannelsCommand(c *Context, command *model.Command) bool { -// cmd1 := cmds["loadTestCommand"] + " channels" -// cmd2 := cmds["loadTestCommand"] + " channels fuzz" - -// if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { -// cmd := cmd1 -// doFuzz := false -// if strings.Index(command.Command, cmd2) == 0 { -// doFuzz = true -// cmd = cmd2 -// } -// channelsr, err := parseRange(command.Command, cmd) -// if err == false { -// channelsr = utils.Range{20, 30} -// } -// client := model.NewClient(c.GetSiteURL()) -// client.MockSession(c.Session.Token) -// channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) -// channelCreator.Fuzzy = doFuzz -// channelCreator.CreateTestChannels(channelsr) -// return true -// } else if strings.Index(cmd1, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random channels to current team "}) -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team "}) -// } else if strings.Index(cmd2, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team "}) -// } - -// return false -// } - -// func loadTestPostsCommand(c *Context, command *model.Command) bool { -// cmd1 := cmds["loadTestCommand"] + " posts" -// cmd2 := cmds["loadTestCommand"] + " posts fuzz" - -// if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { -// cmd := cmd1 -// doFuzz := false -// if strings.Index(command.Command, cmd2) == 0 { -// cmd = cmd2 -// doFuzz = true -// } - -// postsr, err := parseRange(command.Command, cmd) -// if err == false { -// postsr = utils.Range{20, 30} -// } - -// tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) -// rimages := utils.Range{0, 0} -// if len(tokens) >= 3 { -// if numImages, err := strconv.Atoi(tokens[2]); err == nil { -// rimages = utils.Range{numImages, numImages} -// } -// } - -// var usernames []string -// if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil { -// profileUsers := result.Data.(map[string]*model.User) -// usernames = make([]string, len(profileUsers)) -// i := 0 -// for _, userprof := range profileUsers { -// usernames[i] = userprof.Username -// i++ -// } -// } - -// client := model.NewClient(c.GetSiteURL()) -// client.MockSession(c.Session.Token) -// testPoster := NewAutoPostCreator(client, command.ChannelId) -// testPoster.Fuzzy = doFuzz -// testPoster.Users = usernames - -// numImages := utils.RandIntFromRange(rimages) -// numPosts := utils.RandIntFromRange(postsr) -// for i := 0; i < numPosts; i++ { -// testPoster.HasImage = (i < numImages) -// testPoster.CreateRandomPost() -// } -// return true -// } else if strings.Index(cmd1, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add some random posts to current channel "}) -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel "}) -// } else if strings.Index(cmd2, command.Command) == 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel "}) -// } - -// return false -// } - -// func loadTestUrlCommand(c *Context, command *model.Command) bool { -// cmd := cmds["loadTestCommand"] + " url" - -// if strings.Index(command.Command, cmd) == 0 && !command.Suggest { -// url := "" - -// parameters := strings.SplitN(command.Command, " ", 3) -// if len(parameters) != 3 { -// c.Err = model.NewAppError("loadTestUrlCommand", "Command must contain a url", "") -// return true -// } else { -// url = parameters[2] -// } - -// // provide a shortcut to easily access tests stored in doc/developer/tests -// if !strings.HasPrefix(url, "http") { -// url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url - -// if path.Ext(url) == "" { -// url += ".md" -// } -// } - -// var contents io.ReadCloser -// if r, err := http.Get(url); err != nil { -// c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", err.Error()) -// return false -// } else if r.StatusCode > 400 { -// c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", r.Status) -// return false -// } else { -// contents = r.Body -// } - -// bytes := make([]byte, 4000) - -// // break contents into 4000 byte posts -// for { -// length, err := contents.Read(bytes) -// if err != nil && err != io.EOF { -// c.Err = model.NewAppError("loadTestUrlCommand", "Encountered error reading file", err.Error()) -// return false -// } - -// if length == 0 { -// break -// } - -// post := &model.Post{} -// post.Message = string(bytes[:length]) -// post.ChannelId = command.ChannelId - -// if _, err := CreatePost(c, post, false); err != nil { -// l4g.Error("Unable to create post, err=%v", err) -// return false -// } -// } - -// command.Response = model.RESP_EXECUTED - -// return true -// } else if strings.Index(cmd, command.Command) == 0 && strings.Index(command.Command, "/loadtest posts") != 0 { -// command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Add a post containing the text from a given url to current channel "}) -// } - -// return false -// } diff --git a/api/command_loadtest.go b/api/command_loadtest.go new file mode 100644 index 000000000..eaf0b91b1 --- /dev/null +++ b/api/command_loadtest.go @@ -0,0 +1,357 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "io" + "net/http" + "path" + "strconv" + "strings" + + l4g "code.google.com/p/log4go" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +var usage = `Mattermost load testing commands to help configure the system + + COMMANDS: + + Setup - Creates a testing environment in current team. + /loadtest setup [teams] [fuzz] + + Example: + /loadtest setup teams fuzz 10 20 50 + + Users - Add a specified number of random users with fuzz text to current team. + /loadtest users [fuzz] + + Example: + /loadtest users fuzz 5 10 + + Channels - Add a specified number of random channels with fuzz text to current team. + /loadtest channels [fuzz] + + Example: + /loadtest channels fuzz 5 10 + + Posts - Add some random posts with fuzz text to current channel. + /loadtest posts [fuzz] + + Example: + /loadtest posts fuzz 5 10 3 + + Url - Add a post containing the text from a given url to current channel. + /loadtest url + + Example: + /loadtest http://www.example.com/sample_file.md + + +` + +type LoadTestProvider struct { +} + +func init() { + if !utils.Cfg.ServiceSettings.EnableTesting { + RegisterCommandProvider(&LoadTestProvider{}) + } +} + +func (me *LoadTestProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "loadtest", + AutoComplete: false, + AutoCompleteDesc: "Debug Load Testing", + AutoCompleteHint: "help", + DisplayName: "loadtest", + } +} + +func (me *LoadTestProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + + // This command is only available when EnableTesting is true + // if !utils.Cfg.ServiceSettings.EnableTesting { + // return &model.CommandResponse{} + // } + + if strings.HasPrefix(message, "setup") { + return me.SetupCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "users") { + return me.UsersCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "channels") { + return me.ChannelsCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "posts") { + return me.PostsCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "url") { + return me.UrlCommand(c, channelId, message) + } + + return me.HelpCommand(c, channelId, message) +} + +func (me *LoadTestProvider) HelpCommand(c *Context, channelId string, message string) *model.CommandResponse { + return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) SetupCommand(c *Context, channelId string, message string) *model.CommandResponse { + tokens := strings.Fields(strings.TrimPrefix(message, "setup")) + doTeams := contains(tokens, "teams") + doFuzz := contains(tokens, "fuzz") + + numArgs := 0 + if doTeams { + numArgs++ + } + if doFuzz { + numArgs++ + } + + var numTeams int + var numChannels int + var numUsers int + var numPosts int + + // Defaults + numTeams = 10 + numChannels = 10 + numUsers = 10 + numPosts = 10 + + if doTeams { + if (len(tokens) - numArgs) >= 4 { + numTeams, _ = strconv.Atoi(tokens[numArgs+0]) + numChannels, _ = strconv.Atoi(tokens[numArgs+1]) + numUsers, _ = strconv.Atoi(tokens[numArgs+2]) + numPosts, _ = strconv.Atoi(tokens[numArgs+3]) + } + } else { + if (len(tokens) - numArgs) >= 3 { + numChannels, _ = strconv.Atoi(tokens[numArgs+0]) + numUsers, _ = strconv.Atoi(tokens[numArgs+1]) + numPosts, _ = strconv.Atoi(tokens[numArgs+2]) + } + } + client := model.NewClient(c.GetSiteURL()) + + if doTeams { + if err := CreateBasicUser(client); err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) + environment, err := CreateTestEnvironmentWithTeams( + client, + utils.Range{numTeams, numTeams}, + utils.Range{numChannels, numChannels}, + utils.Range{numUsers, numUsers}, + utils.Range{numPosts, numPosts}, + doFuzz) + if err != true { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + l4g.Info("Testing environment created") + for i := 0; i < len(environment.Teams); i++ { + l4g.Info("Team Created: " + environment.Teams[i].Name) + l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) + } + } + } else { + client.MockSession(c.Session.Token) + CreateTestEnvironmentInTeam( + client, + c.Session.TeamId, + utils.Range{numChannels, numChannels}, + utils.Range{numUsers, numUsers}, + utils.Range{numPosts, numPosts}, + doFuzz) + } + + return &model.CommandResponse{Text: "Creating enviroment...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UsersCommand(c *Context, channelId string, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "users")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + usersr, err := parseRange(cmd, "") + if err == false { + usersr = utils.Range{2, 5} + } + + client := model.NewClient(c.GetSiteURL()) + userCreator := NewAutoUserCreator(client, c.Session.TeamId) + userCreator.Fuzzy = doFuzz + userCreator.CreateTestUsers(usersr) + + return &model.CommandResponse{Text: "Adding users...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) ChannelsCommand(c *Context, channelId string, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + channelsr, err := parseRange(cmd, "") + if err == false { + channelsr = utils.Range{2, 5} + } + client := model.NewClient(c.GetSiteURL()) + client.MockSession(c.Session.Token) + channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) + channelCreator.Fuzzy = doFuzz + channelCreator.CreateTestChannels(channelsr) + + return &model.CommandResponse{Text: "Adding channels...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + postsr, err := parseRange(cmd, "") + if err == false { + postsr = utils.Range{20, 30} + } + + tokens := strings.Fields(cmd) + rimages := utils.Range{0, 0} + if len(tokens) >= 3 { + if numImages, err := strconv.Atoi(tokens[2]); err == nil { + rimages = utils.Range{numImages, numImages} + } + } + + var usernames []string + if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil { + profileUsers := result.Data.(map[string]*model.User) + usernames = make([]string, len(profileUsers)) + i := 0 + for _, userprof := range profileUsers { + usernames[i] = userprof.Username + i++ + } + } + + client := model.NewClient(c.GetSiteURL()) + client.MockSession(c.Session.Token) + testPoster := NewAutoPostCreator(client, channelId) + testPoster.Fuzzy = doFuzz + testPoster.Users = usernames + + numImages := utils.RandIntFromRange(rimages) + numPosts := utils.RandIntFromRange(postsr) + for i := 0; i < numPosts; i++ { + testPoster.HasImage = (i < numImages) + testPoster.CreateRandomPost() + } + + return &model.CommandResponse{Text: "Adding posts...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UrlCommand(c *Context, channelId string, message string) *model.CommandResponse { + url := strings.TrimSpace(strings.TrimPrefix(message, "url")) + if len(url) == 0 { + return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + // provide a shortcut to easily access tests stored in doc/developer/tests + if !strings.HasPrefix(url, "http") { + url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url + + if path.Ext(url) == "" { + url += ".md" + } + } + + var contents io.ReadCloser + if r, err := http.Get(url); err != nil { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else if r.StatusCode > 400 { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + contents = r.Body + } + + bytes := make([]byte, 4000) + + // break contents into 4000 byte posts + for { + length, err := contents.Read(bytes) + if err != nil && err != io.EOF { + return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if length == 0 { + break + } + + post := &model.Post{} + post.Message = string(bytes[:length]) + post.ChannelId = channelId + + if _, err := CreatePost(c, post, false); err != nil { + return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + + return &model.CommandResponse{Text: "Loading url...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func parseRange(command string, cmd string) (utils.Range, bool) { + tokens := strings.Fields(strings.TrimPrefix(command, cmd)) + var begin int + var end int + var err1 error + var err2 error + switch { + case len(tokens) == 1: + begin, err1 = strconv.Atoi(tokens[0]) + end = begin + if err1 != nil { + return utils.Range{0, 0}, false + } + case len(tokens) >= 2: + begin, err1 = strconv.Atoi(tokens[0]) + end, err2 = strconv.Atoi(tokens[1]) + if err1 != nil || err2 != nil { + return utils.Range{0, 0}, false + } + default: + return utils.Range{0, 0}, false + } + return utils.Range{begin, end}, true +} + +func contains(items []string, token string) bool { + for _, elem := range items { + if elem == token { + return true + } + } + return false +} diff --git a/api/command_loadtest_test.go b/api/command_loadtest_test.go new file mode 100644 index 000000000..4af2a636a --- /dev/null +++ b/api/command_loadtest_test.go @@ -0,0 +1,220 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "strings" + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" +) + +func TestLoadTestHelpCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest help", false)).Data.(*model.CommandResponse) + if !strings.Contains(rs.Text, "Mattermost load testing commands to help") { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestSetupCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest setup fuzz 1 1 1", false)).Data.(*model.CommandResponse) + if rs.Text != "Creating enviroment..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestUsersCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest users fuzz 1 2", false)).Data.(*model.CommandResponse) + if rs.Text != "Adding users..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestChannelsCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest channels fuzz 1 2", false)).Data.(*model.CommandResponse) + if rs.Text != "Adding channels..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestPostsCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest posts fuzz 2 3 2", false)).Data.(*model.CommandResponse) + if rs.Text != "Adding posts..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestUrlCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + command := "/loadtest url " + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Command must contain a url" { + t.Fatal("/loadtest url with no url should've failed") + } + + command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Unable to get file" { + t.Fatal("/loadtest url with invalid url should've failed") + } + + command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading url..." { + t.Fatal("/loadtest url for README.md should've executed") + } + + command = "/loadtest url test-emoticons.md" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading url..." { + t.Fatal("/loadtest url for test-emoticons.md should've executed") + } + + command = "/loadtest url test-emoticons" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading url..." { + t.Fatal("/loadtest url for test-emoticons should've executed") + } + + posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList) + // note that this may make more than 3 posts if files are too long to fit in an individual post + if len(posts.Order) < 3 { + t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") + } + + time.Sleep(2 * time.Second) +} diff --git a/api/command_logout_test.go b/api/command_logout_test.go index 4c8f3e017..86979316b 100644 --- a/api/command_logout_test.go +++ b/api/command_logout_test.go @@ -10,7 +10,7 @@ import ( "github.com/mattermost/platform/store" ) -func TestLogoutCommand(t *testing.T) { +func TestLogoutTestCommand(t *testing.T) { Setup() team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} diff --git a/api/command_test.go b/api/command_test.go index 1583ac5bb..8243b0ddf 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -197,58 +197,3 @@ func TestDeleteCommand(t *testing.T) { *utils.Cfg.ServiceSettings.EnableCommands = false } - -// func TestLoadTestUrlCommand(t *testing.T) { -// Setup() - -// // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json -// enableTesting := utils.Cfg.ServiceSettings.EnableTesting -// defer func() { -// utils.Cfg.ServiceSettings.EnableTesting = enableTesting -// }() - -// utils.Cfg.ServiceSettings.EnableTesting = true - -// team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} -// team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - -// user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} -// user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) -// store.Must(Srv.Store.User().VerifyEmail(user.Id)) - -// Client.LoginByEmail(team.Name, user.Email, "pwd") - -// channel := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} -// channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) - -// command := "/loadtest url " -// if _, err := Client.Command(channel.Id, command, false); err == nil { -// t.Fatal("/loadtest url with no url should've failed") -// } - -// command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" -// if _, err := Client.Command(channel.Id, command, false); err == nil { -// t.Fatal("/loadtest url with invalid url should've failed") -// } - -// command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md" -// if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { -// t.Fatal("/loadtest url for README.md should've executed") -// } - -// command = "/loadtest url test-emoticons.md" -// if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { -// t.Fatal("/loadtest url for test-emoticons.md should've executed") -// } - -// command = "/loadtest url test-emoticons" -// if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { -// t.Fatal("/loadtest url for test-emoticons should've executed") -// } - -// posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList) -// // note that this may make more than 3 posts if files are too long to fit in an individual post -// if len(posts.Order) < 3 { -// t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") -// } -// } -- cgit v1.2.3-1-g7c22 From 2bf43e79582c2e8ca50caa29d7457716112b4796 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 09:39:09 -0600 Subject: Switching to enable integrations for testing --- api/command_test.go | 25 ++++++++++++++++-------- api/webhook_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/api/command_test.go b/api/command_test.go index 8243b0ddf..e5e954170 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -43,6 +43,11 @@ func TestListCommands(t *testing.T) { func TestCreateCommand(t *testing.T) { Setup() + + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands + defer func() { + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands + }() *utils.Cfg.ServiceSettings.EnableCommands = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} @@ -92,12 +97,14 @@ func TestCreateCommand(t *testing.T) { t.Fatal("bad team id wasn't overwritten") } } - - *utils.Cfg.ServiceSettings.EnableCommands = false } func TestListTeamCommands(t *testing.T) { Setup() + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands + defer func() { + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands + }() *utils.Cfg.ServiceSettings.EnableCommands = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} @@ -125,12 +132,14 @@ func TestListTeamCommands(t *testing.T) { t.Fatal("incorrect number of cmd") } } - - *utils.Cfg.ServiceSettings.EnableCommands = false } func TestRegenToken(t *testing.T) { Setup() + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands + defer func() { + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands + }() *utils.Cfg.ServiceSettings.EnableCommands = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} @@ -159,12 +168,14 @@ func TestRegenToken(t *testing.T) { t.Fatal("regen didn't work properly") } } - - *utils.Cfg.ServiceSettings.EnableCommands = false } func TestDeleteCommand(t *testing.T) { Setup() + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands + defer func() { + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands + }() *utils.Cfg.ServiceSettings.EnableCommands = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} @@ -194,6 +205,4 @@ func TestDeleteCommand(t *testing.T) { if len(cmds) != 0 { t.Fatal("delete didn't work properly") } - - *utils.Cfg.ServiceSettings.EnableCommands = false } diff --git a/api/webhook_test.go b/api/webhook_test.go index 345987863..89c06317f 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -13,6 +13,14 @@ import ( func TestCreateIncomingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -80,6 +88,14 @@ func TestCreateIncomingHook(t *testing.T) { func TestListIncomingHooks(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -122,6 +138,14 @@ func TestListIncomingHooks(t *testing.T) { func TestDeleteIncomingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -166,6 +190,14 @@ func TestDeleteIncomingHook(t *testing.T) { func TestCreateOutgoingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -233,6 +265,14 @@ func TestCreateOutgoingHook(t *testing.T) { func TestListOutgoingHooks(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -275,6 +315,14 @@ func TestListOutgoingHooks(t *testing.T) { func TestDeleteOutgoingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -319,6 +367,14 @@ func TestDeleteOutgoingHook(t *testing.T) { func TestRegenOutgoingHookToken(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) -- cgit v1.2.3-1-g7c22 From 2fd597043b45a75495164915ba6c41d5ab18c5ae Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 10:04:01 -0600 Subject: Chaning webhooks to be team wide --- api/webhook.go | 4 ++-- store/sql_webhook_store.go | 27 +++------------------------ store/sql_webhook_store_test.go | 34 +++------------------------------- store/store.go | 3 +-- 4 files changed, 9 insertions(+), 59 deletions(-) diff --git a/api/webhook.go b/api/webhook.go index de3d567ec..0f03d9e36 100644 --- a/api/webhook.go +++ b/api/webhook.go @@ -140,7 +140,7 @@ func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) { } } - if result := <-Srv.Store.Webhook().GetIncomingByUser(c.Session.UserId); result.Err != nil { + if result := <-Srv.Store.Webhook().GetIncomingByTeam(c.Session.TeamId); result.Err != nil { c.Err = result.Err return } else { @@ -228,7 +228,7 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) { } } - if result := <-Srv.Store.Webhook().GetOutgoingByCreator(c.Session.UserId); result.Err != nil { + if result := <-Srv.Store.Webhook().GetOutgoingByTeam(c.Session.TeamId); result.Err != nil { c.Err = result.Err return } else { diff --git a/store/sql_webhook_store.go b/store/sql_webhook_store.go index b7bf0615f..c65384ec1 100644 --- a/store/sql_webhook_store.go +++ b/store/sql_webhook_store.go @@ -134,7 +134,7 @@ func (s SqlWebhookStore) PermanentDeleteIncomingByUser(userId string) StoreChann return storeChannel } -func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel { +func (s SqlWebhookStore) GetIncomingByTeam(teamId string) StoreChannel { storeChannel := make(StoreChannel) go func() { @@ -142,8 +142,8 @@ func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel { var webhooks []*model.IncomingWebhook - if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE UserId = :UserId AND DeleteAt = 0", map[string]interface{}{"UserId": userId}); err != nil { - result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByUser", "We couldn't get the webhook", "userId="+userId+", err="+err.Error()) + if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil { + result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByUser", "We couldn't get the webhook", "teamId="+teamId+", err="+err.Error()) } result.Data = webhooks @@ -231,27 +231,6 @@ func (s SqlWebhookStore) GetOutgoing(id string) StoreChannel { return storeChannel } -func (s SqlWebhookStore) GetOutgoingByCreator(userId string) StoreChannel { - storeChannel := make(StoreChannel) - - go func() { - result := StoreResult{} - - var webhooks []*model.OutgoingWebhook - - if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM OutgoingWebhooks WHERE CreatorId = :UserId AND DeleteAt = 0", map[string]interface{}{"UserId": userId}); err != nil { - result.Err = model.NewAppError("SqlWebhookStore.GetOutgoingByCreator", "We couldn't get the webhooks", "userId="+userId+", err="+err.Error()) - } - - result.Data = webhooks - - storeChannel <- result - close(storeChannel) - }() - - return storeChannel -} - func (s SqlWebhookStore) GetOutgoingByChannel(channelId string) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/sql_webhook_store_test.go b/store/sql_webhook_store_test.go index 1a9d5be3b..5b43d0730 100644 --- a/store/sql_webhook_store_test.go +++ b/store/sql_webhook_store_test.go @@ -48,7 +48,7 @@ func TestWebhookStoreGetIncoming(t *testing.T) { } } -func TestWebhookStoreGetIncomingByUser(t *testing.T) { +func TestWebhookStoreGetIncomingByTeam(t *testing.T) { Setup() o1 := &model.IncomingWebhook{} @@ -58,7 +58,7 @@ func TestWebhookStoreGetIncomingByUser(t *testing.T) { o1 = (<-store.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook) - if r1 := <-store.Webhook().GetIncomingByUser(o1.UserId); r1.Err != nil { + if r1 := <-store.Webhook().GetIncomingByTeam(o1.TeamId); r1.Err != nil { t.Fatal(r1.Err) } else { if r1.Data.([]*model.IncomingWebhook)[0].CreateAt != o1.CreateAt { @@ -66,7 +66,7 @@ func TestWebhookStoreGetIncomingByUser(t *testing.T) { } } - if result := <-store.Webhook().GetIncomingByUser("123"); result.Err != nil { + if result := <-store.Webhook().GetIncomingByTeam("123"); result.Err != nil { t.Fatal(result.Err) } else { if len(result.Data.([]*model.IncomingWebhook)) != 0 { @@ -201,34 +201,6 @@ func TestWebhookStoreGetOutgoingByChannel(t *testing.T) { } } -func TestWebhookStoreGetOutgoingByCreator(t *testing.T) { - Setup() - - o1 := &model.OutgoingWebhook{} - o1.ChannelId = model.NewId() - o1.CreatorId = model.NewId() - o1.TeamId = model.NewId() - o1.CallbackURLs = []string{"http://nowhere.com/"} - - o1 = (<-store.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook) - - if r1 := <-store.Webhook().GetOutgoingByCreator(o1.CreatorId); r1.Err != nil { - t.Fatal(r1.Err) - } else { - if r1.Data.([]*model.OutgoingWebhook)[0].CreateAt != o1.CreateAt { - t.Fatal("invalid returned webhook") - } - } - - if result := <-store.Webhook().GetOutgoingByCreator("123"); result.Err != nil { - t.Fatal(result.Err) - } else { - if len(result.Data.([]*model.OutgoingWebhook)) != 0 { - t.Fatal("no webhooks should have returned") - } - } -} - func TestWebhookStoreGetOutgoingByTeam(t *testing.T) { Setup() diff --git a/store/store.go b/store/store.go index b19bba826..9d2d86b40 100644 --- a/store/store.go +++ b/store/store.go @@ -169,13 +169,12 @@ type SystemStore interface { type WebhookStore interface { SaveIncoming(webhook *model.IncomingWebhook) StoreChannel GetIncoming(id string) StoreChannel - GetIncomingByUser(userId string) StoreChannel + GetIncomingByTeam(teamId string) StoreChannel GetIncomingByChannel(channelId string) StoreChannel DeleteIncoming(webhookId string, time int64) StoreChannel PermanentDeleteIncomingByUser(userId string) StoreChannel SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel GetOutgoing(id string) StoreChannel - GetOutgoingByCreator(userId string) StoreChannel GetOutgoingByChannel(channelId string) StoreChannel GetOutgoingByTeam(teamId string) StoreChannel DeleteOutgoing(webhookId string, time int64) StoreChannel -- cgit v1.2.3-1-g7c22 From 0b986ed3147c885af6b2f33e1ff3eb6754e8f274 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 11 Jan 2016 12:31:13 -0600 Subject: Adding UI --- .../user_settings/manage_command_hooks.jsx | 260 +++++++++++++++++++++ .../user_settings/user_settings_integrations.jsx | 35 +++ 2 files changed, 295 insertions(+) create mode 100644 web/react/components/user_settings/manage_command_hooks.jsx diff --git a/web/react/components/user_settings/manage_command_hooks.jsx b/web/react/components/user_settings/manage_command_hooks.jsx new file mode 100644 index 000000000..375ccb33f --- /dev/null +++ b/web/react/components/user_settings/manage_command_hooks.jsx @@ -0,0 +1,260 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadingScreen from '../loading_screen.jsx'; + +import * as Client from '../../utils/client.jsx'; + +export default class ManageCommandHooks extends React.Component { + constructor() { + super(); + + this.getHooks = this.getHooks.bind(this); + this.addNewHook = this.addNewHook.bind(this); + this.updateTrigger = this.updateTrigger.bind(this); + this.updateURL = this.updateURL.bind(this); + + this.state = {hooks: [], channelId: '', trigger: '', URL: '', getHooksComplete: false}; + } + + componentDidMount() { + this.getHooks(); + } + + addNewHook(e) { + e.preventDefault(); + + if (this.state.trigger === '' || this.state.URL === '') { + return; + } + + const hook = {}; + if (this.state.trigger.length !== 0) { + hook.trigger = this.state.trigger.trim(); + } + hook.url = this.state.URL.trim(); + + Client.addCommand( + hook, + (data) => { + let hooks = Object.assign([], this.state.hooks); + if (!hooks) { + hooks = []; + } + hooks.push(data); + this.setState({hooks, addError: null, triggerWords: '', URL: ''}); + }, + (err) => { + this.setState({addError: err.message}); + } + ); + } + + removeHook(id) { + const data = {}; + data.id = id; + + Client.deleteCommand( + data, + () => { + const hooks = this.state.hooks; + let index = -1; + for (let i = 0; i < hooks.length; i++) { + if (hooks[i].id === id) { + index = i; + break; + } + } + + if (index !== -1) { + hooks.splice(index, 1); + } + + this.setState({hooks}); + }, + (err) => { + this.setState({editError: err.message}); + } + ); + } + + regenToken(id) { + const regenData = {}; + regenData.id = id; + + Client.regenCommandToken( + regenData, + (data) => { + const hooks = Object.assign([], this.state.hooks); + for (let i = 0; i < hooks.length; i++) { + if (hooks[i].id === id) { + hooks[i] = data; + break; + } + } + + this.setState({hooks, editError: null}); + }, + (err) => { + this.setState({editError: err.message}); + } + ); + } + + getHooks() { + Client.listCommands( + (data) => { + if (data) { + this.setState({hooks: data, getHooksComplete: true, editError: null}); + } + }, + (err) => { + this.setState({editError: err.message}); + } + ); + } + + updateTrigger(e) { + this.setState({trigger: e.target.value}); + } + + updateURL(e) { + this.setState({URL: e.target.value}); + } + + render() { + let addError; + if (this.state.addError) { + addError = ; + } + + let editError; + if (this.state.editError) { + addError = ; + } + + const hooks = []; + this.state.hooks.forEach((hook) => { + let triggerDiv; + if (hook.trigger && hook.trigger.length !== 0) { + triggerDiv = ( +
+ {'Trigger: '}{hook.trigger} +
+ ); + } + + hooks.push( +
+
+ {'URL: '}{hook.url} +
+ {triggerDiv} +
+ {'Token: '}{hook.token} +
+ +
+
+ ); + }); + + let displayHooks; + if (!this.state.getHooksComplete) { + displayHooks = ; + } else if (hooks.length > 0) { + displayHooks = hooks; + } else { + displayHooks =
{'None'}
; + } + + const existingHooks = ( +
+ +
+
+ {displayHooks} +
+
+ ); + + const disableButton = this.state.trigger === '' || this.state.URL === ''; + + return ( +
+ {'Create commands to send new message events to an external integration. Please see '} + + {'http://mattermost.org/commands'} + + {' to learn more.'} +
+
+
+
+ +
+ {'/'} + +
+
{'Word to trigger on'}
+
+
+ +
+