diff --git a/.editorconfig b/.editorconfig index a517dcaab..bd669a83a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,14 +11,14 @@ trim_trailing_whitespace = true [*.go] indent_style = tab -[*.{js,jsx,ts,tsx,json,html}] +[*.{js, jsx, ts, tsx, json, html}] indent_style = space indent_size = 4 [webapp/package.json] indent_size = 2 -[Makefile,*.mk] +[{Makefile, *.mk}] indent_style = tab [*.md] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a640fc9b..5ce03e6bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,4 +18,4 @@ jobs: uses: mattermost/actions-workflows/.github/workflows/plugin-ci.yml@main secrets: inherit with: - golang-version: "1.24.6" + golang-version: "1.24" diff --git a/.golangci.yml b/.golangci.yml index e6adc2039..a01a7728e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,122 +1,65 @@ -version: 2 +version: "2" run: timeout: 5m modules-download-mode: readonly -linters-settings: - goconst: - min-len: 3 - min-occurrences: 2 - gofmt: - simplify: true - goimports: - local-prefixes: github.com/mattermost/mattermost-plugin-github - govet: - enable-all: true - disable: - - fieldalignment - misspell: - locale: US - revive: - linters: - disable-all: true + default: none enable: + - bidichk - bodyclose - errcheck - gocritic - gosec - govet - ineffassign + - makezero - misspell + - modernize - nakedret - revive - staticcheck - unconvert + - unqueryvet - unused - whitespace + settings: + govet: + enable-all: true + disable: + - fieldalignment + revive: + rules: + - name: exported + disabled: true + - name: unused-parameter + disabled: true + unqueryvet: + check-sql-builders: true exclusions: rules: - - linters: - - revive - text: var-naming|error-naming|exported|increment-decrement|error-strings|if-return|unused-parameter|blank-imports|empty-block|package-comments - - linters: - - errcheck - text: Error return value - - linters: - - staticcheck - text: SA1019 - - linters: - - staticcheck - text: ST1023 - - linters: - - staticcheck - text: ST1012 - - linters: - - staticcheck - text: ST1005 - - linters: - - staticcheck - text: QF1011 - - linters: - - staticcheck - text: QF1001 - - linters: - - staticcheck - text: QF1003 - - linters: - - staticcheck - text: QF1012 - - linters: - - staticcheck - text: QT1019 - - linters: - - staticcheck - text: QF1008 - - linters: - - staticcheck - text: QF1004 - - linters: - - staticcheck - text: ST1019 - - linters: - - staticcheck - text: QF1006 - - linters: - - staticcheck - text: QF1002 - - linters: - - staticcheck - text: QF1007 - - linters: - - gosec - text: G104 - - linters: - - gosec - text: G304 - - linters: - - gosec - text: G301 + - path: server/plugin/manifest.go + linters: + - unused + - path: server/configuration.go + linters: + - unused + - path: _test\.go + linters: + - bodyclose formatters: - disable-all: true enable: - gofmt + - gofumpt - goimports - -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 \ No newline at end of file + settings: + gofmt: + simplify: true + rewrite-rules: + - pattern: interface{} + replacement: any + goimports: + local-prefixes: + - github.com/mattermost/mattermost-plugin-github diff --git a/.nvmrc b/.nvmrc index 07c142ffe..a3597ecbd 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.13.1 +20.11 diff --git a/Makefile b/Makefile index 0f20967b9..f9e099008 100644 --- a/Makefile +++ b/Makefile @@ -175,16 +175,21 @@ all: check-style test dist apply: ./build/bin/manifest apply +## Ensures the plugin manifest is valid +.PHONY: manifest-check +manifest-check: + ./build/bin/manifest check + ## Install go tools install-go-tools: @echo Installing go tools - $(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0 - $(GO) install gotest.tools/gotestsum@v1.7.0 + $(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.8.0 + $(GO) install gotest.tools/gotestsum@v1.13.0 $(GO) install github.com/mattermost/mattermost-govet/v2@7d8db289e508999dfcac47b97c9490a0fec12d66 ## Runs eslint and golangci-lint .PHONY: check-style -check-style: apply webapp/node_modules install-go-tools +check-style: manifest-check apply webapp/node_modules install-go-tools @echo Checking for style guide compliance ifneq ($(HAS_WEBAPP),) @@ -362,7 +367,7 @@ endif .PHONY: coverage coverage: apply webapp/node_modules ifneq ($(HAS_SERVER),) - $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./... + $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./server/... $(GO) tool cover -html=server/coverage.txt endif diff --git a/build/manifest/main.go b/build/manifest/main.go index 335f4ebbf..3daf92cf3 100644 --- a/build/manifest/main.go +++ b/build/manifest/main.go @@ -6,13 +6,11 @@ package main import ( "encoding/json" "fmt" - "net/url" "os" "strings" - "github.com/pkg/errors" - "github.com/mattermost/mattermost/server/public/model" + "github.com/pkg/errors" ) const pluginIDGoFileTemplate = `// This file is automatically generated. Do not modify it manually. @@ -91,6 +89,11 @@ func main() { panic("failed to write manifest to dist directory: " + err.Error()) } + case "check": + if err := manifest.IsValid(); err != nil { + panic("failed to check manifest: " + err.Error()) + } + default: panic("unrecognized command: " + cmd) } @@ -101,11 +104,11 @@ func findManifest() (*model.Manifest, error) { if err != nil { return nil, errors.Wrap(err, "failed to find manifest in current working directory") } - manifestFile, err := os.Open(manifestFilePath) + manifestFile, err := os.Open(manifestFilePath) //nolint:gosec if err != nil { return nil, errors.Wrapf(err, "failed to open %s", manifestFilePath) } - defer manifestFile.Close() + defer func() { _ = manifestFile.Close() }() // Re-decode the manifest, disallowing unknown fields. When we write the manifest back out, // we don't want to accidentally clobber anything we won't preserve. @@ -120,8 +123,8 @@ func findManifest() (*model.Manifest, error) { // commit, and use the first version we find (to prevent causing errors) if manifest.Version == "" { var version string - tags := strings.Fields(BuildTagCurrent) - for _, t := range tags { + tags := strings.FieldsSeq(BuildTagCurrent) + for t := range tags { if strings.HasPrefix(t, "v") { version = t break @@ -139,10 +142,7 @@ func findManifest() (*model.Manifest, error) { // If no release notes specified, generate one from the latest tag, if present. if manifest.ReleaseNotesURL == "" && BuildTagLatest != "" { - manifest.ReleaseNotesURL, err = url.JoinPath(manifest.HomepageURL, "releases", "tag", BuildTagLatest) - if err != nil { - return nil, errors.Wrap(err, "failed to generate release notes URL") - } + manifest.ReleaseNotesURL = manifest.HomepageURL + "releases/tag/" + BuildTagLatest } return &manifest, nil @@ -171,8 +171,8 @@ func applyManifest(manifest *model.Manifest) error { // write generated code to file by using Go file template. if err := os.WriteFile( "server/plugin/manifest.go", - []byte(fmt.Sprintf(pluginIDGoFileTemplate, manifestStr)), - 0600, + fmt.Appendf(nil, pluginIDGoFileTemplate, manifestStr), + 0o600, ); err != nil { return errors.Wrap(err, "failed to write server/plugin/manifest.go") } @@ -194,8 +194,8 @@ func applyManifest(manifest *model.Manifest) error { // write generated code to file by using JS file template. if err := os.WriteFile( "webapp/src/manifest.ts", - []byte(fmt.Sprintf(pluginIDJSFileTemplate, manifestStr)), - 0600, + fmt.Appendf(nil, pluginIDJSFileTemplate, manifestStr), + 0o600, ); err != nil { return errors.Wrap(err, "failed to open webapp/src/manifest.ts") } @@ -211,7 +211,7 @@ func distManifest(manifest *model.Manifest) error { return err } - if err := os.WriteFile(fmt.Sprintf("dist/%s/plugin.json", manifest.Id), manifestBytes, 0600); err != nil { + if err := os.WriteFile(fmt.Sprintf("dist/%s/plugin.json", manifest.Id), manifestBytes, 0o600); err != nil { return errors.Wrap(err, "failed to write plugin.json") } diff --git a/build/pluginctl/logs.go b/build/pluginctl/logs.go index d182447a5..bf2ab3094 100644 --- a/build/pluginctl/logs.go +++ b/build/pluginctl/logs.go @@ -181,7 +181,7 @@ func checkJSONLogsSetting(ctx context.Context, client *model.Client4) error { return fmt.Errorf("failed to fetch config: %w", err) } if cfg.LogSettings.FileJson == nil || !*cfg.LogSettings.FileJson { - return errors.New("JSON output for file logs are disabled. Please enable LogSettings.FileJson via the configuration in Mattermost.") //nolint:revive,stylecheck + return errors.New("JSON output for file logs are disabled. Please enable LogSettings.FileJson via the configuration in Mattermost.") //nolint:staticcheck,revive } return nil diff --git a/build/pluginctl/main.go b/build/pluginctl/main.go index 8d3603807..0a3a5eafb 100644 --- a/build/pluginctl/main.go +++ b/build/pluginctl/main.go @@ -128,11 +128,11 @@ func getUnixClient(socketPath string) (*model.Client4, bool) { // deploy attempts to upload and enable a plugin via the Client4 API. // It will fail if plugin uploads are disabled. func deploy(ctx context.Context, client *model.Client4, pluginID, bundlePath string) error { - pluginBundle, err := os.Open(bundlePath) + pluginBundle, err := os.Open(bundlePath) //nolint:gosec // bundlePath is validated input if err != nil { return fmt.Errorf("failed to open %s: %w", bundlePath, err) } - defer pluginBundle.Close() + defer pluginBundle.Close() //nolint:errcheck log.Print("Uploading plugin via API.") _, _, err = client.UploadPluginForced(ctx, pluginBundle) diff --git a/build/setup.mk b/build/setup.mk index 3b90e8570..b90963af8 100644 --- a/build/setup.mk +++ b/build/setup.mk @@ -1,5 +1,3 @@ -GO_BUILD_FLAGS ?= - # Ensure that go is installed. Note that this is independent of whether or not a server is being # built, since the build script itself uses go. ifeq ($(GO),) diff --git a/client/client.go b/client/client.go index b3b668346..f5b190803 100644 --- a/client/client.go +++ b/client/client.go @@ -74,7 +74,7 @@ func (c *Client) GetConfiguration() (*plugin.Configuration, error) { if err != nil { return nil, err } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck respBody, err := io.ReadAll(resp.Body) if err != nil { @@ -111,7 +111,7 @@ func (c *Client) GetToken(userID string) (*oauth2.Token, error) { if err != nil { return nil, err } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck respBody, err := io.ReadAll(resp.Body) if err != nil { diff --git a/client/client_test.go b/client/client_test.go index 0a2d9ff66..cc7fe1e48 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -31,7 +31,7 @@ func TestRoundTripper(t *testing.T) { resp, err := roundTripper.RoundTrip(req) require.NoError(t, err) - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck require.Equal(t, http.StatusOK, resp.StatusCode) }) @@ -46,7 +46,7 @@ func TestRoundTripper(t *testing.T) { resp, err := roundTripper.RoundTrip(req) if resp != nil && resp.Body != nil { - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck } require.Nil(t, resp) require.Error(t, err) diff --git a/go.mod b/go.mod index fc0aca69e..8d8ca0897 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/mattermost/mattermost-plugin-github -go 1.24.6 +go 1.24.11 require ( github.com/Masterminds/sprig/v3 v3.2.2 @@ -15,21 +15,12 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require ( - github.com/beevik/etree v1.6.0 // indirect - github.com/jonboulle/clockwork v0.5.0 // indirect - github.com/mattermost/gosaml2 v0.10.0 // indirect - github.com/mattermost/mattermost/server/v8 v8.0.0-20251014075701-833e0125320d // indirect - github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect - github.com/russellhaering/goxmldsig v1.5.0 // indirect - golang.org/x/mod v0.29.0 // indirect -) - require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/beevik/etree v1.6.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -49,10 +40,14 @@ require ( github.com/hashicorp/yamux v0.1.2 // indirect github.com/huandu/xstrings v1.3.1 // indirect github.com/imdario/mergo v0.3.11 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect + github.com/mattermost/gosaml2 v0.10.0 // indirect github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect github.com/mattermost/logr/v2 v2.0.22 // indirect + github.com/mattermost/mattermost/server/v8 v8.0.0-20251014075701-833e0125320d // indirect + github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect @@ -62,6 +57,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/russellhaering/goxmldsig v1.5.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/shurcooL/githubv4 v0.0.0-20230424031643-6cea62ecd5a9 github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect @@ -74,6 +70,7 @@ require ( github.com/wiggin77/merror v1.0.5 // indirect github.com/wiggin77/srslog v1.0.1 // indirect golang.org/x/crypto v0.45.0 // indirect + golang.org/x/mod v0.29.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect diff --git a/server/plugin/api.go b/server/plugin/api.go index 41e940e07..950049d48 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -65,7 +65,7 @@ type RepoResponse struct { // Only send down fields to client that are needed type RepositoryResponse struct { - DefaultRepo RepoResponse `json:"defaultRepo,omitempty"` + DefaultRepo RepoResponse `json:"defaultRepo,omitzero"` Repos []RepoResponse `json:"repos,omitempty"` } @@ -117,7 +117,7 @@ const ( ResponseTypePlain ResponseType = "TEXT_RESPONSE" ) -func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { +func (p *Plugin) writeJSON(w http.ResponseWriter, v any) { b, err := json.Marshal(v) if err != nil { p.client.Log.Error("Failed to marshal JSON response", "error", err.Error()) @@ -504,7 +504,7 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, orgList := p.configuration.getOrganizations() p.client.Frontend.PublishWebSocketEvent( wsEventConnect, - map[string]interface{}{ + map[string]any{ "connected": true, "github_username": userInfo.GitHubUsername, "github_client_id": config.GitHubOAuthClientID, @@ -582,13 +582,13 @@ func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request config := p.getConfiguration() type ConnectedResponse struct { - Connected bool `json:"connected"` - GitHubUsername string `json:"github_username"` - GitHubClientID string `json:"github_client_id"` - EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` - Organizations []string `json:"organizations"` - UserSettings *UserSettings `json:"user_settings"` - ClientConfiguration map[string]interface{} `json:"configuration"` + Connected bool `json:"connected"` + GitHubUsername string `json:"github_username"` + GitHubClientID string `json:"github_client_id"` + EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` + Organizations []string `json:"organizations"` + UserSettings *UserSettings `json:"user_settings"` + ClientConfiguration map[string]any `json:"configuration"` } orgList := p.configuration.getOrganizations() @@ -678,7 +678,7 @@ func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request } func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Request) { - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) username := c.GHInfo.GitHubUsername orgList := p.configuration.getOrganizations() query := getMentionSearchQuery(username, orgList) @@ -702,7 +702,7 @@ func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Requ } func (p *Plugin) getUnreadsData(c *UserContext) []*FilteredNotification { - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var notifications []*github.Notification var err error cErr := p.useGitHubClient(c.GHInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { @@ -745,7 +745,7 @@ func (p *Plugin) getUnreadsData(c *UserContext) []*FilteredNotification { } func (p *Plugin) getPrsDetails(c *UserContext, w http.ResponseWriter, r *http.Request) { - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var prList []*PRDetails if err := json.NewDecoder(r.Body).Decode(&prList); err != nil { @@ -757,8 +757,6 @@ func (p *Plugin) getPrsDetails(c *UserContext, w http.ResponseWriter, r *http.Re prDetails := make([]*PRDetails, len(prList)) var wg sync.WaitGroup for i, pr := range prList { - i := i - pr := pr wg.Add(1) go func() { defer wg.Done() @@ -831,7 +829,6 @@ func (p *Plugin) fetchPRDetails(c *UserContext, client *github.Client, prURL str func fetchReviews(c *UserContext, client *github.Client, repoOwner string, repoName string, number int) ([]*github.PullRequestReview, error) { reviewsList, _, err := client.PullRequests.ListReviews(c.Ctx, repoOwner, repoName, number, nil) - if err != nil { return []*github.PullRequestReview{}, errors.Wrap(err, "could not list reviews") } @@ -845,7 +842,7 @@ func getRepoOwnerAndNameFromURL(url string) (string, string) { } func (p *Plugin) searchIssues(c *UserContext, w http.ResponseWriter, r *http.Request) { - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) searchTerm := r.FormValue("term") orgsList := p.configuration.getOrganizations() @@ -959,7 +956,7 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht return } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) post, err := p.client.Post.GetPost(req.PostID) if err != nil { @@ -1038,7 +1035,7 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*graphql.GithubPRDetails, assignmentResp []*github.Issue, openPRResp []*graphql.GithubPRDetails, err error) { graphQLClient := p.graphQLConnect(c.GHInfo) - reviewResp, assignmentResp, openPRResp, err = graphQLClient.GetLHSData(c.Context.Ctx) + reviewResp, assignmentResp, openPRResp, err = graphQLClient.GetLHSData(c.Ctx) if err != nil { return []*graphql.GithubPRDetails{}, []*github.Issue{}, []*graphql.GithubPRDetails{}, err } @@ -1072,7 +1069,7 @@ func (p *Plugin) getSidebarContent(c *UserContext, w http.ResponseWriter, r *htt } func (p *Plugin) postToDo(c *UserContext, w http.ResponseWriter, r *http.Request) { - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) text, err := p.GetToDo(c.Ctx, c.GHInfo, githubClient) if err != nil { @@ -1126,7 +1123,7 @@ func (p *Plugin) getIssueByNumber(c *UserContext, w http.ResponseWriter, r *http return } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var result *github.Issue if cErr := p.useGitHubClient(c.GHInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { @@ -1174,7 +1171,7 @@ func (p *Plugin) getPrByNumber(c *UserContext, w http.ResponseWriter, r *http.Re return } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var result *github.PullRequest if cErr := p.useGitHubClient(c.GHInfo, func(userInfo *GitHubUserInfo, token *oauth2.Token) error { result, _, err = githubClient.PullRequests.Get(c.Ctx, owner, repo, numberInt) @@ -1218,7 +1215,7 @@ func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Reques return } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var allLabels []*github.Label opt := github.ListOptions{PerPage: 50} @@ -1256,7 +1253,7 @@ func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Req return } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var allAssignees []*github.User opt := github.ListOptions{PerPage: 50} @@ -1294,7 +1291,7 @@ func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Re return } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var allMilestones []*github.Milestone opt := github.ListOptions{PerPage: 50} @@ -1408,7 +1405,7 @@ func (p *Plugin) getOrganizations(c *UserContext, w http.ResponseWriter, r *http if includeLoggedInUser == "true" { allOrgs = append(allOrgs, &github.Organization{Login: &c.GHInfo.GitHubUsername}) } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) orgList, err := getOrganizationList(c.Ctx, "", githubClient, github.ListOptions{PerPage: 50}) if err != nil { c.Log.WithError(err).Errorf("Failed to list organizations") @@ -1435,7 +1432,7 @@ func (p *Plugin) getOrganizations(c *UserContext, w http.ResponseWriter, r *http } func (p *Plugin) getReposByOrg(c *UserContext, w http.ResponseWriter, r *http.Request) { - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) opt := github.ListOptions{PerPage: 50} @@ -1545,7 +1542,7 @@ func getRepository(c context.Context, org string, repo string, githubClient *git } func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http.Request) { - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) org := p.getConfiguration().GitHubOrg channelID := r.URL.Query().Get(channelIDParam) @@ -1726,7 +1723,7 @@ func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Requ owner := splittedRepo[0] repoName := splittedRepo[1] - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubClient := p.githubConnectUser(c.Ctx, c.GHInfo) var resp *github.Response var result *github.Issue if cErr := p.useGitHubClient(c.GHInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { @@ -1736,7 +1733,7 @@ func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Requ } return nil }); cErr != nil { - if resp != nil && resp.Response.StatusCode == http.StatusGone { + if resp != nil && resp.StatusCode == http.StatusGone { p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) return } diff --git a/server/plugin/api_test.go b/server/plugin/api_test.go index 4378188fe..ca63a7f19 100644 --- a/server/plugin/api_test.go +++ b/server/plugin/api_test.go @@ -25,8 +25,7 @@ import ( "github.com/mattermost/mattermost-plugin-github/server/testutils" ) -type panicHandler struct { -} +type panicHandler struct{} func (ph panicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { panic("bad handler") @@ -58,7 +57,7 @@ func TestWithRecovery(t *testing.T) { resp := w.Result() if resp.Body != nil { - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck _, err := io.Copy(io.Discard, resp.Body) require.NoError(t, err) } diff --git a/server/plugin/cluster.go b/server/plugin/cluster.go index 6b4ff9021..deed96706 100644 --- a/server/plugin/cluster.go +++ b/server/plugin/cluster.go @@ -24,7 +24,7 @@ func (p *Plugin) sendOAuthCompleteEvent(event OAuthCompleteEvent) { p.sendMessageToCluster(oauthCompleteEventID, event) } -func (p *Plugin) sendMessageToCluster(id string, v interface{}) { +func (p *Plugin) sendMessageToCluster(id string, v any) { b, err := json.Marshal(v) if err != nil { p.client.Log.Warn("couldn't get JSON bytes from cluster message", diff --git a/server/plugin/command.go b/server/plugin/command.go index 87f80692d..9c4d273a0 100644 --- a/server/plugin/command.go +++ b/server/plugin/command.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "net/http" + "slices" "strings" "unicode" @@ -111,13 +112,13 @@ func validateFeatures(features []string) (bool, []string) { // checkFeatureConflict returns false when given features // cannot be added together along with a list of the conflicting features. func checkFeatureConflict(fs []string) (bool, []string) { - if SliceContainsString(fs, featureIssues) && SliceContainsString(fs, featureIssueCreation) { + if slices.Contains(fs, featureIssues) && slices.Contains(fs, featureIssueCreation) { return false, []string{featureIssues, featureIssueCreation} } - if SliceContainsString(fs, featurePulls) && SliceContainsString(fs, featurePullsMerged) { + if slices.Contains(fs, featurePulls) && slices.Contains(fs, featurePullsMerged) { return false, []string{featurePulls, featurePullsMerged} } - if SliceContainsString(fs, featurePulls) && SliceContainsString(fs, featurePullsCreated) { + if slices.Contains(fs, featurePulls) && slices.Contains(fs, featurePullsCreated) { return false, []string{featurePulls, featurePullsCreated} } return true, nil @@ -181,15 +182,6 @@ func (p *Plugin) handleMuteList(_ *model.CommandArgs, userInfo *GitHubUserInfo) return "Your muted users:\n" + mutedUsers } -func contains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} - func (p *Plugin) isValidGitHubUsername(username string, userInfo *GitHubUserInfo) (bool, error) { githubClient := p.githubConnectUser(context.Background(), userInfo) @@ -227,7 +219,7 @@ func (p *Plugin) handleMuteAdd(_ *model.CommandArgs, username string, userInfo * return "An error occurred getting muted users. Please try again later" } - if contains(mutedUsernames, username) { + if slices.Contains(mutedUsernames, username) { return username + " is already muted" } @@ -303,20 +295,20 @@ func (p *Plugin) handleMuteCommand(_ *plugin.Context, args *model.CommandArgs, p command := parameters[0] - switch { - case command == "list": + switch command { + case "list": return p.handleMuteList(args, userInfo) - case command == "add": + case "add": if len(parameters) != 2 { return "Invalid number of parameters supplied to " + command } return p.handleMuteAdd(args, parameters[1], userInfo) - case command == "delete": + case "delete": if len(parameters) != 2 { return "Invalid number of parameters supplied to " + command } return p.handleUnmute(args, parameters[1], userInfo) - case command == "delete-all": + case "delete-all": return p.handleUnmuteAll(args, userInfo) default: return fmt.Sprintf("Unknown subcommand %v", command) @@ -361,12 +353,12 @@ func (p *Plugin) handleSubscriptions(c *plugin.Context, args *model.CommandArgs, command := parameters[0] parameters = parameters[1:] - switch { - case command == "list": + switch command { + case "list": return p.handleSubscriptionsList(c, args, parameters, userInfo) - case command == "add": + case "add": return p.handleSubscribesAdd(c, args, parameters, userInfo) - case command == "delete": + case "delete": return p.handleUnsubscribe(c, args, parameters, userInfo) default: return fmt.Sprintf("Unknown subcommand %v", command) @@ -812,8 +804,8 @@ func (p *Plugin) handleIssue(_ *plugin.Context, args *model.CommandArgs, paramet command := parameters[0] parameters = parameters[1:] - switch { - case command == "create": + switch command { + case "create": p.openIssueCreateModal(args.UserId, args.ChannelId, strings.Join(parameters, " ")) return "" default: @@ -829,12 +821,12 @@ func (p *Plugin) handleDefaultRepo(c *plugin.Context, args *model.CommandArgs, p command := parameters[0] parameters = parameters[1:] - switch { - case command == "set": + switch command { + case "set": return p.handleSetDefaultRepo(args, parameters, userInfo) - case command == "get": + case "get": return p.handleGetDefaultRepo(args, userInfo) - case command == "unset": + case "unset": return p.handleUnSetDefaultRepo(args, userInfo) default: return fmt.Sprintf("Unknown subcommand %v", command) @@ -872,7 +864,7 @@ func (p *Plugin) handleSetDefaultRepo(args *model.CommandArgs, parameters []stri return fmt.Sprintf("Unknown repository %s", fullNameFromOwnerAndRepo(owner, repo)) } - if _, err := p.store.Set(fmt.Sprintf(DefaultRepoKey, args.ChannelId, userInfo.UserID), []byte(fmt.Sprintf("%s/%s", owner, repo))); err != nil { + if _, err := p.store.Set(fmt.Sprintf(DefaultRepoKey, args.ChannelId, userInfo.UserID), fmt.Appendf(nil, "%s/%s", owner, repo)); err != nil { return "Error occurred saving the default repo" } @@ -930,8 +922,7 @@ func (p *Plugin) isOrgInLockedOrgs(configuredOrgs, owner string) bool { return true } - orgs := strings.Split(configuredOrgs, ",") - for _, org := range orgs { + for org := range strings.SplitSeq(configuredOrgs, ",") { if strings.EqualFold(strings.TrimSpace(org), strings.TrimSpace(owner)) { return true } @@ -958,12 +949,12 @@ func (p *Plugin) handleSetup(_ *plugin.Context, args *model.CommandArgs, paramet } else { command := parameters[0] - switch { - case command == "oauth": + switch command { + case "oauth": err = p.flowManager.StartOauthWizard(userID) - case command == "webhook": + case "webhook": err = p.flowManager.StartWebhookWizard(userID) - case command == "announcement": + case "announcement": err = p.flowManager.StartAnnouncementWizard(userID) default: return fmt.Sprintf("Unknown subcommand %v", command) @@ -1333,12 +1324,3 @@ func parseCommand(input string) (command, action string, parameters []string) { return command, action, parameters } - -func SliceContainsString(a []string, x string) bool { - for _, n := range a { - if x == n { - return true - } - } - return false -} diff --git a/server/plugin/command_test.go b/server/plugin/command_test.go index 3789baad1..81eebcc44 100644 --- a/server/plugin/command_test.go +++ b/server/plugin/command_test.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "testing" "github.com/golang/mock/gomock" @@ -474,7 +475,7 @@ func TestContains(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - result := contains(tc.slice, tc.element) + result := slices.Contains(tc.slice, tc.element) tc.assertions(t, result) }) } @@ -1194,7 +1195,8 @@ func TestHandleUnsubscribe(t *testing.T) { setup: func() { mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { *value = &Subscriptions{Repositories: map[string][]*Subscription{ - "owner/repo": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner/repo"}}}} + "owner/repo": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner/repo"}}, + }} return nil }).Times(1) mockAPI.On("GetUser", MockUserID).Return(nil, &model.AppError{Message: "error getting user"}).Times(1) @@ -1211,7 +1213,8 @@ func TestHandleUnsubscribe(t *testing.T) { setup: func() { mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { *value = &Subscriptions{Repositories: map[string][]*Subscription{ - "owner/": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner"}}}} + "owner/": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner"}}, + }} return nil }).Times(1) mockAPI.On("GetUser", MockUserID).Return(&model.User{Username: MockUsername}, nil).Times(1) @@ -1230,7 +1233,8 @@ func TestHandleUnsubscribe(t *testing.T) { setup: func() { mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { *value = &Subscriptions{Repositories: map[string][]*Subscription{ - "owner/": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: ""}}}} + "owner/": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: ""}}, + }} return nil }).Times(1) mockAPI.On("GetUser", MockUserID).Return(&model.User{Username: MockUsername}, nil).Times(1) @@ -1247,7 +1251,8 @@ func TestHandleUnsubscribe(t *testing.T) { setup: func() { mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { *value = &Subscriptions{Repositories: map[string][]*Subscription{ - "owner/repo": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner/repo"}}}} + "owner/repo": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner/repo"}}, + }} return nil }).Times(1) mockAPI.On("GetUser", MockUserID).Return(&model.User{Username: MockUsername}, nil).Times(1) @@ -1266,7 +1271,8 @@ func TestHandleUnsubscribe(t *testing.T) { setup: func() { mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { *value = &Subscriptions{Repositories: map[string][]*Subscription{ - "owner/repo": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner/repo"}}}} + "owner/repo": {{ChannelID: MockChannelID, CreatorID: MockCreatorID, Repository: "owner/repo"}}, + }} return nil }).Times(1) mockAPI.ExpectedCalls = nil @@ -1513,7 +1519,7 @@ func TestHandleIssue(t *testing.T) { parameters: []string{"create", "Test issue title"}, setup: func() { mockAPI.On("PublishWebSocketEvent", wsEventCreateIssue, - map[string]interface{}{ + map[string]any{ "title": "Test issue title", "channel_id": "testChannelID", }, @@ -1523,7 +1529,7 @@ func TestHandleIssue(t *testing.T) { assertions: func(result string) { assert.Equal(t, "", result) mockAPI.AssertCalled(t, "PublishWebSocketEvent", wsEventCreateIssue, - map[string]interface{}{ + map[string]any{ "title": "Test issue title", "channel_id": "testChannelID", }, @@ -1635,7 +1641,6 @@ func TestHandleSubscribe(t *testing.T) { name: "default case, handleSubscribesAdd called", parameters: []string{"invalid_parameter_1", "invalid_parameter_2", "invalid_parameter_3"}, setup: func() { - }, assertions: func(result string) { assert.Equal(t, "Please use the correct format for flags: -- ", result) @@ -1736,10 +1741,10 @@ func TestGetCommand(t *testing.T) { // Creating a mock SVG file with dummy content. tempDir := t.TempDir() assetsDir := filepath.Join(tempDir, "assets") - err := os.Mkdir(assetsDir, 0755) + err := os.Mkdir(assetsDir, 0o750) require.NoError(t, err) tempFilePath := filepath.Join(assetsDir, "icon-bg.svg") - err = os.WriteFile(tempFilePath, []byte("icon data"), 0600) + err = os.WriteFile(tempFilePath, []byte("icon data"), 0o600) require.NoError(t, err) tests := []struct { @@ -1849,73 +1854,3 @@ func TestToSlice(t *testing.T) { }) } } - -func TestSliceContainsString(t *testing.T) { - tests := []struct { - name string - slice []string - searchString string - expectedResult bool - }{ - { - name: "Empty slice", - slice: []string{}, - searchString: "testString1", - expectedResult: false, - }, - { - name: "String exists in slice", - slice: []string{"testString1", "testString2", "testString3"}, - searchString: "testString2", - expectedResult: true, - }, - { - name: "String does not exist in slice", - slice: []string{"testString1", "testString2", "testString3"}, - searchString: "testString4", - expectedResult: false, - }, - { - name: "String is the first element in the slice", - slice: []string{"testString2", "testString1", "testString3"}, - searchString: "testString1", - expectedResult: true, - }, - { - name: "String is the last element in the slice", - slice: []string{"testString1", "testString3", "testString2"}, - searchString: "testString2", - expectedResult: true, - }, - { - name: "String with different case", - slice: []string{"testString1", "testString2", "TestString3"}, - searchString: "testString3", - expectedResult: false, - }, - { - name: "Search string is empty", - slice: []string{"testString1", "testString2", "testString3"}, - searchString: "", - expectedResult: false, - }, - { - name: "Slice contains empty string", - slice: []string{"testString1", "testString2", ""}, - searchString: "", - expectedResult: true, - }, - { - name: "Slice with multiple occurrences of the search string", - slice: []string{"testString2", "testString1", "testString2", "testString3"}, - searchString: "testString2", - expectedResult: true, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - result := SliceContainsString(tc.slice, tc.searchString) - assert.Equal(t, tc.expectedResult, result) - }) - } -} diff --git a/server/plugin/configuration.go b/server/plugin/configuration.go index 820d6bbe7..9886560d1 100644 --- a/server/plugin/configuration.go +++ b/server/plugin/configuration.go @@ -44,8 +44,8 @@ type Configuration struct { GetNotificationForDraftPRs bool `json:"getnotificationfordraftprs"` } -func (c *Configuration) ToMap() (map[string]interface{}, error) { - var out map[string]interface{} +func (c *Configuration) ToMap() (map[string]any, error) { + var out map[string]any data, err := json.Marshal(c) if err != nil { return nil, err @@ -117,8 +117,8 @@ func (c *Configuration) IsSASS() bool { return c.EnterpriseBaseURL == "" && c.EnterpriseUploadURL == "" } -func (c *Configuration) ClientConfiguration() map[string]interface{} { - return map[string]interface{}{ +func (c *Configuration) ClientConfiguration() map[string]any { + return map[string]any{ "left_sidebar_enabled": c.EnableLeftSidebar, } } @@ -126,7 +126,7 @@ func (c *Configuration) ClientConfiguration() map[string]interface{} { // Clone shallow copies the configuration. Your implementation may require a deep copy if // your configuration has reference types. func (c *Configuration) Clone() *Configuration { - var clone = *c + clone := *c return &clone } @@ -197,7 +197,7 @@ func (p *Plugin) setConfiguration(configuration *Configuration) { func (p *Plugin) OnConfigurationChange() error { p.ensurePluginAPIClient() - var configuration = new(Configuration) + configuration := new(Configuration) // Load the public configuration fields from the Mattermost server configuration. err := p.client.Configuration.LoadPluginConfiguration(configuration) diff --git a/server/plugin/flows.go b/server/plugin/flows.go index 9fb589e22..f34cdd9c8 100644 --- a/server/plugin/flows.go +++ b/server/plugin/flows.go @@ -313,7 +313,7 @@ func (fm *FlowManager) stepDelegateQuestion() flow.Step { }) } -func (fm *FlowManager) submitDelegateSelection(f *flow.Flow, submitted map[string]interface{}) (flow.Name, flow.State, map[string]string, error) { +func (fm *FlowManager) submitDelegateSelection(f *flow.Flow, submitted map[string]any) (flow.Name, flow.State, map[string]string, error) { delegateIDRaw, ok := submitted["delegate"] if !ok { return "", nil, nil, errors.New("delegate missing") @@ -368,7 +368,6 @@ func (fm *FlowManager) stepEnterprise() flow.Step { SubmitLabel: "Save & continue", Elements: []model.DialogElement{ { - DisplayName: "Enterprise Base URL", Name: "base_url", Type: "text", @@ -394,7 +393,7 @@ func (fm *FlowManager) stepEnterprise() flow.Step { WithButton(cancelButton()) } -func (fm *FlowManager) submitEnterpriseConfig(f *flow.Flow, submitted map[string]interface{}) (flow.Name, flow.State, map[string]string, error) { +func (fm *FlowManager) submitEnterpriseConfig(f *flow.Flow, submitted map[string]any) (flow.Name, flow.State, map[string]string, error) { errorList := map[string]string{} baseURLRaw, ok := submitted["base_url"] @@ -514,7 +513,7 @@ func (fm *FlowManager) stepOAuthInput() flow.Step { WithButton(cancelButton()) } -func (fm *FlowManager) submitOAuthConfig(f *flow.Flow, submitted map[string]interface{}) (flow.Name, flow.State, map[string]string, error) { +func (fm *FlowManager) submitOAuthConfig(f *flow.Flow, submitted map[string]any) (flow.Name, flow.State, map[string]string, error) { errorList := map[string]string{} clientIDRaw, ok := submitted["client_id"] @@ -607,7 +606,6 @@ The final setup step requires a Mattermost System Admin to create a webhook for SubmitLabel: "Create", Elements: []model.DialogElement{ { - DisplayName: "GitHub repository or organization name", Name: "repo_org", Type: "text", @@ -626,7 +624,7 @@ The final setup step requires a Mattermost System Admin to create a webhook for }) } -func (fm *FlowManager) submitWebhook(f *flow.Flow, submitted map[string]interface{}) (flow.Name, flow.State, map[string]string, error) { +func (fm *FlowManager) submitWebhook(f *flow.Flow, submitted map[string]any) (flow.Name, flow.State, map[string]string, error) { repoOrgRaw, ok := submitted["repo_org"] if !ok { return "", nil, nil, errors.New("repo_org missing") @@ -652,7 +650,7 @@ func (fm *FlowManager) submitWebhook(f *flow.Flow, submitted map[string]interfac fm.client.Log.Warn("Failed to build webHookURL", "err", err) } - webhookConfig := map[string]interface{}{ + webhookConfig := map[string]any{ "content_type": "json", "insecure_ssl": "0", "secret": config.WebhookSecret, @@ -799,7 +797,7 @@ func (fm *FlowManager) stepAnnouncementQuestion() flow.Step { }) } -func (fm *FlowManager) submitChannelAnnouncement(f *flow.Flow, submitted map[string]interface{}) (flow.Name, flow.State, map[string]string, error) { +func (fm *FlowManager) submitChannelAnnouncement(f *flow.Flow, submitted map[string]any) (flow.Name, flow.State, map[string]string, error) { channelIDRaw, ok := submitted["channel_id"] if !ok { return "", nil, nil, errors.New("channel_id missing") @@ -845,9 +843,11 @@ func (fm *FlowManager) stepAnnouncementConfirmation() flow.Step { } func printGithubErrorResponse(err *github.ErrorResponse) error { - msg := err.Message - for _, err := range err.Errors { - msg += ", " + err.Message + var sb strings.Builder + sb.WriteString(err.Message) + for _, e := range err.Errors { + sb.WriteString(", ") + sb.WriteString(e.Message) } - return errors.New(msg) + return errors.New(sb.String()) } diff --git a/server/plugin/graphql/client.go b/server/plugin/graphql/client.go index 53083b03a..a606dcb8b 100644 --- a/server/plugin/graphql/client.go +++ b/server/plugin/graphql/client.go @@ -57,7 +57,7 @@ func NewClient(logger pluginapi.LogService, getOrganizations func() []string, to } // executeQuery takes a query struct and sends it to Github GraphQL API via helper package. -func (c *Client) executeQuery(ctx context.Context, qry interface{}, params map[string]interface{}) error { +func (c *Client) executeQuery(ctx context.Context, qry any, params map[string]any) error { if err := c.client.Query(ctx, qry, params); err != nil { return errors.Wrap(err, "error in executing query") } diff --git a/server/plugin/graphql/lhs_request.go b/server/plugin/graphql/lhs_request.go index 6dbf93ab1..e3dc74399 100644 --- a/server/plugin/graphql/lhs_request.go +++ b/server/plugin/graphql/lhs_request.go @@ -67,7 +67,7 @@ func (c *Client) fetchLHSData( baseAssignee = fmt.Sprintf("org:%s %s", org, baseAssignee) } - params := map[string]interface{}{ + params := map[string]any{ queryParamOpenPRQueryArg: githubv4.String(baseOpenPR), queryParamReviewPRQueryArg: githubv4.String(baseReviewPR), queryParamAssigneeQueryArg: githubv4.String(baseAssignee), @@ -78,11 +78,7 @@ func (c *Client) fetchLHSData( allReviewRequestsFetched, allAssignmentsFetched, allOpenPRsFetched := false, false, false - for { - if allReviewRequestsFetched && allAssignmentsFetched && allOpenPRsFetched { - break - } - + for !allReviewRequestsFetched || !allAssignmentsFetched || !allOpenPRsFetched { if err := c.executeQuery(ctx, &mainQuery, params); err != nil { return nil, nil, nil, errors.Wrap(err, "Not able to execute the query") } diff --git a/server/plugin/mm_34646_token_refresh.go b/server/plugin/mm_34646_token_refresh.go index 740c04c4e..e1bf63341 100644 --- a/server/plugin/mm_34646_token_refresh.go +++ b/server/plugin/mm_34646_token_refresh.go @@ -13,9 +13,11 @@ import ( "github.com/mattermost/mattermost/server/public/pluginapi/cluster" ) -const pageSize = 100 -const delayBetweenUsers = 1 * time.Second -const delayToStart = 1 * time.Minute +const ( + pageSize = 100 + delayBetweenUsers = 1 * time.Second + delayToStart = 1 * time.Minute +) func (p *Plugin) forceResetAllMM34646() error { config := p.getConfiguration() diff --git a/server/plugin/permalinks_test.go b/server/plugin/permalinks_test.go index 9c394d3c1..aa2d9f596 100644 --- a/server/plugin/permalinks_test.go +++ b/server/plugin/permalinks_test.go @@ -123,12 +123,14 @@ func TestGetReplacements(t *testing.T) { }, }, }, - }, { + }, + { name: "inside link", input: "should not expand [link](https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22) here", numReplacements: 0, replacements: []replacement{}, - }, { + }, + { name: "one link, one expansion", input: "first should not expand [link](https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22) this should https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22 lorem ipsum", numReplacements: 1, @@ -153,7 +155,8 @@ func TestGetReplacements(t *testing.T) { }, }, }, - }, { + }, + { name: "one expansion, one link", input: "first should expand https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22 lorem ipsum , this should not [link](https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22)", numReplacements: 1, @@ -178,12 +181,14 @@ func TestGetReplacements(t *testing.T) { }, }, }, - }, { + }, + { name: "2 links", input: "both should not expand- [link](https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22) and [link](https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22)", numReplacements: 0, replacements: []replacement{}, - }, { + }, + { name: "multiple expansions", input: "multiple - https://github.com/golang/go/blob/27fc32ff01cc699e160890546816bd99d6c57823/src/debug/macho/macho.go#L13-L16 second https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22", numReplacements: 2, @@ -226,7 +231,8 @@ func TestGetReplacements(t *testing.T) { }, }, }, - }, { + }, + { name: "single line", input: "this is a one line permalink https://github.com/mattermost/mattermost-server/blob/4225977966cf0855c8a5e55f8a0fef702b19dc18/api4/bot.go#L16", numReplacements: 1, @@ -457,7 +463,7 @@ func getClient() (*github.Client, func()) { apiHandler.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/api-v3/repos/mattermost/mattermost-server/contents/app/authentication.go": - fmt.Fprintln(w, `{ + _, _ = fmt.Fprintln(w, `{ "name": "authentication.go", "path": "app/authentication.go", "sha": "c5c4ebf9077d04306ce8eca1e451421e4df7ca3c", @@ -476,7 +482,7 @@ func getClient() (*github.Client, func()) { } }`) case "/api-v3/repos/badorg/badrepo/path/file.go": - fmt.Fprintln(w, `{ + _, _ = fmt.Fprintln(w, `{ "sha": "c5c4ebf9077d04306ce8eca1e451421e4df7ca3c", "size": 7950, "url": "https://api.github.com/repos/badorg/badrepo/path/file.g", diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index 5e46720b5..fcab70252 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -11,6 +11,7 @@ import ( "net/url" "path/filepath" "regexp" + "slices" "strings" "sync" @@ -61,18 +62,16 @@ const ( invalidTokenError = "401 Bad credentials" //#nosec G101 -- False positive ) -var ( - // testOAuthServerURL is the URL for the oauthServer used for testing purposes - // It should be set through ldflags when compiling for E2E, and keep it blank otherwise - testOAuthServerURL = "" -) +// testOAuthServerURL is the URL for the oauthServer used for testing purposes +// It should be set through ldflags when compiling for E2E, and keep it blank otherwise +var testOAuthServerURL = "" type KvStore interface { Set(key string, value any, options ...pluginapi.KVSetOption) (bool, error) ListKeys(page int, count int, options ...pluginapi.ListKeysOption) ([]string, error) Get(key string, o any) error Delete(key string) error - SetAtomicWithRetries(key string, valueFunc func(oldValue []byte) (newValue interface{}, err error)) error + SetAtomicWithRetries(key string, valueFunc func(oldValue []byte) (newValue any, err error)) error } type Plugin struct { @@ -723,7 +722,7 @@ func (p *Plugin) disconnectGitHubAccount(userID string) { func (p *Plugin) openIssueCreateModal(userID string, channelID string, title string) { p.client.Frontend.PublishWebSocketEvent( wsEventCreateIssue, - map[string]interface{}{ + map[string]any{ "title": title, "channel_id": channelID, }, @@ -862,10 +861,11 @@ func (p *Plugin) GetToDo(ctx context.Context, info *GitHubUserInfo, githubClient return "", errors.Wrap(cErr, "error occurred while searching for assignments") } - text := "##### Unread Messages\n" + var text strings.Builder + text.WriteString("##### Unread Messages\n") notificationCount := 0 - notificationContent := "" + var notificationContent strings.Builder for _, n := range notifications { if n.GetReason() == notificationReasonSubscribed { continue @@ -885,7 +885,7 @@ func (p *Plugin) GetToDo(ctx context.Context, info *GitHubUserInfo, githubClient switch notificationType { case "RepositoryVulnerabilityAlert": message := fmt.Sprintf("[Vulnerability Alert for %v](%v)", n.GetRepository().GetFullName(), fixGithubNotificationSubjectURL(n.GetSubject().GetURL(), "")) - notificationContent += fmt.Sprintf("* %v\n", message) + fmt.Fprintf(¬ificationContent, "* %v\n", message) default: issueURL := n.GetSubject().GetURL() issueNumIndex := strings.LastIndex(issueURL, "/") @@ -897,56 +897,56 @@ func (p *Plugin) GetToDo(ctx context.Context, info *GitHubUserInfo, githubClient notificationTitle := notificationSubject.GetTitle() notificationURL := fixGithubNotificationSubjectURL(subjectURL, issueNum) - notificationContent += getToDoDisplayText(baseURL, notificationTitle, notificationURL, notificationType, n.GetRepository()) + notificationContent.WriteString(getToDoDisplayText(baseURL, notificationTitle, notificationURL, notificationType, n.GetRepository())) } notificationCount++ } if notificationCount == 0 { - text += "You don't have any unread messages.\n" + text.WriteString("You don't have any unread messages.\n") } else { - text += fmt.Sprintf("You have %v unread messages:\n", notificationCount) - text += notificationContent + fmt.Fprintf(&text, "You have %v unread messages:\n", notificationCount) + text.WriteString(notificationContent.String()) } - text += "##### Review Requests\n" + text.WriteString("##### Review Requests\n") if issueResults.GetTotal() == 0 { - text += "You don't have any pull requests awaiting your review.\n" + text.WriteString("You don't have any pull requests awaiting your review.\n") } else { - text += fmt.Sprintf("You have %v pull requests awaiting your review:\n", issueResults.GetTotal()) + fmt.Fprintf(&text, "You have %v pull requests awaiting your review:\n", issueResults.GetTotal()) for _, pr := range issueResults.Issues { - text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "", nil) + text.WriteString(getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "", nil)) } } - text += "##### Your Open Pull Requests\n" + text.WriteString("##### Your Open Pull Requests\n") if yourPrs.GetTotal() == 0 { - text += "You don't have any open pull requests.\n" + text.WriteString("You don't have any open pull requests.\n") } else { - text += fmt.Sprintf("You have %v open pull requests:\n", yourPrs.GetTotal()) + fmt.Fprintf(&text, "You have %v open pull requests:\n", yourPrs.GetTotal()) for _, pr := range yourPrs.Issues { - text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "", nil) + text.WriteString(getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "", nil)) } } - text += "##### Your Assignments\n" + text.WriteString("##### Your Assignments\n") if yourAssignments.GetTotal() == 0 { - text += "You don't have any assignments.\n" + text.WriteString("You don't have any assignments.\n") } else { - text += fmt.Sprintf("You have %v assignments:\n", yourAssignments.GetTotal()) + fmt.Fprintf(&text, "You have %v assignments:\n", yourAssignments.GetTotal()) for _, assign := range yourAssignments.Issues { - text += getToDoDisplayText(baseURL, assign.GetTitle(), assign.GetHTMLURL(), "", nil) + text.WriteString(getToDoDisplayText(baseURL, assign.GetTitle(), assign.GetHTMLURL(), "", nil)) } } - return text, nil + return text.String(), nil } func (p *Plugin) HasUnreads(info *GitHubUserInfo) bool { @@ -1045,10 +1045,8 @@ func (p *Plugin) checkOrg(org string) error { return nil } - for _, configOrg := range orgList { - if configOrg == strings.ToLower(org) { - return nil - } + if slices.Contains(orgList, strings.ToLower(org)) { + return nil } return errors.Errorf("only repositories in the %v organization(s) are supported", config.GitHubOrg) @@ -1130,8 +1128,8 @@ func (p *Plugin) sendRefreshEvent(userID string) { ) } -func (s *SidebarContent) toMap() (map[string]interface{}, error) { - var m map[string]interface{} +func (s *SidebarContent) toMap() (map[string]any, error) { + var m map[string]any bytes, err := json.Marshal(&s) if err != nil { return nil, err diff --git a/server/plugin/subscriptions.go b/server/plugin/subscriptions.go index a6b7527a0..2f9f10a9d 100644 --- a/server/plugin/subscriptions.go +++ b/server/plugin/subscriptions.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "slices" "sort" "strconv" "strings" @@ -184,12 +185,7 @@ func (s *Subscription) RenderStyle() string { } func (s *Subscription) excludedRepoForSub(repo *github.Repository) bool { - for _, repository := range s.Flags.ExcludeRepository { - if repository == repo.GetFullName() { - return true - } - } - return false + return slices.Contains(s.Flags.ExcludeRepository, repo.GetFullName()) } func (p *Plugin) Subscribe(ctx context.Context, githubClient *github.Client, userID, owner, repo, channelID string, features Features, flags SubscriptionFlags) error { @@ -350,7 +346,7 @@ func (p *Plugin) GetSubscriptions() (*Subscriptions, error) { } func (p *Plugin) StoreSubscriptions(s *Subscriptions) error { - return p.store.SetAtomicWithRetries(SubscriptionsKey, func(_ []byte) (interface{}, error) { + return p.store.SetAtomicWithRetries(SubscriptionsKey, func(_ []byte) (any, error) { modifiedBytes, err := json.Marshal(s) if err != nil { return nil, errors.Wrap(err, "could not store subscriptions in KV store") diff --git a/server/plugin/template.go b/server/plugin/template.go index bb024776d..7861540fa 100644 --- a/server/plugin/template.go +++ b/server/plugin/template.go @@ -32,14 +32,16 @@ const mdCommentRegexPattern string = `()` // Note that the username, with the @ sign, is in the second capturing group. const gitHubUsernameRegexPattern string = `(^|[^_\x60[:alnum:]])(@[[:alnum:]](-?[[:alnum:]]+)*)` -var mdCommentRegex = regexp.MustCompile(mdCommentRegexPattern) -var gitHubUsernameRegex = regexp.MustCompile(gitHubUsernameRegexPattern) -var masterTemplate *template.Template -var gitHubToUsernameMappingCallback func(string) string -var showAuthorInCommitNotification bool +var ( + mdCommentRegex = regexp.MustCompile(mdCommentRegexPattern) + gitHubUsernameRegex = regexp.MustCompile(gitHubUsernameRegexPattern) + masterTemplate *template.Template + gitHubToUsernameMappingCallback func(string) string + showAuthorInCommitNotification bool +) func init() { - var funcMap = sprig.TxtFuncMap() + funcMap := sprig.TxtFuncMap() // Try to parse out email footer junk funcMap["trimBody"] = func(body string) string { @@ -93,11 +95,11 @@ func init() { funcMap["pathEscape"] = url.PathEscape // Transform multiple variables to dictionary - funcMap["dict"] = func(values ...interface{}) (map[string]interface{}, error) { + funcMap["dict"] = func(values ...any) (map[string]any, error) { if len(values)%2 != 0 { return nil, errors.New("invalid dict call, exactly one value is required for every key") } - dict := make(map[string]interface{}, len(values)/2) + dict := make(map[string]any, len(values)/2) for i := 0; i < len(values); i += 2 { key, ok := values[i].(string) if !ok { @@ -524,7 +526,7 @@ func setShowAuthorInCommitNotification(value bool) { showAuthorInCommitNotification = value } -func renderTemplate(name string, data interface{}) (string, error) { +func renderTemplate(name string, data any) (string, error) { var output bytes.Buffer t := masterTemplate.Lookup(name) if t == nil { diff --git a/server/plugin/template_test.go b/server/plugin/template_test.go index bab6bc969..9ed57f0f3 100644 --- a/server/plugin/template_test.go +++ b/server/plugin/template_test.go @@ -42,8 +42,8 @@ var pullRequest = github.PullRequest{ Number: iToP(42), HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/pull/42"), Title: sToP("Leverage git-get-head"), - CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), - UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), + CreatedAt: tToP(time.Date(2019, 0o4, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), + UpdatedAt: tToP(time.Date(2019, 0o5, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), Body: sToP(`git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases. git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases. ` + gitHubMentions + ` git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases. git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases. git-get-head gets the non-sent upstream heads inside the stashed non-cleaned applied areas, and after pruning bases to many archives, you can initialize the origin of the bases. git-get-head sounds like a great feature we should support`), } @@ -122,8 +122,8 @@ var issueWithMentions = github.Issue{ Number: iToP(1), HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), Title: sToP("Implement git-get-head"), - CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), - UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), + CreatedAt: tToP(time.Date(2019, 0o4, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), + UpdatedAt: tToP(time.Date(2019, 0o5, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), Body: sToP(`git-get-head sounds like a great feature we should support ` + gitHubMentions), } @@ -132,8 +132,8 @@ var issueWithLabelAndAssignee = github.Issue{ Number: iToP(1), HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), Title: sToP("Implement git-get-head"), - CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), - UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), + CreatedAt: tToP(time.Date(2019, 0o4, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), + UpdatedAt: tToP(time.Date(2019, 0o5, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), Body: sToP(`git-get-head sounds like a great feature we should support`), Labels: singleLabel, Assignee: &user, @@ -144,8 +144,8 @@ var issueWithMultipleLabelsAndAssignee = github.Issue{ Number: iToP(1), HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), Title: sToP("Implement git-get-head"), - CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), - UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), + CreatedAt: tToP(time.Date(2019, 0o4, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), + UpdatedAt: tToP(time.Date(2019, 0o5, 0o1, 0o2, 0o3, 0o4, 0, time.UTC)), Body: sToP(`git-get-head sounds like a great feature we should support`), Labels: labels, Assignees: []*github.User{&user, &user}, diff --git a/server/plugin/utils.go b/server/plugin/utils.go index e9b566708..a39b61edf 100644 --- a/server/plugin/utils.go +++ b/server/plugin/utils.go @@ -96,7 +96,7 @@ func encrypt(key []byte, text string) (string, error) { return "", errors.Wrap(err, "readFull was unsuccessful, check buffer size") } - cfb := cipher.NewCFBEncrypter(block, iv) + cfb := cipher.NewCFBEncrypter(block, iv) //nolint:staticcheck // CFB mode kept for backward compatibility with existing encrypted data cfb.XORKeyStream(ciphertext[aes.BlockSize:], msg) finalMsg := base64.URLEncoding.EncodeToString(ciphertext) return finalMsg, nil @@ -120,7 +120,7 @@ func decrypt(key []byte, text string) (string, error) { iv := decodedMsg[:aes.BlockSize] msg := decodedMsg[aes.BlockSize:] - cfb := cipher.NewCFBDecrypter(block, iv) + cfb := cipher.NewCFBDecrypter(block, iv) //nolint:staticcheck // CFB mode kept for backward compatibility with existing encrypted data cfb.XORKeyStream(msg, msg) unpadMsg, err := unpad(msg) @@ -150,7 +150,7 @@ func parseGitHubUsernamesFromText(text string) []string { usernames := []string{} for _, word := range strings.FieldsFunc(text, func(c rune) bool { - return !(c == '-' || c == '@' || unicode.IsLetter(c) || unicode.IsNumber(c)) + return c != '-' && c != '@' && !unicode.IsLetter(c) && !unicode.IsNumber(c) }) { if len(word) < 2 || word[0] != '@' { continue @@ -196,16 +196,6 @@ func parseFlag(flag string) string { return strings.TrimPrefix(flag, "--") } -func containsValue(arr []string, value string) bool { - for _, element := range arr { - if element == value { - return true - } - } - - return false -} - // filterLines filters lines in a string from start to end. func filterLines(s string, start, end int) (string, error) { scanner := bufio.NewScanner(strings.NewReader(s)) diff --git a/server/plugin/utils_test.go b/server/plugin/utils_test.go index 3b7f4c9b9..c4d629a1a 100644 --- a/server/plugin/utils_test.go +++ b/server/plugin/utils_test.go @@ -122,22 +122,6 @@ func TestParseFlag(t *testing.T) { } } -func TestContainsValue(t *testing.T) { - tcs := []struct { - List []string - Value string - Expected bool - }{ - {List: []string{"value1", "value2"}, Value: "value1", Expected: true}, - {List: []string{}, Value: "value1", Expected: false}, - {List: []string{"value1", "value2"}, Value: "value2", Expected: true}, - } - - for _, tc := range tcs { - assert.Equal(t, tc.Expected, containsValue(tc.List, tc.Value)) - } -} - func TestGetLineNumbers(t *testing.T) { tcs := []struct { input string diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 8bc2d54e4..90d62ac4d 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -12,6 +12,7 @@ import ( "html" "io" "net/http" + "slices" "strings" "sync" "time" @@ -59,7 +60,7 @@ type RenderConfig struct { // EventWithRenderConfig holds an event along with configuration options for // rendering. type EventWithRenderConfig struct { - Event interface{} + Event any Config RenderConfig Label string } @@ -98,7 +99,7 @@ func signBody(secret, body []byte) ([]byte, error) { // GetEventWithRenderConfig wraps any github Event into an EventWithRenderConfig // which also contains per-subscription configuration options. -func GetEventWithRenderConfig(event interface{}, sub *Subscription) *EventWithRenderConfig { +func GetEventWithRenderConfig(event any, sub *Subscription) *EventWithRenderConfig { style := "" subscriptionLabel := "" if sub != nil { @@ -550,7 +551,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { func (p *Plugin) sanitizeDescription(description string) string { if strings.Contains(description, "
") { - var policy = bluemonday.StrictPolicy() + policy := bluemonday.StrictPolicy() policy.SkipElementsContent("details") description = html.UnescapeString(policy.Sanitize(description)) } @@ -1194,14 +1195,8 @@ func (p *Plugin) handleCommentAssigneeNotification(event *github.IssueCommentEve mentionedUsernames := parseGitHubUsernamesFromText(event.GetComment().GetBody()) for _, assignee := range assignees { - usernameMentioned := false template := templateName - for _, username := range mentionedUsernames { - if username == *assignee.Login { - usernameMentioned = true - break - } - } + usernameMentioned := slices.Contains(mentionedUsernames, *assignee.Login) if usernameMentioned { switch eventType { diff --git a/server/plugin/webhook_test.go b/server/plugin/webhook_test.go index aea818d16..1f3ef60a2 100644 --- a/server/plugin/webhook_test.go +++ b/server/plugin/webhook_test.go @@ -40,7 +40,7 @@ func TestPostPushEvent(t *testing.T) { name: "No subscription found", pushEvent: GetMockPushEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -96,7 +96,7 @@ func TestPostCreateEvent(t *testing.T) { name: "No subscription found", createEvent: GetMockCreateEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -152,7 +152,7 @@ func TestPostDeleteEvent(t *testing.T) { name: "No subscription found", deleteEvent: GetMockDeleteEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -209,7 +209,7 @@ func TestPostIssueCommentEvent(t *testing.T) { name: "No subscriptions found", event: GetMockIssueCommentEvent(actionCreated, "mockBody", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -276,10 +276,10 @@ func TestSenderMutedByReceiver(t *testing.T) { userID: "user1", sender: "sender1", setup: func(mockKVStore *mocks.MockKvStore, _ *plugintest.API) { - mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok - })).Return(nil).Do(func(key string, value interface{}) { + })).Return(nil).Do(func(key string, value any) { *value.(*[]byte) = []byte("sender1,sender2") }).Times(1) }, @@ -292,10 +292,10 @@ func TestSenderMutedByReceiver(t *testing.T) { userID: "user1", sender: "sender3", setup: func(mockKVStore *mocks.MockKvStore, _ *plugintest.API) { - mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok - })).Return(nil).Do(func(key string, value interface{}) { + })).Return(nil).Do(func(key string, value any) { *value.(*[]byte) = []byte("sender1,sender2") }).Times(1) }, @@ -308,7 +308,7 @@ func TestSenderMutedByReceiver(t *testing.T) { userID: "user1", sender: "sender1", setup: func(mockKVStore *mocks.MockKvStore, mockAPI *plugintest.API) { - mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(errors.New("store error")).Times(1) @@ -345,7 +345,7 @@ func TestPostPullRequestReviewEvent(t *testing.T) { name: "No subscriptions found", event: GetMockPullRequestReviewEvent("submitted", "approved", MockRepo, false, "authorUser", "reviewerUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -409,7 +409,7 @@ func TestPostPullRequestReviewCommentEvent(t *testing.T) { name: "No subscriptions found", event: GetMockPullRequestReviewCommentEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -473,7 +473,7 @@ func TestHandleCommentMentionNotification(t *testing.T) { name: "Error getting channel details", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -483,11 +483,11 @@ func TestHandleCommentMentionNotification(t *testing.T) { name: "Error getting channel details", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("otherUserID")).Times(1) - mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -498,11 +498,11 @@ func TestHandleCommentMentionNotification(t *testing.T) { name: "Error creating post", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("otherUserID")).Times(1) - mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -515,11 +515,11 @@ func TestHandleCommentMentionNotification(t *testing.T) { name: "Successful mention notification", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("otherUserID")).Times(1) - mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -562,7 +562,7 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Author not mapped to user ID", event: GetMockIssueCommentEvent(actionCreated, "mockBody", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -572,7 +572,7 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Author has no permission to repo", event: GetMockIssueCommentEvent(actionCreated, "mockBody", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) @@ -582,7 +582,7 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Unhandled issue type", event: GetMockIssueCommentEventWithURL(actionCreated, "mockBody", "mockUser", "https://mockurl.com/unhandledType/123"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) @@ -593,15 +593,15 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Error creating post", event: GetMockIssueCommentEventWithURL(actionCreated, "mockBody", "mockUser", "https://mockurl.com/issues/123"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) - mockKVStore.EXPECT().Get("authorUserID-muted-users", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("authorUserID-muted-users", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) - mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -613,15 +613,15 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Successful notification", event: GetMockIssueCommentEventWithURL(actionCreated, "mockBody", "mockUser", "https://mockurl.com/issues/123"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) - mockKVStore.EXPECT().Get("authorUserID-muted-users", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("authorUserID-muted-users", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) - mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -661,7 +661,7 @@ func TestHandleCommentAssigneeNotification(t *testing.T) { name: "Assignee is the author", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "assigneeUser", []string{"assigneeUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -671,7 +671,7 @@ func TestHandleCommentAssigneeNotification(t *testing.T) { name: "Issue author is assignee", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "assigneeUser", []string{"issueAuthor"}), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("issueAuthor")).Times(1) @@ -681,7 +681,7 @@ func TestHandleCommentAssigneeNotification(t *testing.T) { name: "Assignee is the sender", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "mockUser", []string{"mockUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("mockUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("mockUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -691,11 +691,11 @@ func TestHandleCommentAssigneeNotification(t *testing.T) { name: "Comment mentions assignee (self-mention)", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mention @assigneeUser", "mockUser", []string{"assigneeUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) - mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -705,11 +705,11 @@ func TestHandleCommentAssigneeNotification(t *testing.T) { name: "No permission to the repo", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "mockUser", []string{"assigneeUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) - mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -745,7 +745,7 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Review requested with no repo permission", event: GetMockPullRequestEvent("review_requested", "mockRepo", true, "senderUser", "requestedReviewer", ""), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("requestedReviewer_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("requestedReviewer_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -760,11 +760,11 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Pull request closed successfully", event: GetMockPullRequestEvent(actionClosed, "mockRepo", false, "authorUser", "senderUser", ""), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("senderUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("senderUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) - mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -776,7 +776,7 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Pull request reopened with no repo permission", event: GetMockPullRequestEvent(actionReopened, "mockRepo", true, "authorUser", "senderUser", ""), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("senderUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("senderUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -791,13 +791,13 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Pull request assigned successfully", event: GetMockPullRequestEvent(actionAssigned, "mockRepo", false, "senderUser", "assigneeUser", "assigneeUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) mockAPI.On("GetDirectChannel", "assigneeUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) - mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -807,13 +807,13 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Review requested with valid user ID", event: GetMockPullRequestEvent("review_requested", "mockRepo", false, "senderUser", "requestedReviewer", ""), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("requestedReviewer_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("requestedReviewer_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("requestedUserID")).Times(1) mockAPI.On("GetDirectChannel", "requestedUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) - mockKVStore.EXPECT().Get("requestedUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get("requestedUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -861,11 +861,11 @@ func TestHandleIssueNotification(t *testing.T) { name: "issue closed successfully", event: GetMockIssuesEvent(actionClosed, MockRepo, true, "authorUser", "senderUser", ""), setup: func() { - mockKvStore.EXPECT().Get("authorUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("authorUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) - mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -875,7 +875,7 @@ func TestHandleIssueNotification(t *testing.T) { name: "issue reopened with no repo permission", event: GetMockIssuesEvent(actionReopened, MockRepo, true, "authorUser", "senderUser", ""), setup: func() { - mockKvStore.EXPECT().Get("authorUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("authorUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -890,7 +890,7 @@ func TestHandleIssueNotification(t *testing.T) { name: "issue assigned successfully", event: GetMockIssuesEvent(actionAssigned, MockRepo, false, "senderUser", "assigneeUser", "assigneeUser"), setup: func() { - mockKvStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) @@ -900,7 +900,7 @@ func TestHandleIssueNotification(t *testing.T) { name: "issue assigned with no repo permission for assignee", event: GetMockIssuesEvent(actionAssigned, MockRepo, true, "senderUser", "demoassigneeUser", "assigneeUser"), setup: func() { - mockKvStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) @@ -949,7 +949,7 @@ func TestHandlePullRequestReviewNotification(t *testing.T) { name: "review with author not mapped to user ID", event: GetMockPullRequestReviewEvent(actionSubmitted, "approved", MockRepo, false, "unknownAuthor", "reviewerUser"), setup: func() { - mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).Return(nil).Times(1) @@ -959,11 +959,11 @@ func TestHandlePullRequestReviewNotification(t *testing.T) { name: "private repo, no permission for author", event: GetMockPullRequestReviewEvent(actionSubmitted, "approved", MockRepo, true, "authorUser", "reviewerUser"), setup: func() { - mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) - mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) @@ -973,13 +973,13 @@ func TestHandlePullRequestReviewNotification(t *testing.T) { name: "successful review notification", event: GetMockPullRequestReviewEvent(actionSubmitted, "approved", MockRepo, false, "authorUser", "reviewerUser"), setup: func() { - mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val any) bool { _, ok := val.(*[]uint8) return ok })).DoAndReturn(setByteValue("authorUserID")).Times(1) mockAPI.On("GetDirectChannel", "authorUserID", "mockBotID").Return(nil, &model.AppError{Message: "error getting channel"}).Times(1) mockAPI.On("LogWarn", "Couldn't get bot's DM channel", "userID", "authorUserID", "error", "error getting channel") - mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val any) bool { _, ok := val.(**GitHubUserInfo) return ok })).Return(nil).Times(1) @@ -1011,7 +1011,7 @@ func TestPostStarEvent(t *testing.T) { name: "no subscribed channels for repository", event: GetMockStarEvent(MockRepo, MockOrg, false, MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -1021,7 +1021,7 @@ func TestPostStarEvent(t *testing.T) { name: "error creating post", event: GetMockStarEvent(MockRepo, MockOrg, false, MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1038,7 +1038,7 @@ func TestPostStarEvent(t *testing.T) { name: "successful star event notification", event: GetMockStarEvent(MockRepo, MockOrg, false, MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1076,7 +1076,7 @@ func TestPostReleaseEvent(t *testing.T) { name: "no subscribed channels for repository", event: GetMockReleaseEvent(MockRepo, MockOrg, "created", MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -1091,7 +1091,7 @@ func TestPostReleaseEvent(t *testing.T) { name: "error creating post", event: GetMockReleaseEvent(MockRepo, MockOrg, "created", MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1108,7 +1108,7 @@ func TestPostReleaseEvent(t *testing.T) { name: "successful release event notification", event: GetMockReleaseEvent(MockRepo, MockOrg, "created", MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1146,7 +1146,7 @@ func TestPostDiscussionEvent(t *testing.T) { name: "no subscribed channels for repository", event: GetMockDiscussionEvent(MockRepo, MockOrg, MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -1156,7 +1156,7 @@ func TestPostDiscussionEvent(t *testing.T) { name: "error creating discussion post", event: GetMockDiscussionEvent(MockRepo, MockOrg, MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1173,7 +1173,7 @@ func TestPostDiscussionEvent(t *testing.T) { name: "successful discussion notification", event: GetMockDiscussionEvent(MockRepo, MockOrg, MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1210,7 +1210,7 @@ func TestPostDiscussionCommentEvent(t *testing.T) { name: "no subscribed channels for repository", event: GetMockDiscussionCommentEvent(MockRepo, MockOrg, "created", MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).Return(nil).Times(1) @@ -1220,7 +1220,7 @@ func TestPostDiscussionCommentEvent(t *testing.T) { name: "error creating discussion comment post", event: GetMockDiscussionCommentEvent(MockRepo, MockOrg, "created", MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1237,7 +1237,7 @@ func TestPostDiscussionCommentEvent(t *testing.T) { name: "successful discussion comment notification", event: GetMockDiscussionCommentEvent(MockRepo, MockOrg, "created", MockSender), setup: func() { - mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ @@ -1262,10 +1262,10 @@ func TestPostDiscussionCommentEvent(t *testing.T) { } func mockSubscription(mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val any) bool { _, ok := val.(**Subscriptions) return ok - })).DoAndReturn(func(key string, value interface{}) error { + })).DoAndReturn(func(key string, value any) error { if v, ok := value.(**Subscriptions); ok { *v = GetMockSubscriptions() } @@ -1273,8 +1273,8 @@ func mockSubscription(mockKVStore *mocks.MockKvStore) { }).Times(1) } -func setupMockSubscriptions(subs map[string][]*Subscription) func(string, interface{}) error { - return func(_ string, value interface{}) error { +func setupMockSubscriptions(subs map[string][]*Subscription) func(string, any) error { + return func(_ string, value any) error { if v, ok := value.(**Subscriptions); ok { *v = &Subscriptions{ Repositories: subs, @@ -1284,8 +1284,8 @@ func setupMockSubscriptions(subs map[string][]*Subscription) func(string, interf } } -func setByteValue(data string) func(key string, value interface{}) error { - return func(key string, value interface{}) error { +func setByteValue(data string) func(key string, value any) error { + return func(key string, value any) error { if v, ok := value.(*[]byte); ok { *v = []byte(data) } @@ -1335,9 +1335,10 @@ func mockGitHubServer(user string) *httptest.Server { return } - if user == orgMember { + switch user { + case orgMember: w.WriteHeader(http.StatusNoContent) - } else if user == orgCollaborator { + case orgCollaborator: w.WriteHeader(http.StatusFound) } })) diff --git a/server/testutils/http.go b/server/testutils/http.go index beb239863..5b20ab9c1 100644 --- a/server/testutils/http.go +++ b/server/testutils/http.go @@ -34,24 +34,24 @@ type Request struct { Method string URL string Header http.Header - Body interface{} + Body any } // ExpectedResponse stores expected response basic data type ExpectedResponse struct { StatusCode int ResponseType contentType - Body interface{} + Body any } // HTTPTest encapsulates data for testing needs type HTTPTest struct { *testing.T - Encoder func(interface{}) ([]byte, error) + Encoder func(any) ([]byte, error) } // EncodeJSON encodes json data in bytes -func EncodeJSON(data interface{}) ([]byte, error) { +func EncodeJSON(data any) ([]byte, error) { if data == nil { return []byte{}, nil } @@ -64,7 +64,7 @@ func EncodeJSON(data interface{}) ([]byte, error) { } // EncodeJSON encodes json data in bytes -func EncodeString(data interface{}) ([]byte, error) { +func EncodeString(data any) ([]byte, error) { if data == nil { return []byte{}, nil }