diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f79cb5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +build +coverage.txt +gofbot.lock \ No newline at end of file diff --git a/.gitignore b/.gitignore index b5e26ad..0f79cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out build -coverage.txt \ No newline at end of file +coverage.txt +gofbot.lock \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6e0676a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:latest AS build-env + +ENV APP_HOME /app +WORKDIR $APP_HOME + +ADD go.mod go.sum Makefile .git $APP_HOME/ +RUN make mod + +ADD . $APP_HOME +RUN make + + +# Runing Environment +FROM debian:9 + +ENV APP_HOME /app +WORKDIR $APP_HOME + +ADD deployments $APP_HOME/deployments +COPY --from=build-env /app/build/gofbot $APP_HOME/gofbot + +ENTRYPOINT ["./gofbot"] diff --git a/Makefile b/Makefile index 7858dc8..a834a41 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) MKFILE_DIR := $(dir $(MKFILE_PATH)) TARGET = ${MKFILE_DIR}build/gofbot -RELEASE?=$(shell git describe --tags) +RELEASE := $(shell git describe --tags --always | awk -F '-' '{print $$1}') GIT_REPO_INFO=$(shell git config --get remote.origin.url) ifndef COMMIT @@ -20,12 +20,12 @@ mod: build: @echo "-------------- building the program ---------------" - cd ${MKFILE_DIR} && go build -v -ldflags "-s -w \ - -X main.repo=${GIT_REPO_INFO} \ - -X main.commit=${COMMIT} \ - -X main.version=${RELEASE} \ - -X 'main.buildTime=${BUILD_TIME}' \ - " -o ${TARGET} ${MKFILE_DIR}server + cd ${MKFILE_DIR} && go build -v -ldflags "-s -w \ + -X main.repo=${GIT_REPO_INFO} \ + -X main.commit=${COMMIT} \ + -X main.version=${RELEASE} \ + -X 'main.buildTime=${BUILD_TIME}' \ + " -o ${TARGET} ${MKFILE_DIR}cmd/server.go @echo "-------------- version detail ---------------" @${TARGET} -v diff --git a/api/server.go b/api/server.go new file mode 100644 index 0000000..4daa75d --- /dev/null +++ b/api/server.go @@ -0,0 +1,103 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "time" + + "github.com/gin-gonic/gin" + + "github.com/saltbo/gofbot/robot" +) + +type Server struct { + http.Server + router *gin.Engine + robots map[string]*robot.Robot +} + +func NewServer() *Server { + router := gin.Default() + server := &Server{ + Server: http.Server{ + Addr: ":8080", + Handler: router, + }, + router: router, + robots: make(map[string]*robot.Robot), + } + + router.POST("/incoming/:alias", server.incomingHandler) + return server +} + +func (s *Server) SetupRobots(robots []*robot.Robot) { + for _, bot := range robots { + s.robots[bot.Alias] = bot + } +} + +func (s *Server) Run(addr ...string) error { + if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { + return err + } + + return nil +} + +func (s *Server) Shutdown() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return s.Server.Shutdown(ctx) +} + +func (s *Server) incomingHandler(ctx *gin.Context) { + alias := ctx.Param("alias") + bot, ok := s.robots[alias] + if !ok { + ctx.AbortWithError(http.StatusNotFound, fmt.Errorf("not found your robot")) + return + } + + body, err := ioutil.ReadAll(ctx.Request.Body) + if err != nil { + ctx.AbortWithError(http.StatusBadRequest, err) + return + } + + params := make(robot.Map) + if err := json.Unmarshal(body, ¶ms); err != nil { + ctx.AbortWithError(http.StatusBadRequest, err) + return + } + + msg, err := bot.MatchMessage(body) + if err != nil { + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + + msgStr := robot.BuildMessage(msg.Template, params) // 正则替换参数 + postBody := robot.BuildPostBody(bot.BodyTpl, msgStr) + if err := forwardToRobot(ctx, bot.WebHook, postBody); err != nil { + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } +} + +func forwardToRobot(ctx *gin.Context, url string, body io.Reader) error { + http.DefaultClient.Timeout = 3 * time.Second + resp, err := http.DefaultClient.Post(url, "application/json", body) + if err != nil { + return err + } + + defer resp.Body.Close() + rb, _ := ioutil.ReadAll(resp.Body) + ctx.Data(resp.StatusCode, resp.Header.Get("Content-Type"), rb) + return nil +} diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..7037a9b --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/signal" + "syscall" + "time" + + "github.com/urfave/cli" + + "github.com/saltbo/gofbot/api" + "github.com/saltbo/gofbot/pkg/process" + "github.com/saltbo/gofbot/robot" +) + +var ( + repo string + commit string + version string + buildTime string +) + +var pidCtrl = process.New("gofbot.lock") + +// define some flags +var flags = []cli.Flag{ + cli.StringFlag{ + Name: "robots", + Value: "deployments/robots", + }, +} + +// define some commands +var commands = []cli.Command{ + { + Name: "reload", + Usage: "reload for the config", + Action: reloadAction, + }, +} + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("repo: %s\ncommit: %s\nversion: %s\nbuildTime: %s\n", repo, commit, version, buildTime) + } + app := cli.NewApp() + app.Compiled = time.Now() + app.Copyright = "(c) 2019 yanbo.me" + app.Flags = flags + app.Commands = commands + app.Action = serverRun + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +func serverRun(c *cli.Context) { + robotsPath := c.String("robots") + robots, err := robot.LoadAndParse(robotsPath) + if err != nil { + log.Fatal(err) + } + + if err := pidCtrl.Save(); err != nil { + log.Fatal(err) + } + defer pidCtrl.Clean() + + server := api.NewServer() + server.SetupRobots(robots) + setupSignalHandler(server, robotsPath) + + // startup + if err := server.Run(":9613"); err != nil { + log.Fatal(err) + } + + log.Println("normal exited.") +} + +func reloadAction(c *cli.Context) { + p, err := pidCtrl.Find() + if err != nil { + log.Println(err) + return + } + + if err := p.Signal(syscall.SIGUSR1); err != nil { + log.Println(err) + return + } +} + +func setupSignalHandler(server *api.Server, robotsPath string) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) + go func() { + for { + switch <-ch { + case syscall.SIGINT, syscall.SIGTERM: + _ = pidCtrl.Clean() + signal.Stop(ch) + _ = server.Shutdown() + log.Print("system exit.") + return + case syscall.SIGUSR1: + // hot reload + robots, err := robot.LoadAndParse(robotsPath) + if err != nil { + log.Println(err) + return + } + server.SetupRobots(robots) + log.Printf("config reload.") + } + } + }() +} diff --git a/deployments/robots/wxwork4gitlab.yaml b/deployments/robots/wxwork4gitlab.yaml new file mode 100644 index 0000000..d36e016 --- /dev/null +++ b/deployments/robots/wxwork4gitlab.yaml @@ -0,0 +1,12 @@ +name: wxwork4gitlab +webhook: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=0d3ffb7e-3a7a-4f4c-8385-bea0db5e581a +bodytpl: '{"msgtype":"markdown","markdown":{"content":"$template"}}' +messages: + - regexp: push # regexp for match this message. + template: {{ $project.name }} 有新的PUSH,请相关同事注意。\n + >Commit: {{ $project.id }} \n + >Author: {{ $user_name }}({{ $user_email }}) \n + - regexp: merge + template: test merge message. + - regexp: issue + template: test push message. diff --git a/go.mod b/go.mod index 05f04b5..d77d2d5 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,8 @@ require ( github.com/gin-gonic/gin v1.4.0 github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.3.0 + github.com/urfave/cli v1.20.0 gopkg.in/yaml.v2 v2.2.2 ) + +go 1.13 diff --git a/go.sum b/go.sum index de3ffab..f9f9a94 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/pkg/process/process.go b/pkg/process/process.go new file mode 100644 index 0000000..7de2cb7 --- /dev/null +++ b/pkg/process/process.go @@ -0,0 +1,45 @@ +package process + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" +) + +type PidCtrl struct { + pidFile string +} + +func New(pidFile string) *PidCtrl { + return &PidCtrl{pidFile: pidFile} +} + +func (p *PidCtrl) Save() error { + _, err := os.Stat(p.pidFile) // os.Stat获取文件信息 + if (err != nil && os.IsExist(err)) || err == nil { + return fmt.Errorf("gofbot already running.") + } + + pid := strconv.Itoa(os.Getpid()) + return ioutil.WriteFile(p.pidFile, []byte(pid), 0600) +} + +func (p *PidCtrl) Clean() error { + return os.Remove(p.pidFile) +} + +func (p *PidCtrl) Find() (*os.Process, error) { + data, err := ioutil.ReadFile(p.pidFile) + if err != nil { + return nil, err + } + + pidStr := string(data) + pid, err := strconv.Atoi(pidStr) + if err != nil { + return nil, err + } + + return os.FindProcess(pid) +} diff --git a/robot/message.go b/robot/message.go new file mode 100644 index 0000000..444f309 --- /dev/null +++ b/robot/message.go @@ -0,0 +1,63 @@ +package robot + +import ( + "bytes" + "fmt" + "regexp" + "strconv" + "strings" +) + +var tplArgExp = regexp.MustCompile(`{{(\s*\$\S+\s*)}}`) + +type Map map[string]interface{} + +type Message struct { + Regexp string `yaml:"regexp"` + Template string `yaml:"template"` + + Exp *regexp.Regexp +} + +type variable struct { + full string + name string +} + +func BuildMessage(tpl string, params Map) string { + variables := make([]variable, 0) + for _, v := range tplArgExp.FindAllStringSubmatch(tpl, -1) { + variables = append(variables, variable{full: v[0], name: v[1]}) + } + + newMsg := tpl + for _, v := range variables { + newMsg = strings.Replace(newMsg, v.full, extractArgs(params, strings.TrimSpace(v.name)), -1) + } + + if strconv.CanBackquote(newMsg) { + return newMsg + } + + return strconv.Quote(newMsg) +} + +func BuildPostBody(bodyTpl string, message string) *bytes.Buffer { + return bytes.NewBufferString(strings.Replace(bodyTpl, "$template", message, -1)) +} + +func extractArgs(params Map, key string) string { + key = strings.Replace(key, "$", "", -1) + keys := strings.Split(key, ".") + for index, k := range keys { + if index == len(keys)-1 { + return fmt.Sprintf("%v", params[k]) + } + + if nextParams, ok := params[k].(map[string]interface{}); ok { + params = nextParams + } + } + + return "" +} diff --git a/robot/message_test.go b/robot/message_test.go new file mode 100644 index 0000000..d5ea3e3 --- /dev/null +++ b/robot/message_test.go @@ -0,0 +1,26 @@ +package robot + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildMessage(t *testing.T) { + params := Map{ + "name": "saltbo", + "age": "53", + "info": map[string]interface{}{ + "city": "Beijing", + }, + } + tpl := `name: {{$name}}, age: {{ $age }}, city: {{ $info.city }}` + msg := BuildMessage(tpl, params) + assert.Contains(t, msg, params["name"]) + assert.Contains(t, msg, params["age"]) + assert.Contains(t, msg, params["info"].(map[string]interface{})["city"]) + + bodyTpl := `{"msgtype": "markdown", "content": "$template"}` + body := BuildPostBody(bodyTpl, msg) + assert.Contains(t, body.String(), msg) +} diff --git a/server/robot.go b/robot/robot.go similarity index 82% rename from server/robot.go rename to robot/robot.go index 84ed9b4..6885dff 100644 --- a/server/robot.go +++ b/robot/robot.go @@ -1,4 +1,4 @@ -package main +package robot import ( "crypto/md5" @@ -13,6 +13,43 @@ import ( "strings" ) +func LoadAndParse(robotsPath string) ([]*Robot, error) { + robots := make([]*Robot, 0) + robotCreator := func(filepath string) error { + robot, err := newRobot(filepath) + if err != nil { + return err + } + + robots = append(robots, robot) + return nil + } + + if err := findRobots(robotsPath, robotCreator); err != nil { + return nil, err + } + + if len(robots) == 0 { + return nil, fmt.Errorf("not found any robot.") + } + + return robots, nil +} + +func findRobots(root string, creator func(filepath string) error) error { + return filepath.Walk(root, func(filepath string, info os.FileInfo, err error) error { + if err != nil { + return err + } else if info.IsDir() { + return nil + } else if path.Ext(filepath) != ".yaml" && path.Ext(filepath) != ".yml" { + return nil + } + + return creator(filepath) + }) +} + type Robot struct { Name string `yaml:"name"` Alias string `yaml:"uuid"` @@ -21,11 +58,14 @@ type Robot struct { Messages []*Message `yaml:"messages"` } -type Message struct { - Regexp string `yaml:"regexp"` - Template string `yaml:"template"` +func (r *Robot) MatchMessage(body []byte) (*Message, error) { + for _, msg := range r.Messages { + if msg.Exp.Match(body) { + return msg, nil + } + } - Exp *regexp.Regexp + return nil, fmt.Errorf("not found any message") } func newRobot(yamlPath string) (*Robot, error) { @@ -43,9 +83,9 @@ func newRobot(yamlPath string) (*Robot, error) { robot.Alias = hex.EncodeToString(nameHash[:]) errors := make([]string, 0) for _, msg := range robot.Messages { - exp, err2 := regexp.Compile(msg.Regexp) + exp, err := regexp.Compile(msg.Regexp) if err != nil { - errors = append(errors, err2.Error()) + errors = append(errors, err.Error()) continue } @@ -58,40 +98,3 @@ func newRobot(yamlPath string) (*Robot, error) { return robot, nil } - -func findRobots(root string, creator func(filepath string) error) error { - return filepath.Walk(root, func(filepath string, info os.FileInfo, err error) error { - if err != nil { - return err - } else if info.IsDir() { - return nil - } else if path.Ext(filepath) != ".yaml" && path.Ext(filepath) != ".yml" { - return nil - } - - return creator(filepath) - }) -} - -func loadRobots(robotsPath string) ([]*Robot, error) { - robots := make([]*Robot, 0) - robotCreator := func(filepath string) error { - robot, err := newRobot(filepath) - if err != nil { - return err - } - - robots = append(robots, robot) - return nil - } - - if err := findRobots(robotsPath, robotCreator); err != nil { - return nil, err - } - - if len(robots) == 0 { - return nil, fmt.Errorf("not found any robot.") - } - - return robots, nil -} diff --git a/server/robot_test.go b/robot/robot_test.go similarity index 60% rename from server/robot_test.go rename to robot/robot_test.go index b417ed1..b0a3c74 100644 --- a/server/robot_test.go +++ b/robot/robot_test.go @@ -1,11 +1,12 @@ -package main +package robot import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestNewRobot(t *testing.T) { - _, e := newRobot("../robots/wxwork4gitlab.yaml") + _, e := newRobot("../deployments/robots/wxwork4gitlab.yaml") assert.NoError(t, e) } diff --git a/robots/wxwork4gitlab.yaml b/robots/wxwork4gitlab.yaml deleted file mode 100644 index 38355a9..0000000 --- a/robots/wxwork4gitlab.yaml +++ /dev/null @@ -1,13 +0,0 @@ -name: wxwork4gitlab -webhook: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=0d3ffb7e-3a7a-4f4c-8385-bea0db5e581a -bodytpl: '{"msgtype":"markdown","markdown":{"content":"$template"}}' -messages: - - regexp: name # regexp for match this message. - template: 实时新增用户反馈132例,请相关同事注意。\n - >类型:用户反馈 \n - >普通用户反馈:{{ $test }} \n - >VIP用户反馈:{{ $test2 }}例 - - regexp: merge - template: test merge message. - - regexp: issue - template: test push message. \ No newline at end of file diff --git a/server/main.go b/server/main.go deleted file mode 100644 index 3b80eb1..0000000 --- a/server/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "os" -) - -var ( - showVer bool - repo string - commit string - version string - buildTime string -) - -func init() { - flag.BoolVar(&showVer, "v", false, "show build version") - flag.Parse() - - if showVer { - fmt.Printf("repo: %s\ncommit: %s\nversion: %s\nbuildTime: %s\n", repo, commit, version, buildTime) - os.Exit(0) - } -} - -func main() { - robots, err := loadRobots("robots") - if err != nil { - log.Fatal(err) - } - - if s, err := New(robots); err != nil { - log.Fatal(err) - } else { - log.Fatal(s.Run(":9613")) - } -} diff --git a/server/server.go b/server/server.go deleted file mode 100644 index 2cbef43..0000000 --- a/server/server.go +++ /dev/null @@ -1,115 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "regexp" - "strconv" - "strings" - "time" - - "github.com/gin-gonic/gin" -) - -var tplArgExp = regexp.MustCompile(`{{(\s*\$\S+\s*)}}`) - -type Map map[string]interface{} - -type Server struct { - *gin.Engine -} - -func New(robots []*Robot) (*Server, error) { - router := gin.Default() - for _, robot := range robots { - router.POST(fmt.Sprintf("/incoming/%s", robot.Alias), func(context *gin.Context) { - incomingHandler(context, robot) - }) - } - - return &Server{ - Engine: router, - }, nil -} - -func incomingHandler(ctx *gin.Context, robot *Robot) { - body, err := ioutil.ReadAll(ctx.Request.Body) - if err != nil { - ctx.AbortWithError(http.StatusBadRequest, err) - return - } - - params := make(Map) - if err := json.Unmarshal(body, ¶ms); err != nil { - ctx.AbortWithError(http.StatusInternalServerError, err) - return - } - - for _, msg := range robot.Messages { - if !msg.Exp.Match(body) { - continue - } - - // 正则替换参数 - message := buildMessage(msg.Template, params) - body := buildPostBody(robot.BodyTpl, message) - http.DefaultClient.Timeout = 3 * time.Second - if resp, err := http.DefaultClient.Post(robot.WebHook, "application/json", body); err != nil { - ctx.AbortWithError(http.StatusInternalServerError, err) - return - } else { - defer resp.Body.Close() - rb, _ := ioutil.ReadAll(resp.Body) - ctx.Data(resp.StatusCode, resp.Header.Get("Content-Type"), rb) - } - } -} - -type variable struct { - full string - name string -} - -func buildMessage(tpl string, params Map) string { - variables := make([]variable, 0) - for _, v := range tplArgExp.FindAllStringSubmatch(tpl, -1) { - variables = append(variables, variable{full: v[0], name: v[1]}) - } - - newMsg := tpl - for _, v := range variables { - newMsg = strings.Replace(newMsg, v.full, extractArgs(params, strings.TrimSpace(v.name)), -1) - } - - if strconv.CanBackquote(newMsg) { - return newMsg - } - - return strconv.Quote(newMsg) -} - -func buildPostBody(bodyTpl string, message string) *bytes.Buffer { - return bytes.NewBufferString(strings.Replace(bodyTpl, "$template", message, -1)) -} - -func extractArgs(params Map, key string) string { - key = strings.Replace(key, "$", "", -1) - keys := strings.Split(key, ".") - for index, k := range keys { - if index == len(keys)-1 { - if v, ok := params[k].(string); ok { - return v - } - return "" - } - - if nextParams, ok := params[k].(Map); ok { - params = nextParams - } - } - - return "" -} diff --git a/server/server_test.go b/server/server_test.go deleted file mode 100644 index d763f8a..0000000 --- a/server/server_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBuildMessage(t *testing.T) { - params := Map{ - "name": "saltbo", - "age": "53", - "info": Map{ - "city": "Beijing", - }, - } - tpl := `name: {{$name}}, age: {{ $age }}, city: {{ $info.city }}` - msg := buildMessage(tpl, params) - assert.Contains(t, msg, params["name"]) - assert.Contains(t, msg, params["age"]) - assert.Contains(t, msg, params["info"].(Map)["city"]) - - bodyTpl := `{"msgtype": "markdown", "content": "$template"}` - body := buildPostBody(bodyTpl, msg) - assert.Contains(t, body.String(), msg) -} - -func performRequest(r http.Handler, method, path string, body io.Reader) *httptest.ResponseRecorder { - req := httptest.NewRequest(method, path, body) - w := httptest.NewRecorder() - r.ServeHTTP(w, req) - return w -} - -type testServer struct { -} - -func (ts *testServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - b, e := ioutil.ReadAll(r.Body) - if e != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - if _, err := w.Write(b); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } -} - -func TestServer_Run(t *testing.T) { - ts := httptest.NewServer(&testServer{}) - defer ts.Close() - - robots, err := loadRobots("../robots") - assert.NoError(t, err) - - r, e := New(robots) - assert.NoError(t, e) - for _, robot := range robots { - // reset the hook to the test server URL - robot.WebHook = ts.URL - - // RUN - body := bytes.NewBufferString(`{"name": "saltbo", "sex": "man", "info":{"city": "beijing"}}`) - w := performRequest(r, "POST", fmt.Sprintf("/incoming/%s", robot.Alias), body) - - // TEST - assert.Equal(t, http.StatusOK, w.Code) - } -}