diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..32e98b97 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,62 @@ +run: + timeout: 5m + modules-download-mode: readonly + +linters-settings: + goconst: + min-len: 2 + min-occurrences: 2 + gofmt: + simplify: true + goimports: + local-prefixes: github.com/mattermost/mattermost-plugin-autolink + golint: + min-confidence: 0 + govet: + check-shadowing: true + enable-all: true + misspell: + locale: US + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - errcheck + - goconst + - gocritic + - gofmt + - goimports + - golint + - gosec + - gosimple + - govet + - ineffassign + - interfacer + - misspell + - nakedret + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unused + - varcheck + - whitespace + +issues: + exclude-rules: + - path: server/manifest.go + linters: + - deadcode + - unused + - varcheck + - path: server/configuration.go + linters: + - unused + - path: _test\.go + linters: + - bodyclose + - goconst + - scopelint # https://github.com/kyoh86/scopelint/issues/4 diff --git a/Makefile b/Makefile index 26f9c589..ca7f0bf9 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ GO ?= $(shell command -v go 2> /dev/null) NPM ?= $(shell command -v npm 2> /dev/null) CURL ?= $(shell command -v curl 2> /dev/null) MANIFEST_FILE ?= plugin.json +GOPATH ?= $(shell go env GOPATH) +GO_TEST_FLAGS ?= -race +GO_BUILD_FLAGS ?= MM_UTILITIES_DIR ?= ../mattermost-utilities export GO111MODULE=on @@ -14,6 +17,11 @@ include build/setup.mk BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz +# Include custom makefile, if present +ifneq ($(wildcard build/custom.mk),) + include build/custom.mk +endif + ## Checks the code style, tests, builds and bundles the plugin. all: check-style test dist @@ -22,55 +30,34 @@ all: check-style test dist apply: ./build/bin/manifest apply -## Runs govet and gofmt against all packages. +## Runs golangci-lint and eslint. .PHONY: check-style -check-style: webapp/.npminstall gofmt govet +check-style: webapp/.npminstall golangci-lint @echo Checking for style guide compliance ifneq ($(HAS_WEBAPP),) cd webapp && npm run lint endif -## Runs gofmt against all packages. -.PHONY: gofmt -gofmt: -ifneq ($(HAS_SERVER),) - @echo Running gofmt - @for package in $$(go list ./server/...); do \ - echo "Checking "$$package; \ - files=$$(go list -f '{{range .GoFiles}}{{$$.Dir}}/{{.}} {{end}}' $$package); \ - if [ "$$files" ]; then \ - gofmt_output=$$(gofmt -d -s $$files 2>&1); \ - if [ "$$gofmt_output" ]; then \ - echo "$$gofmt_output"; \ - echo "Gofmt failure"; \ - exit 1; \ - fi; \ - fi; \ - done - @echo Gofmt success -endif - -## Runs govet against all packages. -.PHONY: govet -govet: -ifneq ($(HAS_SERVER),) - @echo Running govet - @# Workaroung because you can't install binaries without adding them to go.mod - env GO111MODULE=off $(GO) get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow - $(GO) vet ./server/... - $(GO) vet -vettool=$(GOPATH)/bin/shadow ./server/... - @echo Govet success -endif +## Run golangci-lint on codebase. +.PHONY: golangci-lint +golangci-lint: + @if ! [ -x "$$(command -v golangci-lint)" ]; then \ + echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \ + exit 1; \ + fi; \ + + @echo Running golangci-lint + golangci-lint run ./... ## Builds the server, if it exists, including support for multiple architectures. .PHONY: server server: ifneq ($(HAS_SERVER),) mkdir -p server/dist; - cd server && env GOOS=linux GOARCH=amd64 $(GO) build -o dist/plugin-linux-amd64; - cd server && env GOOS=darwin GOARCH=amd64 $(GO) build -o dist/plugin-darwin-amd64; - cd server && env GOOS=windows GOARCH=amd64 $(GO) build -o dist/plugin-windows-amd64.exe; + cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-linux-amd64; + cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-darwin-amd64; + cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-windows-amd64.exe; endif ## Ensures NPM dependencies are installed without having to run this all the time. @@ -87,6 +74,14 @@ ifneq ($(HAS_WEBAPP),) cd webapp && $(NPM) run build; endif +## Builds the webapp in debug mode, if it exists. +.PHONY: webapp-debug +webapp-debug: webapp/.npminstall +ifneq ($(HAS_WEBAPP),) + cd webapp && \ + $(NPM) run debug; +endif + ## Generates a tar bundle of the plugin for install. .PHONY: bundle bundle: @@ -116,48 +111,45 @@ endif dist: apply server webapp bundle ## Installs the plugin to a (development) server. +## It uses the API if appropriate environment variables are defined, +## and otherwise falls back to trying to copy the plugin to a sibling mattermost-server directory. .PHONY: deploy deploy: dist -## It uses the API if appropriate environment variables are defined, -## or copying the files directly to a sibling mattermost-server directory. -ifneq ($(and $(MM_SERVICESETTINGS_SITEURL),$(MM_ADMIN_USERNAME),$(MM_ADMIN_PASSWORD),$(CURL)),) - @echo "Installing plugin via API" - $(eval TOKEN := $(shell curl -i -X POST $(MM_SERVICESETTINGS_SITEURL)/api/v4/users/login -d '{"login_id": "$(MM_ADMIN_USERNAME)", "password": "$(MM_ADMIN_PASSWORD)"}' | grep Token | cut -f2 -d' ' 2> /dev/null)) - @curl -s -H "Authorization: Bearer $(TOKEN)" -X POST $(MM_SERVICESETTINGS_SITEURL)/api/v4/plugins -F "plugin=@dist/$(BUNDLE_NAME)" -F "force=true" > /dev/null && \ - curl -s -H "Authorization: Bearer $(TOKEN)" -X POST $(MM_SERVICESETTINGS_SITEURL)/api/v4/plugins/$(PLUGIN_ID)/enable > /dev/null && \ - echo "OK." || echo "Sorry, something went wrong." -else ifneq ($(wildcard ../mattermost-server/.*),) - @echo "Installing plugin via filesystem. Server restart and manual plugin enabling required" - mkdir -p ../mattermost-server/plugins - tar -C ../mattermost-server/plugins -zxvf dist/$(BUNDLE_NAME) -else - @echo "No supported deployment method available. Install plugin manually." -endif + ./build/bin/deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME) + +.PHONY: debug-deploy +debug-deploy: debug-dist deploy + +.PHONY: debug-dist +debug-dist: apply server webapp-debug bundle ## Runs any lints and unit tests defined for the server and webapp, if they exist. .PHONY: test test: webapp/.npminstall ifneq ($(HAS_SERVER),) - $(GO) test -race -v ./server/... + $(GO) test -v $(GO_TEST_FLAGS) ./server/... endif ifneq ($(HAS_WEBAPP),) - cd webapp && $(NPM) run fix; + cd webapp && $(NPM) run fix && $(NPM) run test; endif ## Creates a coverage report for the server code. .PHONY: coverage -coverage: +coverage: webapp/.npminstall ifneq ($(HAS_SERVER),) - $(GO) test -race -coverprofile=server/coverage.txt ./server/... + $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./server/... $(GO) tool cover -html=server/coverage.txt endif ## Extract strings for translation from the source code. .PHONY: i18n-extract -i18n-extract: +i18n-extract: ifneq ($(HAS_WEBAPP),) - @[[ -d $(MM_UTILITIES_DIR) ]] || echo "You must clone github.com/mattermost/mattermost-utilities repo in .. to use this command" - @[[ -d $(MM_UTILITIES_DIR) ]] && cd $(MM_UTILITIES_DIR) && npm install && npm run babel && node mmjstool/build/index.js i18n extract-webapp --webapp-dir ../mattermost-plugin-demo/webapp +ifeq ($(HAS_MM_UTILITIES),) + @echo "You must clone github.com/mattermost/mattermost-utilities repo in .. to use this command" +else + cd $(MM_UTILITIES_DIR) && npm install && npm run babel && node mmjstool/build/index.js i18n extract-webapp --webapp-dir $(PWD)/webapp +endif endif ## Clean removes all build artifacts. @@ -165,15 +157,17 @@ endif clean: rm -fr dist/ ifneq ($(HAS_SERVER),) + rm -fr server/coverage.txt rm -fr server/dist endif ifneq ($(HAS_WEBAPP),) rm -fr webapp/.npminstall + rm -fr webapp/junit.xml rm -fr webapp/dist rm -fr webapp/node_modules endif rm -fr build/bin/ -# Help documentatin à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +# Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html help: @cat Makefile | grep -v '\.PHONY' | grep -v '\help:' | grep -B1 -E '^[a-zA-Z0-9_.-]+:.*' | sed -e "s/:.*//" | sed -e "s/^## //" | grep -v '\-\-' | sed '1!G;h;$$!d' | awk 'NR%2{printf "\033[36m%-30s\033[0m",$$0;next;}1' | sort diff --git a/build/custom.mk b/build/custom.mk new file mode 100644 index 00000000..efad4fbd --- /dev/null +++ b/build/custom.mk @@ -0,0 +1 @@ +# Include custome targets and environment variables here diff --git a/build/deploy/main.go b/build/deploy/main.go new file mode 100644 index 00000000..186bb492 --- /dev/null +++ b/build/deploy/main.go @@ -0,0 +1,122 @@ +// main handles deployment of the plugin to a development server using either the Client4 API +// or by copying the plugin bundle into a sibling mattermost-server/plugin directory. +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/mattermost/mattermost-server/v5/model" + "github.com/mholt/archiver/v3" + "github.com/pkg/errors" +) + +func main() { + err := deploy() + if err != nil { + fmt.Printf("Failed to deploy: %s\n", err.Error()) + fmt.Println() + fmt.Println("Usage:") + fmt.Println(" deploy ") + os.Exit(1) + } +} + +// deploy handles deployment of the plugin to a development server. +func deploy() error { + if len(os.Args) < 3 { + return errors.New("invalid number of arguments") + } + + pluginID := os.Args[1] + bundlePath := os.Args[2] + + siteURL := os.Getenv("MM_SERVICESETTINGS_SITEURL") + adminToken := os.Getenv("MM_ADMIN_TOKEN") + adminUsername := os.Getenv("MM_ADMIN_USERNAME") + adminPassword := os.Getenv("MM_ADMIN_PASSWORD") + copyTargetDirectory, _ := filepath.Abs("../mattermost-server") + + if siteURL != "" { + client := model.NewAPIv4Client(siteURL) + + if adminToken != "" { + log.Printf("Authenticating using token against %s.", siteURL) + client.SetToken(adminToken) + + return uploadPlugin(client, pluginID, bundlePath) + } + + if adminUsername != "" && adminPassword != "" { + client := model.NewAPIv4Client(siteURL) + log.Printf("Authenticating as %s against %s.", adminUsername, siteURL) + _, resp := client.Login(adminUsername, adminPassword) + if resp.Error != nil { + return errors.Wrapf(resp.Error, "failed to login as %s", adminUsername) + } + + return uploadPlugin(client, pluginID, bundlePath) + } + } + + _, err := os.Stat(copyTargetDirectory) + if os.IsNotExist(err) { + return errors.New("no supported deployment method available, please install plugin manually") + } else if err != nil { + return errors.Wrapf(err, "failed to stat %s", copyTargetDirectory) + } + + log.Printf("Installing plugin to mattermost-server found in %s.", copyTargetDirectory) + log.Print("Server restart required to load updated plugin.") + return copyPlugin(pluginID, copyTargetDirectory, bundlePath) +} + +// uploadPlugin attempts to upload and enable a plugin via the Client4 API. +// It will fail if plugin uploads are disabled. +func uploadPlugin(client *model.Client4, pluginID, bundlePath string) error { + pluginBundle, err := os.Open(bundlePath) + if err != nil { + return errors.Wrapf(err, "failed to open %s", bundlePath) + } + defer pluginBundle.Close() + + log.Print("Uploading plugin via API.") + _, resp := client.UploadPluginForced(pluginBundle) + if resp.Error != nil { + return errors.Wrap(resp.Error, "failed to upload plugin bundle") + } + + log.Print("Enabling plugin.") + _, resp = client.EnablePlugin(pluginID) + if resp.Error != nil { + return errors.Wrap(resp.Error, "Failed to enable plugin") + } + + return nil +} + +// copyPlugin attempts to install a plugin by copying it to a sibling ../mattermost-server/plugin +// directory. A server restart is required before the plugin will start. +func copyPlugin(pluginID, targetPath, bundlePath string) error { + targetPath = filepath.Join(targetPath, "plugins") + + err := os.MkdirAll(targetPath, 0777) + if err != nil { + return errors.Wrapf(err, "failed to create %s", targetPath) + } + + existingPluginPath := filepath.Join(targetPath, pluginID) + err = os.RemoveAll(existingPluginPath) + if err != nil { + return errors.Wrapf(err, "failed to remove existing existing plugin directory %s", existingPluginPath) + } + + err = archiver.Unarchive(bundlePath, targetPath) + if err != nil { + return errors.Wrapf(err, "failed to unarchive %s into %s", bundlePath, targetPath) + } + + return nil +} diff --git a/build/manifest/main.go b/build/manifest/main.go index 4bf4e19a..89a397c1 100644 --- a/build/manifest/main.go +++ b/build/manifest/main.go @@ -10,18 +10,22 @@ import ( "github.com/pkg/errors" ) -const pluginIdGoFileTemplate = `package main +const pluginIDGoFileTemplate = `// This file is automatically generated. Do not modify it manually. + +package main var manifest = struct { - Id string + ID string Version string }{ - Id: "%s", + ID: "%s", Version: "%s", } ` -const pluginIdJsFileTemplate = `export const id = '%s'; +const pluginIDJSFileTemplate = `// This file is automatically generated. Do not modify it manually. + +export const id = '%s'; export const version = '%s'; ` @@ -38,7 +42,7 @@ func main() { cmd := os.Args[1] switch cmd { case "id": - dumpPluginId(manifest) + dumpPluginID(manifest) case "version": dumpPluginVersion(manifest) @@ -87,7 +91,7 @@ func findManifest() (*model.Manifest, error) { } // dumpPluginId writes the plugin id from the given manifest to standard out -func dumpPluginId(manifest *model.Manifest) { +func dumpPluginID(manifest *model.Manifest) { fmt.Printf("%s", manifest.Id) } @@ -101,7 +105,7 @@ func applyManifest(manifest *model.Manifest) error { if manifest.HasServer() { if err := ioutil.WriteFile( "server/manifest.go", - []byte(fmt.Sprintf(pluginIdGoFileTemplate, manifest.Id, manifest.Version)), + []byte(fmt.Sprintf(pluginIDGoFileTemplate, manifest.Id, manifest.Version)), 0644, ); err != nil { return errors.Wrap(err, "failed to write server/manifest.go") @@ -111,7 +115,7 @@ func applyManifest(manifest *model.Manifest) error { if manifest.HasWebapp() { if err := ioutil.WriteFile( "webapp/src/manifest.js", - []byte(fmt.Sprintf(pluginIdJsFileTemplate, manifest.Id, manifest.Version)), + []byte(fmt.Sprintf(pluginIDJSFileTemplate, manifest.Id, manifest.Version)), 0644, ); err != nil { return errors.Wrap(err, "failed to open webapp/src/manifest.js") diff --git a/build/setup.mk b/build/setup.mk index 13ece2b8..bc1fdc35 100644 --- a/build/setup.mk +++ b/build/setup.mk @@ -7,6 +7,9 @@ endif # Ensure that the build tools are compiled. Go's caching makes this quick. $(shell cd build/manifest && $(GO) build -o ../bin/manifest) +# Ensure that the deployment tools are compiled. Go's caching makes this quick. +$(shell cd build/deploy && $(GO) build -o ../bin/deploy) + # Extract the plugin id from the manifest. PLUGIN_ID ?= $(shell build/bin/manifest id) ifeq ($(PLUGIN_ID),) @@ -28,6 +31,12 @@ HAS_WEBAPP ?= $(shell build/bin/manifest has_webapp) # Determine if a /public folder is in use HAS_PUBLIC ?= $(wildcard public/.) +# Determine if the mattermost-utilities repo is present +HAS_MM_UTILITIES ?= $(wildcard $(MM_UTILITIES_DIR)/.) + +# Store the current path for later use +PWD ?= $(shell pwd) + # Ensure that npm (and thus node) is installed. ifneq ($(HAS_WEBAPP),) ifeq ($(NPM),) diff --git a/go.mod b/go.mod index 232ced76..dc610691 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.12 require ( github.com/gorilla/mux v1.7.3 github.com/mattermost/mattermost-server/v5 v5.18.0 + github.com/mholt/archiver/v3 v3.3.0 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.4.0 ) diff --git a/go.sum b/go.sum index acc31854..4643b063 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c= +github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -59,6 +61,9 @@ github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjC github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= @@ -87,6 +92,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 h1:KRMr9A3qfbVM7iV/WcLY/rL5LICqwMHLhwRXKu99fXw= +github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -100,10 +107,13 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -175,6 +185,12 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY= +github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM= +github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -215,6 +231,8 @@ github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= +github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.19/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/minio/minio-go/v6 v6.0.40/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= @@ -237,6 +255,8 @@ github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78Rwc github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= +github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= +github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -258,6 +278,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -335,6 +356,10 @@ github.com/throttled/throttled v2.2.4+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLs github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= diff --git a/server/api/api.go b/server/api/api.go index d3d6195c..aa3051fe 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) @@ -55,25 +56,12 @@ func (h *Handler) handleError(w http.ResponseWriter, err error) { _, _ = w.Write(b) } -func (h *Handler) handleErrorWithCode(w http.ResponseWriter, code int, errTitle string, err error) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - b, _ := json.Marshal(struct { - Error string `json:"error"` - Details string `json:"details"` - }{ - Error: errTitle, - Details: err.Error(), - }) - _, _ = w.Write(b) -} - func (h *Handler) adminOrPluginRequired(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var err error authorized := false - pluginId := r.Header.Get("Mattermost-Plugin-ID") - if pluginId != "" { + pluginID := r.Header.Get("Mattermost-Plugin-ID") + if pluginID != "" { // All other plugins are allowed authorized = true } @@ -102,7 +90,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) setLink(w http.ResponseWriter, r *http.Request) { var newLink autolink.Autolink if err := json.NewDecoder(r.Body).Decode(&newLink); err != nil { - h.handleError(w, fmt.Errorf("Unable to decode body: %w", err)) + h.handleError(w, fmt.Errorf("unable to decode body: %w", err)) return } @@ -126,7 +114,7 @@ func (h *Handler) setLink(w http.ResponseWriter, r *http.Request) { status := http.StatusNotModified if changed { if err := h.store.SaveLinks(links); err != nil { - h.handleError(w, fmt.Errorf("Unable to save link: %w", err)) + h.handleError(w, fmt.Errorf("unable to save link: %w", err)) return } status = http.StatusOK diff --git a/server/api/api_test.go b/server/api/api_test.go index 1c299f35..faca9f9f 100644 --- a/server/api/api_test.go +++ b/server/api/api_test.go @@ -7,8 +7,9 @@ import ( "net/http/httptest" "testing" - "github.com/mattermost/mattermost-plugin-autolink/server/autolink" "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) type authorizeAll struct{} @@ -50,11 +51,9 @@ func TestSetLink(t *testing.T) { }, expectStatus: http.StatusOK, expectSaveCalled: true, - expectSaved: []autolink.Autolink{ - autolink.Autolink{ - Name: "test", - }, - }, + expectSaved: []autolink.Autolink{{ + Name: "test", + }}, }, { name: "add new link", @@ -63,27 +62,22 @@ func TestSetLink(t *testing.T) { Pattern: ".*1", Template: "test1", }, - prevLinks: []autolink.Autolink{ - autolink.Autolink{ - Name: "test2", - Pattern: ".*2", - Template: "test2", - }, - }, + prevLinks: []autolink.Autolink{{ + Name: "test2", + Pattern: ".*2", + Template: "test2", + }}, expectStatus: http.StatusOK, expectSaveCalled: true, - expectSaved: []autolink.Autolink{ - autolink.Autolink{ - Name: "test2", - Pattern: ".*2", - Template: "test2", - }, - autolink.Autolink{ - Name: "test1", - Pattern: ".*1", - Template: "test1", - }, - }, + expectSaved: []autolink.Autolink{{ + Name: "test2", + Pattern: ".*2", + Template: "test2", + }, { + Name: "test1", + Pattern: ".*1", + Template: "test1", + }}, }, { name: "replace link", link: autolink.Autolink{ @@ -91,42 +85,34 @@ func TestSetLink(t *testing.T) { Pattern: ".*2", Template: "new template", }, - prevLinks: []autolink.Autolink{ - autolink.Autolink{ - Name: "test1", - Pattern: ".*1", - Template: "test1", - }, - autolink.Autolink{ - Name: "test2", - Pattern: ".*2", - Template: "test2", - }, - autolink.Autolink{ - Name: "test3", - Pattern: ".*3", - Template: "test3", - }, - }, + prevLinks: []autolink.Autolink{{ + Name: "test1", + Pattern: ".*1", + Template: "test1", + }, { + Name: "test2", + Pattern: ".*2", + Template: "test2", + }, { + Name: "test3", + Pattern: ".*3", + Template: "test3", + }}, expectStatus: http.StatusOK, expectSaveCalled: true, - expectSaved: []autolink.Autolink{ - autolink.Autolink{ - Name: "test1", - Pattern: ".*1", - Template: "test1", - }, - autolink.Autolink{ - Name: "test2", - Pattern: ".*2", - Template: "new template", - }, - autolink.Autolink{ - Name: "test3", - Pattern: ".*3", - Template: "test3", - }, - }, + expectSaved: []autolink.Autolink{{ + Name: "test1", + Pattern: ".*1", + Template: "test1", + }, { + Name: "test2", + Pattern: ".*2", + Template: "new template", + }, { + Name: "test3", + Pattern: ".*3", + Template: "test3", + }}, }, { name: "no change", @@ -135,18 +121,15 @@ func TestSetLink(t *testing.T) { Pattern: ".*2", Template: "test2", }, - prevLinks: []autolink.Autolink{ - autolink.Autolink{ - Name: "test1", - Pattern: ".*1", - Template: "test1", - }, - autolink.Autolink{ - Name: "test2", - Pattern: ".*2", - Template: "test2", - }, - }, + prevLinks: []autolink.Autolink{{ + Name: "test1", + Pattern: ".*1", + Template: "test1", + }, { + Name: "test2", + Pattern: ".*2", + Template: "test2", + }}, expectStatus: http.StatusNotModified, expectSaveCalled: false, }, diff --git a/server/autolink/autolink.go b/server/autolink/autolink.go index 1f5d1c84..c93c3d5a 100644 --- a/server/autolink/autolink.go +++ b/server/autolink/autolink.go @@ -70,11 +70,11 @@ func (l *Autolink) Compile() error { } if !l.DisableNonWordSuffix { if l.WordMatch { - pattern = pattern + `\b` + pattern += `\b` canReplaceAll = true } else { - pattern = pattern + `(?P$|[\s\.\!\?\,\)])` - template = template + `${MattermostNonWordSuffix}` + pattern += `(?P$|[\s\.\!\?\,\)])` + template += `${MattermostNonWordSuffix}` } } @@ -126,11 +126,11 @@ func (l Autolink) ToMarkdown(i int) string { if l.Disabled { text += fmt.Sprintf("~~%s~~", l.Name) } else { - text += fmt.Sprintf("%s", l.Name) + text += l.Name } } if l.Disabled { - text += fmt.Sprintf(" **Disabled**") + text += " **Disabled**" } text += "\n" diff --git a/server/autolink/autolink_test.go b/server/autolink/autolink_test.go index e4b665b7..baa8d172 100644 --- a/server/autolink/autolink_test.go +++ b/server/autolink/autolink_test.go @@ -5,13 +5,14 @@ import ( "regexp" "testing" - "github.com/mattermost/mattermost-plugin-autolink/server/autolink" - "github.com/mattermost/mattermost-plugin-autolink/server/autolinkplugin" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin/plugintest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" + "github.com/mattermost/mattermost-plugin-autolink/server/autolinkplugin" ) func setupTestPlugin(t *testing.T, l autolink.Autolink) *autolinkplugin.Plugin { @@ -197,9 +198,11 @@ func TestCreditCard(t *testing.T) { for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - _ = tt.Link.Compile() + err := tt.Link.Compile() actual := tt.Link.Replace(tt.inputMessage) + assert.Equal(t, tt.expectedMessage, actual) + assert.NoError(t, err) }) } } @@ -355,7 +358,6 @@ func TestLink(t *testing.T) { "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345)", }, } { - t.Run(tc.Name, func(t *testing.T) { p := setupTestPlugin(t, tc.Link) post, _ := p.MessageWillBePosted(nil, &model.Post{ @@ -371,7 +373,6 @@ func TestLegacyWordBoundaries(t *testing.T) { const pattern = "(KEY)(-)(?P\\d+)" const template = "[KEY-$ID](someurl/KEY-$ID)" const ref = "KEY-12345" - const ID = "12345" const markdown = "[KEY-12345](someurl/KEY-12345)" var defaultLink = autolink.Autolink{ @@ -434,7 +435,6 @@ func TestLegacyWordBoundaries(t *testing.T) { {Sep: "lsbracket", Name: "bracket neither prefix suffix", Prefix: "[", Link: linkNoPrefixNoSuffix}, {Sep: "rand", Name: "random separators", Prefix: "%() ", Suffix: "?! $%^&"}, } { - orig := fmt.Sprintf("word1%s%s%sword2", tc.Prefix, ref, tc.Suffix) expected := fmt.Sprintf("word1%s%s%sword2", tc.Prefix, markdown, tc.Suffix) @@ -472,7 +472,6 @@ func TestWordMatch(t *testing.T) { const pattern = "(KEY)(-)(?P\\d+)" const template = "[KEY-$ID](someurl/KEY-$ID)" const ref = "KEY-12345" - const ID = "12345" const markdown = "[KEY-12345](someurl/KEY-12345)" var defaultLink = autolink.Autolink{ @@ -530,7 +529,6 @@ func TestWordMatch(t *testing.T) { {Sep: "rsbracket", Name: "bracket", Suffix: "]", Link: linkNoPrefix}, {Sep: "rand", Name: "random separators", Prefix: "% (", Suffix: "-- $%^&"}, } { - orig := fmt.Sprintf("word1%s%s%sword2", tc.Prefix, ref, tc.Suffix) expected := fmt.Sprintf("word1%s%s%sword2", tc.Prefix, markdown, tc.Suffix) @@ -584,7 +582,6 @@ func TestEquals(t *testing.T) { expectEqual: true, }, } { - t.Run(tc.l1.Name+"-"+tc.l2.Name, func(t *testing.T) { eq := tc.l1.Equals(tc.l2) assert.Equal(t, tc.expectEqual, eq) diff --git a/server/autolinkclient/client.go b/server/autolinkclient/client.go index f6bc85d3..69587a5b 100644 --- a/server/autolinkclient/client.go +++ b/server/autolinkclient/client.go @@ -10,7 +10,7 @@ import ( "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) -const autolinkPluginId = "mattermost-autolink" +const autolinkPluginID = "mattermost-autolink" type PluginAPI interface { PluginHTTP(*http.Request) *http.Response @@ -27,7 +27,7 @@ type pluginAPIRoundTripper struct { func (p *pluginAPIRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { resp := p.api.PluginHTTP(req) if resp == nil { - return nil, fmt.Errorf("Failed to make interplugin request") + return nil, fmt.Errorf("failed to make interplugin request") } return resp, nil } @@ -45,7 +45,7 @@ func (c *Client) Add(links ...autolink.Autolink) error { return err } - req, err := http.NewRequest("POST", "/"+autolinkPluginId+"/api/v1/link", bytes.NewReader(linkBytes)) + req, err := http.NewRequest("POST", "/"+autolinkPluginID+"/api/v1/link", bytes.NewReader(linkBytes)) if err != nil { return err } @@ -54,10 +54,11 @@ func (c *Client) Add(links ...autolink.Autolink) error { if err != nil { return err } - resp.Body.Close() - if resp == nil || resp.StatusCode != http.StatusOK { + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { respBody, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("Unable to install autolink. Error: %v, %v", resp.StatusCode, string(respBody)) + return fmt.Errorf("unable to install autolink. Error: %v, %v", resp.StatusCode, string(respBody)) } } diff --git a/server/autolinkclient/client_test.go b/server/autolinkclient/client_test.go index bcd5862a..9c3fddea 100644 --- a/server/autolinkclient/client_test.go +++ b/server/autolinkclient/client_test.go @@ -4,10 +4,11 @@ import ( "net/http" "testing" - "github.com/mattermost/mattermost-plugin-autolink/server/autolink" "github.com/mattermost/mattermost-server/v5/plugin/plugintest" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) func TestRoundTripper(t *testing.T) { diff --git a/server/autolinkplugin/command.go b/server/autolinkplugin/command.go index 80d91488..6d0e56fb 100644 --- a/server/autolinkplugin/command.go +++ b/server/autolinkplugin/command.go @@ -5,10 +5,11 @@ import ( "strconv" "strings" - "github.com/mattermost/mattermost-plugin-autolink/server/autolink" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin" "github.com/pkg/errors" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) const helpText = "###### Mattermost Autolink Plugin Administration\n" + @@ -69,7 +70,7 @@ func (ch CommandHandler) Handle(p *Plugin, c *plugin.Context, header *model.Comm func (p *Plugin) ExecuteCommand(c *plugin.Context, commandArgs *model.CommandArgs) (*model.CommandResponse, *model.AppError) { isAdmin, err := p.IsAuthorizedAdmin(commandArgs.UserId) if err != nil { - return responsef("error occured while authorizing the command: %v", err), nil + return responsef("error occurred while authorizing the command: %v", err), nil } if !isAdmin { return responsef("`/autolink` commands can only be executed by a system administrator or `autolink` plugin admins."), nil @@ -114,7 +115,7 @@ func executeDelete(p *Plugin, c *plugin.Context, header *model.CommandArgs, args removed := oldLinks[n] newLinks := oldLinks[:n] - if int(n+1) < len(oldLinks) { + if n+1 < len(oldLinks) { newLinks = append(newLinks, oldLinks[n+1:]...) } @@ -294,7 +295,7 @@ func parseLinkRef(p *Plugin, requireUnique bool, args ...string) ([]autolink.Aut links := p.getConfig().Sorted().Links if len(args) == 0 { if requireUnique { - return nil, nil, errors.New("Unreachable") + return nil, nil, errors.New("unreachable") } return links, nil, nil diff --git a/server/autolinkplugin/config.go b/server/autolinkplugin/config.go index ae696028..23ca7d16 100644 --- a/server/autolinkplugin/config.go +++ b/server/autolinkplugin/config.go @@ -5,8 +5,9 @@ import ( "sort" "strings" - "github.com/mattermost/mattermost-plugin-autolink/server/autolink" "github.com/mattermost/mattermost-server/v5/model" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) // Config from config.json @@ -83,7 +84,7 @@ func (p *Plugin) SaveLinks(links []autolink.Autolink) error { }) appErr := p.API.SavePluginConfig(p.getConfig().ToConfig()) if appErr != nil { - return fmt.Errorf("Unable to save links: %w", appErr) + return fmt.Errorf("unable to save links: %w", appErr) } return nil @@ -133,13 +134,13 @@ func (conf *Config) parsePluginAdminList(p *Plugin) { userIDs := strings.Split(conf.PluginAdmins, ",") for _, v := range userIDs { - userId := strings.TrimSpace(v) + userID := strings.TrimSpace(v) // Let's verify that the given user really exists - _, appErr := p.API.GetUser(userId) + _, appErr := p.API.GetUser(userID) if appErr != nil { - p.API.LogError(fmt.Sprintf("error occured while verifying userId %s: %v", v, appErr)) + p.API.LogError(fmt.Sprintf("error occurred while verifying userID %s: %v", v, appErr)) } else { - conf.AdminUserIds[userId] = struct{}{} + conf.AdminUserIds[userID] = struct{}{} } } } diff --git a/server/autolinkplugin/config_test.go b/server/autolinkplugin/config_test.go index fdc12f23..45fbe1ed 100644 --- a/server/autolinkplugin/config_test.go +++ b/server/autolinkplugin/config_test.go @@ -2,21 +2,23 @@ package autolinkplugin import ( "errors" - "github.com/mattermost/mattermost-plugin-autolink/server/autolink" + "testing" + "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin/plugintest" "github.com/mattermost/mattermost-server/v5/plugin/plugintest/mock" "github.com/stretchr/testify/assert" - "testing" + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) func TestOnConfigurationChange(t *testing.T) { - t.Run("Invalid Configuration", func(t *testing.T) { api := &plugintest.API{} api.On("LoadPluginConfiguration", mock.AnythingOfType("*autolinkplugin.Config")).Return(func(dest interface{}) error { - return errors.New("LoadPluginConfiguration Error") + return errors.New("loadPluginConfiguration Error") }) p := Plugin{} @@ -28,13 +30,11 @@ func TestOnConfigurationChange(t *testing.T) { t.Run("Invalid Autolink", func(t *testing.T) { conf := Config{ - Links: []autolink.Autolink{ - autolink.Autolink{ - Name: "existing", - Pattern: ")", - Template: "otherthing", - }, - }, + Links: []autolink.Autolink{{ + Name: "existing", + Pattern: ")", + Template: "otherthing", + }}, } api := &plugintest.API{} @@ -56,7 +56,8 @@ func TestOnConfigurationChange(t *testing.T) { p := New() p.SetAPI(api) - p.OnConfigurationChange() + err := p.OnConfigurationChange() + require.NoError(t, err) api.AssertNumberOfCalls(t, "LogError", 1) }) diff --git a/server/autolinkplugin/plugin.go b/server/autolinkplugin/plugin.go index d2e0e969..e2c51b44 100644 --- a/server/autolinkplugin/plugin.go +++ b/server/autolinkplugin/plugin.go @@ -6,12 +6,12 @@ import ( "strings" "sync" - "github.com/mattermost/mattermost-plugin-autolink/server/api" - "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin" "github.com/mattermost/mattermost-server/v5/utils/markdown" + + "github.com/mattermost/mattermost-plugin-autolink/server/api" ) // Plugin the main struct for everything @@ -37,22 +37,22 @@ func (p *Plugin) OnActivate() error { return nil } -func (p *Plugin) IsAuthorizedAdmin(userId string) (bool, error) { - user, err := p.API.GetUser(userId) +func (p *Plugin) IsAuthorizedAdmin(userID string) (bool, error) { + user, err := p.API.GetUser(userID) if err != nil { return false, fmt.Errorf( - "failed to obtain information about user `%s`: %w", userId, err) + "failed to obtain information about user `%s`: %w", userID, err) } if strings.Contains(user.Roles, "system_admin") { p.API.LogInfo( - fmt.Sprintf("UserId `%s` is authorized basing on the sysadmin role membership", userId)) + fmt.Sprintf("UserID `%s` is authorized basing on the sysadmin role membership", userID)) return true, nil } conf := p.getConfig() - if _, ok := conf.AdminUserIds[userId]; ok { + if _, ok := conf.AdminUserIds[userID]; ok { p.API.LogInfo( - fmt.Sprintf("UserId `%s` is authorized basing on the list of plugin admins list", userId)) + fmt.Sprintf("UserID `%s` is authorized basing on the list of plugin admins list", userID)) return true, nil } @@ -62,24 +62,25 @@ func (p *Plugin) IsAuthorizedAdmin(userId string) (bool, error) { func contains(team string, channel string, list []string) bool { for _, channelTeam := range list { channelTeamSplit := strings.Split(channelTeam, "/") - if len(channelTeamSplit) == 2 { - if strings.EqualFold(channelTeamSplit[0], team) && strings.EqualFold(channelTeamSplit[1], channel) { + switch len(channelTeamSplit) { + case 1: + if strings.EqualFold(channelTeamSplit[0], team) { return true } - } else if len(channelTeamSplit) == 1 { - if strings.EqualFold(channelTeamSplit[0], team) { + case 2: + if strings.EqualFold(channelTeamSplit[0], team) && strings.EqualFold(channelTeamSplit[1], channel) { return true } - } else { + default: mlog.Error("error splitting channel & team combination.") } - } + return false } -func (p *Plugin) isBotUser(userId string) (bool, *model.AppError) { - user, appErr := p.API.GetUser(userId) +func (p *Plugin) isBotUser(userID string) (bool, *model.AppError) { + user, appErr := p.API.GetUser(userID) if appErr != nil { p.API.LogError("failed to check if message for rewriting was send by a bot", "error", appErr) return false, appErr @@ -169,9 +170,9 @@ func (p *Plugin) ProcessPost(c *plugin.Context, post *model.Post) (*model.Post, // Let's assume for now that former is a lesser evil and carry on. } else if isBot { // We intentionally use a single if/else block so that the code is - // more readable and does not relly on hidden side effect of + // more readable and does not rely on hidden side effect of // isBot==false when appErr!=nil. - p.API.LogDebug("not rewriting message from bot", "userId", post.UserId) + p.API.LogDebug("not rewriting message from bot", "userID", post.UserId) return nil, "" } @@ -198,9 +199,9 @@ func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*mode // to the database. func (p *Plugin) MessageWillBeUpdated(c *plugin.Context, post *model.Post, _ *model.Post) (*model.Post, string) { conf := p.getConfig() - if conf.EnableOnUpdate { - return p.ProcessPost(c, post) - } else { + if !conf.EnableOnUpdate { return post, "" } + + return p.ProcessPost(c, post) } diff --git a/server/autolinkplugin/plugin_test.go b/server/autolinkplugin/plugin_test.go index 85811d92..24d71679 100644 --- a/server/autolinkplugin/plugin_test.go +++ b/server/autolinkplugin/plugin_test.go @@ -8,7 +8,6 @@ import ( "net/http/httptest" "testing" - "github.com/mattermost/mattermost-plugin-autolink/server/autolink" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin" "github.com/mattermost/mattermost-server/v5/plugin/plugintest" @@ -16,16 +15,16 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" ) func TestPlugin(t *testing.T) { conf := Config{ - Links: []autolink.Autolink{ - autolink.Autolink{ - Pattern: "(Mattermost)", - Template: "[Mattermost](https://mattermost.com)", - }, - }, + Links: []autolink.Autolink{{ + Pattern: "(Mattermost)", + Template: "[Mattermost](https://mattermost.com)", + }}, } testChannel := model.Channel{ @@ -54,7 +53,8 @@ func TestPlugin(t *testing.T) { p := New() p.SetAPI(api) - p.OnConfigurationChange() + err := p.OnConfigurationChange() + require.NoError(t, err) post := &model.Post{Message: "Welcome to Mattermost!"} rpost, _ := p.MessageWillBePosted(&plugin.Context{}, post) @@ -91,17 +91,17 @@ func (suite *SuiteAuthorization) SetupTest() { "GetUser", mock.AnythingOfType("string"), ).Return( - func(userId string) *model.User { - return suite.userInfo[userId] + func(userID string) *model.User { + return suite.userInfo[userID] }, - func(userId string) *model.AppError { - if _, ok := suite.userInfo[userId]; ok { - return nil - } else { + func(userID string) *model.AppError { + if _, ok := suite.userInfo[userID]; !ok { return &model.AppError{ - Message: fmt.Sprintf("user %s not found", userId), + Message: fmt.Sprintf("user %s not found", userID), } } + + return nil }, ) suite.api.On( @@ -273,6 +273,7 @@ func (suite *SuiteAuthorization) TestNonExistantUsersAreIgnored() { allowed, err = p.IsAuthorizedAdmin("karynaId") require.Error(suite.T(), err) + require.False(suite.T(), allowed) } func TestSuiteAuthorization(t *testing.T) { @@ -337,7 +338,8 @@ func TestSpecialCases(t *testing.T) { p := New() p.SetAPI(api) - p.OnConfigurationChange() + err := p.OnConfigurationChange() + require.NoError(t, err) var tests = []struct { inputMessage string @@ -510,12 +512,10 @@ func TestSpecialCases(t *testing.T) { func TestBotMessagesAreRewritenWhenGetUserFails(t *testing.T) { conf := Config{ - Links: []autolink.Autolink{ - autolink.Autolink{ - Pattern: "(Mattermost)", - Template: "[Mattermost](https://mattermost.com)", - }, - }, + Links: []autolink.Autolink{{ + Pattern: "(Mattermost)", + Template: "[Mattermost](https://mattermost.com)", + }}, } testChannel := model.Channel{ @@ -538,14 +538,14 @@ func TestBotMessagesAreRewritenWhenGetUserFails(t *testing.T) { api.On("GetTeam", mock.AnythingOfType("string")).Return(&testTeam, nil).Once() api.On("LogError", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("*model.AppError")) - err := &model.AppError{ + api.On("GetUser", mock.AnythingOfType("string")).Return(nil, &model.AppError{ Message: "foo error!", - } - api.On("GetUser", mock.AnythingOfType("string")).Return(nil, err).Once() + }).Once() p := New() p.SetAPI(api) - p.OnConfigurationChange() + err := p.OnConfigurationChange() + require.NoError(t, err) post := &model.Post{Message: "Welcome to Mattermost!"} rpost, _ := p.MessageWillBePosted(&plugin.Context{}, post) @@ -555,12 +555,10 @@ func TestBotMessagesAreRewritenWhenGetUserFails(t *testing.T) { func TestGetUserApiCallIsNotExecutedWhenThereAreNoChanges(t *testing.T) { conf := Config{ - Links: []autolink.Autolink{ - autolink.Autolink{ - Pattern: "(Mattermost)", - Template: "[Mattermost](https://mattermost.com)", - }, - }, + Links: []autolink.Autolink{{ + Pattern: "(Mattermost)", + Template: "[Mattermost](https://mattermost.com)", + }}, } testChannel := model.Channel{ @@ -585,7 +583,8 @@ func TestGetUserApiCallIsNotExecutedWhenThereAreNoChanges(t *testing.T) { p := New() p.SetAPI(api) - p.OnConfigurationChange() + err := p.OnConfigurationChange() + require.NoError(t, err) post := &model.Post{Message: "Welcome to FooBarism!"} rpost, _ := p.MessageWillBePosted(&plugin.Context{}, post) @@ -595,12 +594,10 @@ func TestGetUserApiCallIsNotExecutedWhenThereAreNoChanges(t *testing.T) { func TestBotMessagesAreNotRewriten(t *testing.T) { conf := Config{ - Links: []autolink.Autolink{ - autolink.Autolink{ - Pattern: "(Mattermost)", - Template: "[Mattermost](https://mattermost.com)", - }, - }, + Links: []autolink.Autolink{{ + Pattern: "(Mattermost)", + Template: "[Mattermost](https://mattermost.com)", + }}, } testChannel := model.Channel{ @@ -629,7 +626,8 @@ func TestBotMessagesAreNotRewriten(t *testing.T) { p := New() p.SetAPI(api) - p.OnConfigurationChange() + err := p.OnConfigurationChange() + require.NoError(t, err) post := &model.Post{Message: "Welcome to Mattermost!"} rpost, _ := p.MessageWillBePosted(&plugin.Context{}, post) @@ -639,16 +637,13 @@ func TestBotMessagesAreNotRewriten(t *testing.T) { func TestHashtags(t *testing.T) { conf := Config{ - Links: []autolink.Autolink{ - autolink.Autolink{ - Pattern: "foo", - Template: "#bar", - }, - autolink.Autolink{ - Pattern: "hash tags", - Template: "#hash #tags", - }, - }, + Links: []autolink.Autolink{{ + Pattern: "foo", + Template: "#bar", + }, { + Pattern: "hash tags", + Template: "#hash #tags", + }}, } testChannel := model.Channel{ @@ -677,7 +672,8 @@ func TestHashtags(t *testing.T) { p := New() p.SetAPI(api) - p.OnConfigurationChange() + err := p.OnConfigurationChange() + require.NoError(t, err) post := &model.Post{Message: "foo"} rpost, _ := p.MessageWillBePosted(&plugin.Context{}, post) @@ -692,13 +688,11 @@ func TestHashtags(t *testing.T) { func TestAPI(t *testing.T) { conf := Config{ - Links: []autolink.Autolink{ - autolink.Autolink{ - Name: "existing", - Pattern: "thing", - Template: "otherthing", - }, - }, + Links: []autolink.Autolink{{ + Name: "existing", + Pattern: "thing", + Template: "otherthing", + }}, } testChannel := model.Channel{ @@ -721,15 +715,20 @@ func TestAPI(t *testing.T) { p := New() p.SetAPI(api) - p.OnConfigurationChange() - p.OnActivate() + err := p.OnConfigurationChange() + require.NoError(t, err) + err = p.OnActivate() + require.NoError(t, err) - jbyte, _ := json.Marshal(&autolink.Autolink{Name: "new", Pattern: "newpat", Template: "newtemp"}) + jbyte, err := json.Marshal(&autolink.Autolink{Name: "new", Pattern: "newpat", Template: "newtemp"}) + require.NoError(t, err) recorder := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/api/v1/link", bytes.NewReader(jbyte)) + req, err := http.NewRequest("POST", "/api/v1/link", bytes.NewReader(jbyte)) + require.NoError(t, err) p.ServeHTTP(&plugin.Context{SourcePluginId: "somthing"}, recorder, req) resp := recorder.Result() + require.NotNil(t, resp) assert.Equal(t, http.StatusOK, resp.StatusCode) - require.Len(t, p.conf.Links, 2) + assert.Len(t, p.conf.Links, 2) assert.Equal(t, "new", p.conf.Links[1].Name) } diff --git a/server/main.go b/server/main.go index e155061e..aff3df49 100644 --- a/server/main.go +++ b/server/main.go @@ -1,8 +1,9 @@ package main import ( - "github.com/mattermost/mattermost-plugin-autolink/server/autolinkplugin" "github.com/mattermost/mattermost-server/v5/plugin" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolinkplugin" ) func main() { diff --git a/server/manifest.go b/server/manifest.go index a9de0cca..8b004a88 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -1,9 +1,11 @@ +// This file is automatically generated. Do not modify it manually. + package main var manifest = struct { - Id string + ID string Version string }{ - Id: "mattermost-autolink", + ID: "mattermost-autolink", Version: "1.2.0", }