From 54beca9c25fd8798585196a583f400442a234d7a Mon Sep 17 00:00:00 2001 From: justusbunsi Date: Tue, 12 Jul 2022 11:20:08 +0200 Subject: [PATCH] Introduce better test case structure Signed-off-by: Steven Kriegler --- internal/actions/actions_test.go | 16 +- internal/api/gitea_test.go | 402 ++++----- internal/api/main_test.go | 190 +++-- internal/api/request_validation_test.go | 57 +- internal/api/sonarqube_test.go | 260 +++--- internal/clients/sonarqube/sonarqube_test.go | 828 ++++++++++--------- internal/settings/settings_test.go | 485 +++++------ internal/webhooks/sonarqube/webhook_test.go | 58 +- 8 files changed, 1169 insertions(+), 1127 deletions(-) diff --git a/internal/actions/actions_test.go b/internal/actions/actions_test.go index 14e13c7..21eebf5 100644 --- a/internal/actions/actions_test.go +++ b/internal/actions/actions_test.go @@ -6,12 +6,14 @@ import ( "github.com/stretchr/testify/assert" ) -func TestIsValidBotCommentForInvalidComment(t *testing.T) { - assert.False(t, IsValidBotComment(""), "Undetected missing action prefix") - assert.False(t, IsValidBotComment("/sq-bot invalid-command"), "Undetected invalid bot command") - assert.False(t, IsValidBotComment("Some context with /sq-bot review within"), "Incorrect bot prefix detected inside random comment") -} +func TestIsValidBotComment(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + assert.True(t, IsValidBotComment("/sq-bot review"), "Correct bot comment not recognized") + }) -func TestIsValidBotCommentForValidComment(t *testing.T) { - assert.True(t, IsValidBotComment("/sq-bot review"), "Correct bot comment not recognized") + t.Run("Invalid", func(t *testing.T) { + assert.False(t, IsValidBotComment(""), "Undetected missing action prefix") + assert.False(t, IsValidBotComment("/sq-bot invalid-command"), "Undetected invalid bot command") + assert.False(t, IsValidBotComment("Some context with /sq-bot review within"), "Incorrect bot prefix detected inside random comment") + }) } diff --git a/internal/api/gitea_test.go b/internal/api/gitea_test.go index e82b05b..9702ae0 100644 --- a/internal/api/gitea_test.go +++ b/internal/api/gitea_test.go @@ -13,226 +13,230 @@ import ( "github.com/stretchr/testify/assert" ) -func withValidGiteaCommentRequestData(t *testing.T, jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc) { - webhookHandler := NewGiteaWebhookHandler(new(GiteaSdkMock), new(SQSdkMock)) +func TestHandleGiteaCommentWebhook(t *testing.T) { + withValidRequestData := func(t *testing.T, jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc) { + webhookHandler := NewGiteaWebhookHandler(new(GiteaSdkMock), new(SQSdkMock)) - req, err := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer(jsonBody)) - if err != nil { - t.Fatal(err) + req, err := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer(jsonBody)) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + status, response := webhookHandler.HandleComment(r) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + io.WriteString(w, fmt.Sprintf(`{"message": "%s"}`, response)) + }) + + return req, rr, handler } - rr := httptest.NewRecorder() - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - status, response := webhookHandler.HandleComment(r) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - io.WriteString(w, fmt.Sprintf(`{"message": "%s"}`, response)) + t.Run("On success", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + Template: "PR-%d", + } + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "gitea-sonarqube-bot", + }, + Gitea: settings.GiteaRepository{ + Owner: "test-user", + Name: "gitea-sonarqube-bot", + }, + }, + } + req, rr, handler := withValidRequestData(t, []byte(`{"action":"created","issue":{"id":1,"url":"http://localhost:3000/api/v1/repos/test-user/gitea-sonarqube-bot/issues/1","html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"title":"„README.md“ ändern","body":"","ref":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:57:29Z","closed_at":null,"due_date":null,"pull_request":{"merged":false,"merged_at":null},"repository":{"id":1,"name":"gitea-sonarqube-bot","owner":"test-user","full_name":"test-user/gitea-sonarqube-bot"}},"comment":{"id":2,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1#issuecomment-2","pull_request_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","issue_url":"","user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"body":"/sq-bot review","created_at":"2022-05-15T18:57:29Z","updated_at":"2022-05-15T18:57:29Z"},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":1,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"is_pull":true}`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) + + t.Cleanup(func() { + settings.Pattern = nil + }) }) - return req, rr, handler -} + t.Run("With invalid JSON body", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + Template: "PR-%d", + } + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "gitea-sonarqube-bot", + }, + }, + } -func withValidGiteaSynchronizeRequestData(t *testing.T, jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc) { - webhookHandler := NewGiteaWebhookHandler(new(GiteaSdkMock), new(SQSdkMock)) + req, rr, handler := withValidRequestData(t, []byte(`{ "action": ["non-string-value-for-action"] }`)) + handler.ServeHTTP(rr, req) - req, err := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer(jsonBody)) - if err != nil { - t.Fatal(err) - } + assert.Equal(t, http.StatusUnprocessableEntity, rr.Code) + assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String()) - rr := httptest.NewRecorder() - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - status, response := webhookHandler.HandleSynchronize(r) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - io.WriteString(w, fmt.Sprintf(`{"message": "%s"}`, response)) + t.Cleanup(func() { + settings.Pattern = nil + }) }) - return req, rr, handler -} - -func TestHandleGiteaCommentWebhookSuccess(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - Template: "PR-%d", - } - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "gitea-sonarqube-bot", + t.Run("With invalid signature", func(t *testing.T) { + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "gitea-comment-test-webhook", }, - Gitea: settings.GiteaRepository{ - Owner: "test-user", - Name: "gitea-sonarqube-bot", + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "pr-bot", + }, }, - }, - } - req, rr, handler := withValidGiteaCommentRequestData(t, []byte(`{"action":"created","issue":{"id":1,"url":"http://localhost:3000/api/v1/repos/test-user/gitea-sonarqube-bot/issues/1","html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"title":"„README.md“ ändern","body":"","ref":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:57:29Z","closed_at":null,"due_date":null,"pull_request":{"merged":false,"merged_at":null},"repository":{"id":1,"name":"gitea-sonarqube-bot","owner":"test-user","full_name":"test-user/gitea-sonarqube-bot"}},"comment":{"id":2,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1#issuecomment-2","pull_request_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","issue_url":"","user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"body":"/sq-bot review","created_at":"2022-05-15T18:57:29Z","updated_at":"2022-05-15T18:57:29Z"},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":1,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"is_pull":true}`)) - handler.ServeHTTP(rr, req) + } + req, rr, handler := withValidRequestData(t, []byte(`{"action":"created","issue":{"id":1,"url":"http://localhost:3000/api/v1/repos/test-user/gitea-sonarqube-bot/issues/1","html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"title":"„README.md“ ändern","body":"","ref":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:57:29Z","closed_at":null,"due_date":null,"pull_request":{"merged":false,"merged_at":null},"repository":{"id":1,"name":"gitea-sonarqube-bot","owner":"test-user","full_name":"test-user/gitea-sonarqube-bot"}},"comment":{"id":2,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1#issuecomment-2","pull_request_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","issue_url":"","user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"body":"/sq-bot review","created_at":"2022-05-15T18:57:29Z","updated_at":"2022-05-15T18:57:29Z"},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":1,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"is_pull":true}`)) + req.Header.Set("X-Gitea-Signature", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467") + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) + assert.Equal(t, http.StatusPreconditionFailed, rr.Code) + assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String()) + }) - t.Cleanup(func() { - settings.Pattern = nil + t.Run("With ignored project", func(t *testing.T) { + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "gitea-sonarqube-bot", + }, + }, + } + req, rr, handler := withValidRequestData(t, []byte(`{"action":"created","issue":{"id":1,"url":"http://localhost:3000/api/v1/repos/test-user/gitea-sonarqube-bot/issues/1","html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"title":"„README.md“ ändern","body":"","ref":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:57:29Z","closed_at":null,"due_date":null,"pull_request":{"merged":false,"merged_at":null},"repository":{"id":1,"name":"gitea-sonarqube-bot","owner":"test-user","full_name":"test-user/gitea-sonarqube-bot"}},"comment":{"id":2,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1#issuecomment-2","pull_request_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","issue_url":"","user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"body":"/sq-bot review","created_at":"2022-05-15T18:57:29Z","updated_at":"2022-05-15T18:57:29Z"},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":1,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"is_pull":true}`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "ignore hook for non-configured project 'test-user/gitea-sonarqube-bot'"}`, rr.Body.String()) }) } -func TestHandleGiteaCommentWebhookInvalidJSONBody(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - Template: "PR-%d", +func TestHandleGiteaSynchronizeWebhook(t *testing.T) { + withValidRequestData := func(t *testing.T, jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc) { + webhookHandler := NewGiteaWebhookHandler(new(GiteaSdkMock), new(SQSdkMock)) + + req, err := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer(jsonBody)) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + status, response := webhookHandler.HandleSynchronize(r) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + io.WriteString(w, fmt.Sprintf(`{"message": "%s"}`, response)) + }) + + return req, rr, handler } - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "gitea-sonarqube-bot", + + t.Run("On success", func(t *testing.T) { + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "", }, - }, - } + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "gitea-sonarqube-bot", + }, + Gitea: settings.GiteaRepository{ + Owner: "test-user", + Name: "gitea-sonarqube-bot", + }, + }, + } + req, rr, handler := withValidRequestData(t, []byte(`{"action":"opened","number":1,"pull_request":{"id":1,"url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"title":"„README.md“ ändern","body":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","diff_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.diff","patch_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.patch","mergeable":true,"merged":false,"merged_at":null,"merge_commit_sha":null,"merged_by":null,"base":{"label":"main","ref":"main","sha":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"head":{"label":"test-user-patch-1","ref":"test-user-patch-1","sha":"4d3f126f7f6b76c01187a06ec704a8a3055591de","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"merge_base":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","due_date":null,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:46:19Z","closed_at":null},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"review":null}`)) + handler.ServeHTTP(rr, req) - req, rr, handler := withValidGiteaCommentRequestData(t, []byte(`{ "action": ["non-string-value-for-action"] }`)) - handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) + }) - assert.Equal(t, http.StatusUnprocessableEntity, rr.Code) - assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String()) + t.Run("With invalid JSON body", func(t *testing.T) { + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "gitea-sonarqube-bot", + }, + }, + } - t.Cleanup(func() { - settings.Pattern = nil + req, rr, handler := withValidRequestData(t, []byte(`{ "action": ["non-string-value-for-action"] }`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusUnprocessableEntity, rr.Code) + assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String()) + }) + + t.Run("With invalid signature", func(t *testing.T) { + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "gitea-synchronize-test-webhook", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "pr-bot", + }, + }, + } + req, rr, handler := withValidRequestData(t, []byte(`{"action":"opened","number":1,"pull_request":{"id":1,"url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"title":"„README.md“ ändern","body":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","diff_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.diff","patch_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.patch","mergeable":true,"merged":false,"merged_at":null,"merge_commit_sha":null,"merged_by":null,"base":{"label":"main","ref":"main","sha":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"head":{"label":"test-user-patch-1","ref":"test-user-patch-1","sha":"4d3f126f7f6b76c01187a06ec704a8a3055591de","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"merge_base":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","due_date":null,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:46:19Z","closed_at":null},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"review":null}`)) + req.Header.Set("X-Gitea-Signature", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467") + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusPreconditionFailed, rr.Code) + assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String()) + }) + + t.Run("With ignored project", func(t *testing.T) { + settings.Gitea = settings.GiteaConfig{ + Webhook: &settings.Webhook{ + Secret: "", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "gitea-sonarqube-bot", + }, + }, + } + req, rr, handler := withValidRequestData(t, []byte(`{"action":"opened","number":1,"pull_request":{"id":1,"url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"title":"„README.md“ ändern","body":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","diff_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.diff","patch_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.patch","mergeable":true,"merged":false,"merged_at":null,"merge_commit_sha":null,"merged_by":null,"base":{"label":"main","ref":"main","sha":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"head":{"label":"test-user-patch-1","ref":"test-user-patch-1","sha":"4d3f126f7f6b76c01187a06ec704a8a3055591de","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"merge_base":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","due_date":null,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:46:19Z","closed_at":null},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"review":null}`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "ignore hook for non-configured project 'test-user/gitea-sonarqube-bot'"}`, rr.Body.String()) }) } - -func TestHandleGiteaCommentInvalidWebhookSignature(t *testing.T) { - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "gitea-comment-test-webhook", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "pr-bot", - }, - }, - } - req, rr, handler := withValidGiteaCommentRequestData(t, []byte(`{"action":"created","issue":{"id":1,"url":"http://localhost:3000/api/v1/repos/test-user/gitea-sonarqube-bot/issues/1","html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"title":"„README.md“ ändern","body":"","ref":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:57:29Z","closed_at":null,"due_date":null,"pull_request":{"merged":false,"merged_at":null},"repository":{"id":1,"name":"gitea-sonarqube-bot","owner":"test-user","full_name":"test-user/gitea-sonarqube-bot"}},"comment":{"id":2,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1#issuecomment-2","pull_request_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","issue_url":"","user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"body":"/sq-bot review","created_at":"2022-05-15T18:57:29Z","updated_at":"2022-05-15T18:57:29Z"},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":1,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"is_pull":true}`)) - req.Header.Set("X-Gitea-Signature", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467") - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusPreconditionFailed, rr.Code) - assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String()) -} - -func TestHandleGiteaCommentWebhookIgnoredProject(t *testing.T) { - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "gitea-sonarqube-bot", - }, - }, - } - req, rr, handler := withValidGiteaCommentRequestData(t, []byte(`{"action":"created","issue":{"id":1,"url":"http://localhost:3000/api/v1/repos/test-user/gitea-sonarqube-bot/issues/1","html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"title":"„README.md“ ändern","body":"","ref":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:57:29Z","closed_at":null,"due_date":null,"pull_request":{"merged":false,"merged_at":null},"repository":{"id":1,"name":"gitea-sonarqube-bot","owner":"test-user","full_name":"test-user/gitea-sonarqube-bot"}},"comment":{"id":2,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1#issuecomment-2","pull_request_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","issue_url":"","user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"original_author":"","original_author_id":0,"body":"/sq-bot review","created_at":"2022-05-15T18:57:29Z","updated_at":"2022-05-15T18:57:29Z"},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":1,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"is_pull":true}`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "ignore hook for non-configured project 'test-user/gitea-sonarqube-bot'"}`, rr.Body.String()) -} - -func TestHandleGiteaSynchronizeWebhookSuccess(t *testing.T) { - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "gitea-sonarqube-bot", - }, - Gitea: settings.GiteaRepository{ - Owner: "test-user", - Name: "gitea-sonarqube-bot", - }, - }, - } - req, rr, handler := withValidGiteaSynchronizeRequestData(t, []byte(`{"action":"opened","number":1,"pull_request":{"id":1,"url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"title":"„README.md“ ändern","body":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","diff_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.diff","patch_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.patch","mergeable":true,"merged":false,"merged_at":null,"merge_commit_sha":null,"merged_by":null,"base":{"label":"main","ref":"main","sha":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"head":{"label":"test-user-patch-1","ref":"test-user-patch-1","sha":"4d3f126f7f6b76c01187a06ec704a8a3055591de","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"merge_base":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","due_date":null,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:46:19Z","closed_at":null},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"review":null}`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) -} - -func TestHandleGiteaSynchronizeWebhookInvalidJSONBody(t *testing.T) { - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "gitea-sonarqube-bot", - }, - }, - } - - req, rr, handler := withValidGiteaSynchronizeRequestData(t, []byte(`{ "action": ["non-string-value-for-action"] }`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusUnprocessableEntity, rr.Code) - assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String()) -} - -func TestHandleGiteaSynchronizeInvalidWebhookSignature(t *testing.T) { - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "gitea-synchronize-test-webhook", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "pr-bot", - }, - }, - } - req, rr, handler := withValidGiteaSynchronizeRequestData(t, []byte(`{"action":"opened","number":1,"pull_request":{"id":1,"url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"title":"„README.md“ ändern","body":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","diff_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.diff","patch_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.patch","mergeable":true,"merged":false,"merged_at":null,"merge_commit_sha":null,"merged_by":null,"base":{"label":"main","ref":"main","sha":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"head":{"label":"test-user-patch-1","ref":"test-user-patch-1","sha":"4d3f126f7f6b76c01187a06ec704a8a3055591de","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"merge_base":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","due_date":null,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:46:19Z","closed_at":null},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"review":null}`)) - req.Header.Set("X-Gitea-Signature", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467") - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusPreconditionFailed, rr.Code) - assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String()) -} - -func TestHandleGiteaSynchronizeWebhookIgnoredProject(t *testing.T) { - settings.Gitea = settings.GiteaConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "gitea-sonarqube-bot", - }, - }, - } - req, rr, handler := withValidGiteaSynchronizeRequestData(t, []byte(`{"action":"opened","number":1,"pull_request":{"id":1,"url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","number":1,"user":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"title":"„README.md“ ändern","body":"","labels":[],"milestone":null,"assignee":null,"assignees":null,"state":"open","is_locked":false,"comments":0,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1","diff_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.diff","patch_url":"http://localhost:3000/test-user/gitea-sonarqube-bot/pulls/1.patch","mergeable":true,"merged":false,"merged_at":null,"merge_commit_sha":null,"merged_by":null,"base":{"label":"main","ref":"main","sha":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"head":{"label":"test-user-patch-1","ref":"test-user-patch-1","sha":"4d3f126f7f6b76c01187a06ec704a8a3055591de","repo_id":1,"repo":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}},"merge_base":"2e5c9f7fe85fd8fb6019b3dd299744e0afce076b","due_date":null,"created_at":"2022-05-15T18:46:19Z","updated_at":"2022-05-15T18:46:19Z","closed_at":null},"repository":{"id":1,"owner":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"name":"gitea-sonarqube-bot","full_name":"test-user/gitea-sonarqube-bot","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":110,"html_url":"http://localhost:3000/test-user/gitea-sonarqube-bot","ssh_url":"git@localhost:test-user/gitea-sonarqube-bot.git","clone_url":"http://localhost:3000/test-user/gitea-sonarqube-bot.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"main","archived":false,"created_at":"2022-05-15T18:45:46Z","updated_at":"2022-05-15T18:46:09Z","permissions":{"admin":true,"push":true,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":true,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null},"sender":{"id":1,"login":"test-user","full_name":"","email":"a@b.c","avatar_url":"http://localhost:3000/avatar/5d60d4e28066df254d5452f92c910092","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2022-05-15T18:42:54Z","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"test-user"},"review":null}`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "ignore hook for non-configured project 'test-user/gitea-sonarqube-bot'"}`, rr.Body.String()) -} diff --git a/internal/api/main_test.go b/internal/api/main_test.go index 6fdd816..ecac2a7 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -93,105 +93,115 @@ func TestMain(m *testing.M) { } func TestNonAPIRoutes(t *testing.T) { - router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) + t.Run("favicon", func(t *testing.T) { + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/favicon.ico", nil) - router.Engine.ServeHTTP(w, req) - assert.Equal(t, http.StatusNoContent, w.Code) + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/favicon.ico", nil) + router.Engine.ServeHTTP(w, req) + assert.Equal(t, http.StatusNoContent, w.Code) + }) - w = httptest.NewRecorder() - req, _ = http.NewRequest("GET", "/ping", nil) - router.Engine.ServeHTTP(w, req) - assert.Equal(t, http.StatusOK, w.Code) + t.Run("ping", func(t *testing.T) { + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/ping", nil) + router.Engine.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + }) } -func TestSonarQubeAPIRouteMissingProjectHeader(t *testing.T) { - router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) +func TestSonarQubeAPIRoute(t *testing.T) { + t.Run("Missing project header", func(t *testing.T) { + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) - router.Engine.ServeHTTP(w, req) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) + router.Engine.ServeHTTP(w, req) - assert.Equal(t, http.StatusNotFound, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("Processing", func(t *testing.T) { + sonarQubeHandlerMock := new(SonarQubeHandlerMock) + sonarQubeHandlerMock.On("Handle", mock.IsType(&http.Request{})) + + router := New(new(GiteaHandlerMock), sonarQubeHandlerMock) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) + req.Header.Add("X-SonarQube-Project", "gitea-sonarqube-bot") + router.Engine.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1) + sonarQubeHandlerMock.AssertExpectations(t) + }) } -func TestSonarQubeAPIRouteProcessing(t *testing.T) { - sonarQubeHandlerMock := new(SonarQubeHandlerMock) - sonarQubeHandlerMock.On("Handle", mock.IsType(&http.Request{})) +func TestGiteaAPIRoute(t *testing.T) { + t.Run("Missing event header", func(t *testing.T) { + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) - router := New(new(GiteaHandlerMock), sonarQubeHandlerMock) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) + router.Engine.ServeHTTP(w, req) - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) - req.Header.Add("X-SonarQube-Project", "gitea-sonarqube-bot") - router.Engine.ServeHTTP(w, req) + assert.Equal(t, http.StatusNotFound, w.Code) + }) - assert.Equal(t, http.StatusOK, w.Code) - sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1) - sonarQubeHandlerMock.AssertExpectations(t) -} - -func TestGiteaAPIRouteMissingEventHeader(t *testing.T) { - router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) - - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) - router.Engine.ServeHTTP(w, req) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) { - giteaHandlerMock := new(GiteaHandlerMock) - giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil) - giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe() - - router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) - - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) - req.Header.Add("X-Gitea-Event", "pull_request") - router.Engine.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1) - giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0) - giteaHandlerMock.AssertExpectations(t) -} - -func TestGiteaAPIRouteCommentProcessing(t *testing.T) { - giteaHandlerMock := new(GiteaHandlerMock) - giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe() - giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil) - - router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) - - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) - req.Header.Add("X-Gitea-Event", "issue_comment") - router.Engine.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0) - giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 1) - giteaHandlerMock.AssertExpectations(t) -} - -func TestGiteaAPIRouteUnknownEvent(t *testing.T) { - giteaHandlerMock := new(GiteaHandlerMock) - giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe() - giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe() - - router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) - - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) - req.Header.Add("X-Gitea-Event", "unknown") - router.Engine.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0) - giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0) - giteaHandlerMock.AssertExpectations(t) + t.Run("Processing synchronize", func(t *testing.T) { + giteaHandlerMock := new(GiteaHandlerMock) + giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil) + giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe() + + router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) + req.Header.Add("X-Gitea-Event", "pull_request") + router.Engine.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1) + giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0) + giteaHandlerMock.AssertExpectations(t) + }) + + t.Run("Processing comment", func(t *testing.T) { + giteaHandlerMock := new(GiteaHandlerMock) + giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe() + giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil) + + router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) + req.Header.Add("X-Gitea-Event", "issue_comment") + router.Engine.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0) + giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 1) + giteaHandlerMock.AssertExpectations(t) + }) + + t.Run("Unknown event", func(t *testing.T) { + giteaHandlerMock := new(GiteaHandlerMock) + giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe() + giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe() + + router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) + req.Header.Add("X-Gitea-Event", "unknown") + router.Engine.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0) + giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0) + giteaHandlerMock.AssertExpectations(t) + }) } diff --git a/internal/api/request_validation_test.go b/internal/api/request_validation_test.go index c5e241d..2d25d3a 100644 --- a/internal/api/request_validation_test.go +++ b/internal/api/request_validation_test.go @@ -10,37 +10,38 @@ func getRequestData() []byte { return []byte(`{"serverUrl":"https://example.com","status":"SUCCESS","analysedAt":"2022-05-15T16:45:31+0000","revision":"378080777919s07657a07f7a3e2d05dc75f64edd","changedAt":"2022-05-15T16:41:39+0000","project":{"key":"gitea-sonarqube-bot","name":"Gitea SonarQube Bot","url":"https://example.com/dashboard?id=gitea-sonarqube-bot"},"branch":{"name":"PR-1822","type":"PULL_REQUEST","isMain":false,"url":"https://example.com/dashboard?id=gitea-sonarqube-bot&pullRequest=PR-1822"},"qualityGate":{"name":"GiteaSonarQubeBot","status":"OK","conditions":[{"metric":"new_reliability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_maintainability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_hotspots_reviewed","operator":"LESS_THAN","status":"OK","errorThreshold":"100"}]},"properties":{"sonar.analysis.sqbot":"378080777919s07657a07f7a3e2d05dc75f64edd"}}`) } -func TestIsValidWebhookSuccess(t *testing.T) { - actual, _ := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component") - assert.True(t, actual, "Expected successful webhook signature validation") -} +func TestIsValidWebhook(t *testing.T) { + t.Run("Success", func(t *testing.T) { + actual, _ := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component") + assert.True(t, actual, "Expected successful webhook signature validation") + }) -func TestIsValidWebhookNothingConfiguredOrProvidedSuccess(t *testing.T) { - actual, _ := isValidWebhook(getRequestData(), "", "", "test-component") - assert.True(t, actual, "Webhook signature validation not skipped") -} + t.Run("Nothing configured or provided", func(t *testing.T) { + actual, _ := isValidWebhook(getRequestData(), "", "", "test-component") + assert.True(t, actual, "Webhook signature validation not skipped") + }) -func TestIsValidWebhookSignatureDecodingFailure(t *testing.T) { - actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "invalid-signature", "test-component") - assert.False(t, actual) - assert.EqualError(t, err, "Error decoding signature for test-component webhook.", "Undetected signature encoding error") -} + t.Run("Signature decoding error", func(t *testing.T) { + actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "invalid-signature", "test-component") + assert.False(t, actual) + assert.EqualError(t, err, "Error decoding signature for test-component webhook.", "Undetected signature encoding error") + }) -func TestIsValidWebhookSignatureMismatchFailure(t *testing.T) { - actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "fde6a666b7a1a46c27efb1961c17b46b6cf7aa13db5560e5ac95e801a18a92f3", "test-component") - assert.False(t, actual) - assert.EqualError(t, err, "Signature header does not match the received test-component webhook content. Request rejected.", "Undetected signature mismatch") - // assert.EqualError(t, err, "Signature header received but no test-component webhook secret configured. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (1)") -} + t.Run("Signature mismatch", func(t *testing.T) { + actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "fde6a666b7a1a46c27efb1961c17b46b6cf7aa13db5560e5ac95e801a18a92f3", "test-component") + assert.False(t, actual) + assert.EqualError(t, err, "Signature header does not match the received test-component webhook content. Request rejected.", "Undetected signature mismatch") + }) -func TestIsValidWebhookEmptySecretConfigurationFailure(t *testing.T) { - actual, err := isValidWebhook(getRequestData(), "", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component") - assert.False(t, actual) - assert.EqualError(t, err, "Signature header received but no test-component webhook secret configured. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (1)") -} + t.Run("Empty secret configuration", func(t *testing.T) { + actual, err := isValidWebhook(getRequestData(), "", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component") + assert.False(t, actual) + assert.EqualError(t, err, "Signature header received but no test-component webhook secret configured. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (1)") + }) -func TestIsValidWebhookEmptySignatureConfigurationFailure(t *testing.T) { - actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "", "test-component") - assert.False(t, actual) - assert.EqualError(t, err, "test-component webhook secret configured but no signature header received. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (2)") + t.Run("Empty signature configuration", func(t *testing.T) { + actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "", "test-component") + assert.False(t, actual) + assert.EqualError(t, err, "test-component webhook secret configured but no signature header received. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (2)") + }) } diff --git a/internal/api/sonarqube_test.go b/internal/api/sonarqube_test.go index 8f71c6b..e79e517 100644 --- a/internal/api/sonarqube_test.go +++ b/internal/api/sonarqube_test.go @@ -34,137 +34,139 @@ func withValidSonarQubeRequestData(t *testing.T, jsonBody []byte) (*http.Request return req, rr, handler } -func TestHandleSonarQubeWebhookProjectMapped(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - } - settings.SonarQube = settings.SonarQubeConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "pr-bot", +func TestHandleSonarQubeWebhook(t *testing.T) { + t.Run("With mapped Project", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + } + settings.SonarQube = settings.SonarQubeConfig{ + Webhook: &settings.Webhook{ + Secret: "", }, - }, - } - req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) - handler.ServeHTTP(rr, req) + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "pr-bot", + }, + }, + } + req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) - t.Cleanup(func() { - settings.Pattern = nil - }) -} - -func TestHandleSonarQubeWebhookProjectNotMapped(t *testing.T) { - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "another-project", - }, - }, - } - req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "Project 'pr-bot' not in configured list. Request ignored."}`, rr.Body.String()) -} - -func TestHandleSonarQubeWebhookInvalidJSONBody(t *testing.T) { - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "pr-bot", - }, - }, - } - - req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": ["invalid-server-url-content"] }`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusUnprocessableEntity, rr.Code) - assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String()) -} - -func TestHandleSonarQubeWebhookInvalidWebhookSignature(t *testing.T) { - settings.SonarQube = settings.SonarQubeConfig{ - Webhook: &settings.Webhook{ - Secret: "sonarqube-test-webhook-secret", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "pr-bot", - }, - }, - } - req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) - req.Header.Set("X-Sonar-Webhook-HMAC-SHA256", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467") - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusPreconditionFailed, rr.Code) - assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String()) -} - -func TestHandleSonarQubeWebhookForPullRequest(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - } - settings.SonarQube = settings.SonarQubeConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "pr-bot", - }, - }, - } - - req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) - - t.Cleanup(func() { - settings.Pattern = nil - }) -} - -func TestHandleSonarQubeWebhookForBranch(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - } - settings.SonarQube = settings.SonarQubeConfig{ - Webhook: &settings.Webhook{ - Secret: "", - }, - } - settings.Projects = []settings.Project{ - { - SonarQube: struct{ Key string }{ - Key: "pr-bot", - }, - }, - } - - req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "BRANCH", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "Ignore Hook for non-PR analysis."}`, rr.Body.String()) - - t.Cleanup(func() { - settings.Pattern = nil + t.Cleanup(func() { + settings.Pattern = nil + }) + }) + + t.Run("Without mapped project", func(t *testing.T) { + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "another-project", + }, + }, + } + req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "Project 'pr-bot' not in configured list. Request ignored."}`, rr.Body.String()) + }) + + t.Run("With invalid JSON body", func(t *testing.T) { + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "pr-bot", + }, + }, + } + + req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": ["invalid-server-url-content"] }`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusUnprocessableEntity, rr.Code) + assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String()) + }) + + t.Run("With invalid webhook signature", func(t *testing.T) { + settings.SonarQube = settings.SonarQubeConfig{ + Webhook: &settings.Webhook{ + Secret: "sonarqube-test-webhook-secret", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "pr-bot", + }, + }, + } + req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) + req.Header.Set("X-Sonar-Webhook-HMAC-SHA256", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467") + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusPreconditionFailed, rr.Code) + assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String()) + }) + + t.Run("Running for Pull Request", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + } + settings.SonarQube = settings.SonarQubeConfig{ + Webhook: &settings.Webhook{ + Secret: "", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "pr-bot", + }, + }, + } + + req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) + + t.Cleanup(func() { + settings.Pattern = nil + }) + }) + + t.Run("Running for branch", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + } + settings.SonarQube = settings.SonarQubeConfig{ + Webhook: &settings.Webhook{ + Secret: "", + }, + } + settings.Projects = []settings.Project{ + { + SonarQube: struct{ Key string }{ + Key: "pr-bot", + }, + }, + } + + req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "BRANCH", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, `{"message": "Ignore Hook for non-PR analysis."}`, rr.Body.String()) + + t.Cleanup(func() { + settings.Pattern = nil + }) }) } diff --git a/internal/clients/sonarqube/sonarqube_test.go b/internal/clients/sonarqube/sonarqube_test.go index 928fce1..28a0616 100644 --- a/internal/clients/sonarqube/sonarqube_test.go +++ b/internal/clients/sonarqube/sonarqube_test.go @@ -28,29 +28,31 @@ func (c *ClientMock) Do(req *http.Request) (*http.Response, error) { }, c.responseError } -func TestParsePRIndexSuccess(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - } +func TestParsePRIndex(t *testing.T) { + t.Run("Success", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + } - actual, _ := ParsePRIndex("PR-1337") - assert.Equal(t, 1337, actual, "PR index parsing is broken") + actual, _ := ParsePRIndex("PR-1337") + assert.Equal(t, 1337, actual, "PR index parsing is broken") - t.Cleanup(func() { - settings.Pattern = nil + t.Cleanup(func() { + settings.Pattern = nil + }) }) -} -func TestParsePRIndexNonIntegerFailure(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - } + t.Run("No integer value", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + } - _, err := ParsePRIndex("PR-invalid") - assert.EqualErrorf(t, err, "branch name 'PR-invalid' does not match regex '^PR-(\\d+)$'", "Integer parsing succeeds unexpectedly") + _, err := ParsePRIndex("PR-invalid") + assert.EqualErrorf(t, err, "branch name 'PR-invalid' does not match regex '^PR-(\\d+)$'", "Integer parsing succeeds unexpectedly") - t.Cleanup(func() { - settings.Pattern = nil + t.Cleanup(func() { + settings.Pattern = nil + }) }) } @@ -66,16 +68,14 @@ func TestPRNameFromIndex(t *testing.T) { }) } -func TestGetRenderedQualityGateSuccess(t *testing.T) { - actual := GetRenderedQualityGate("OK") +func TestGetRenderedQualityGate(t *testing.T) { + t.Run("Passed", func(t *testing.T) { + assert.Contains(t, GetRenderedQualityGate("OK"), ":white_check_mark:", "Undetected successful quality gate during status rendering") + }) - assert.Contains(t, actual, ":white_check_mark:", "Undetected successful quality gate during status rendering") -} - -func TestGetRenderedQualityGateFailure(t *testing.T) { - actual := GetRenderedQualityGate("ERROR") - - assert.Contains(t, actual, ":x:", "Undetected failed quality gate during status rendering") + t.Run("Failed", func(t *testing.T) { + assert.Contains(t, GetRenderedQualityGate("ERROR"), ":x:", "Undetected failed quality gate during status rendering") + }) } func TestGetPullRequestUrl(t *testing.T) { @@ -94,435 +94,445 @@ func TestGetPullRequestUrl(t *testing.T) { }) } -func TestRetrieveDataFromApiSuccess(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) +func TestRetrieveDataFromApi(t *testing.T) { + t.Run("Success", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + } + + request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) + wrapper := &PullsResponse{} + err := retrieveDataFromApi(sdk, request, wrapper) + + assert.Nil(t, err, "Successful data retrieval broken and throws error") + assert.Equal(t, "Basic dGVzdC10b2tlbjo=", request.Header.Get("Authorization"), "Authorization header not set") + assert.Equal(t, "PR-1", wrapper.PullRequests[0].Key, "Unmarshallowing into wrapper broken") }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - } - request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) - wrapper := &PullsResponse{} - err := retrieveDataFromApi(sdk, request, wrapper) + t.Run("Internal error", func(t *testing.T) { + expected := fmt.Errorf("This error indicates an error while performing the request") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), + recoder: httptest.NewRecorder(), + responseError: expected, + }, + bodyReader: io.ReadAll, + } - assert.Nil(t, err, "Successful data retrieval broken and throws error") - assert.Equal(t, "Basic dGVzdC10b2tlbjo=", request.Header.Get("Authorization"), "Authorization header not set") - assert.Equal(t, "PR-1", wrapper.PullRequests[0].Key, "Unmarshallowing into wrapper broken") -} + request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) + err := retrieveDataFromApi(sdk, request, &PullsResponse{}) -func TestRetrieveDataFromApiRequestError(t *testing.T) { - expected := fmt.Errorf("This error indicates an error while performing the request") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), - recoder: httptest.NewRecorder(), - responseError: expected, - }, - bodyReader: io.ReadAll, - } - - request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) - err := retrieveDataFromApi(sdk, request, &PullsResponse{}) - - assert.ErrorIs(t, err, expected, "Undetected request performing error") -} - -func TestRetrieveDataFromApiUnauthorized(t *testing.T) { - recorder := httptest.NewRecorder() - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - recorder.Code = http.StatusUnauthorized + assert.ErrorIs(t, err, expected, "Undetected request performing error") }) - sdk := &SonarQubeSdk{ - token: "simulated-invalid-token", - client: &ClientMock{ - handler: handler, - recoder: recorder, - responseError: nil, - }, - bodyReader: io.ReadAll, - } - request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) - err := retrieveDataFromApi(sdk, request, &PullsResponse{}) + t.Run("Unauthorized", func(t *testing.T) { + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + recorder.Code = http.StatusUnauthorized + }) + sdk := &SonarQubeSdk{ + token: "simulated-invalid-token", + client: &ClientMock{ + handler: handler, + recoder: recorder, + responseError: nil, + }, + bodyReader: io.ReadAll, + } - assert.Errorf(t, err, "missing or invalid API token", "Undetected unauthorized error") -} + request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) + err := retrieveDataFromApi(sdk, request, &PullsResponse{}) -func TestRetrieveDataFromApiBodyReadError(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + assert.Errorf(t, err, "missing or invalid API token", "Undetected unauthorized error") }) - expected := fmt.Errorf("Error reading body content") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: func(r io.Reader) ([]byte, error) { - return []byte(``), expected - }, - } - request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) - err := retrieveDataFromApi(sdk, request, &PullsResponse{}) + t.Run("Body read error", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + expected := fmt.Errorf("Error reading body content") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: func(r io.Reader) ([]byte, error) { + return []byte(``), expected + }, + } - assert.ErrorIs(t, err, expected, "Undetected body processing error") -} + request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) + err := retrieveDataFromApi(sdk, request, &PullsResponse{}) -func TestRetrieveDataFromApiBodyUnmarshalError(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullReq`)) + assert.ErrorIs(t, err, expected, "Undetected body processing error") }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - } - request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) - err := retrieveDataFromApi(sdk, request, &PullsResponse{}) + t.Run("Unmarshal error", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullReq`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + } - assert.Errorf(t, err, "unexpected end of JSON input", "Undetected body unmarshal error") -} + request := httptest.NewRequest(http.MethodGet, "http://example.com", nil) + err := retrieveDataFromApi(sdk, request, &PullsResponse{}) -func TestFetchPullRequestsSuccess(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) - }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - - actual, err := sdk.fetchPullRequests("test-project") - - assert.Nil(t, err, "Successful data retrieval broken and throws error") - assert.IsType(t, &PullsResponse{}, actual, "Happy path broken") -} - -func TestFetchPullRequestsRequestBuildingFailure(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) - }) - expected := fmt.Errorf("Some simulated error") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return nil, expected - }, - } - - _, err := sdk.fetchPullRequests("test-project") - - assert.Equal(t, expected, err, "Unexpected error instance returned") -} - -func TestFetchPullRequestsRequestError(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) - }) - expected := fmt.Errorf("Some simulated error") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: expected, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - - _, err := sdk.fetchPullRequests("test-project") - - assert.Equal(t, expected, err) -} - -func TestFetchPullRequestsErrorsInResponse(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"errors":[{"msg":"Project 'test-project' not found"}]}`)) - }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - - _, err := sdk.fetchPullRequests("test-project") - - assert.Errorf(t, err, "Project 'test-project' not found", "Response error parsing broken") -} - -func TestGetPullRequestSuccess(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - Template: "PR-%d", - } - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) - }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - - actual, err := sdk.GetPullRequest("test-project", 1) - - assert.Nil(t, err, "Successful data retrieval broken and throws error") - assert.IsType(t, &PullRequest{}, actual, "Happy path broken") - - t.Cleanup(func() { - settings.Pattern = nil + assert.Errorf(t, err, "unexpected end of JSON input", "Undetected body unmarshal error") }) } -func TestGetPullRequestFetchError(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) +func TestFetchPullRequests(t *testing.T) { + t.Run("Success", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } + + actual, err := sdk.fetchPullRequests("test-project") + + assert.Nil(t, err, "Successful data retrieval broken and throws error") + assert.IsType(t, &PullsResponse{}, actual, "Happy path broken") }) - expected := fmt.Errorf("Some simulated error") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: expected, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - _, err := sdk.GetPullRequest("test-project", 1) + t.Run("Building failure", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + expected := fmt.Errorf("Some simulated error") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return nil, expected + }, + } - assert.Errorf(t, err, "fetching pull requests failed", "Incorrect edge case is throwing errors") - assert.Errorf(t, err, "Some simulated error", "Unexpected error cause") -} + _, err := sdk.fetchPullRequests("test-project") -func TestGetPullRequestUnknownPR(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - Template: "PR-%d", - } - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + assert.Equal(t, expected, err, "Unexpected error instance returned") }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - _, err := sdk.GetPullRequest("test-project", 1337) + t.Run("Internal error", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + expected := fmt.Errorf("Some simulated error") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: expected, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } - assert.Errorf(t, err, "no pull request found with name 'PR-1337'") + _, err := sdk.fetchPullRequests("test-project") - t.Cleanup(func() { - settings.Pattern = nil + assert.Equal(t, expected, err) + }) + + t.Run("Errors in response", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"errors":[{"msg":"Project 'test-project' not found"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } + + _, err := sdk.fetchPullRequests("test-project") + + assert.Errorf(t, err, "Project 'test-project' not found", "Response error parsing broken") }) } -func TestGetMeasuresSuccess(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) +func TestGetPullRequest(t *testing.T) { + t.Run("Success", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + Template: "PR-%d", + } + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } + + actual, err := sdk.GetPullRequest("test-project", 1) + + assert.Nil(t, err, "Successful data retrieval broken and throws error") + assert.IsType(t, &PullRequest{}, actual, "Happy path broken") + + t.Cleanup(func() { + settings.Pattern = nil + }) }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - actual, err := sdk.GetMeasures("test-project", "PR-1") + t.Run("Fetch error", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + expected := fmt.Errorf("Some simulated error") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: expected, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } - assert.Nil(t, err, "Successful data retrieval broken and throws error") - assert.IsType(t, &MeasuresResponse{}, actual, "Happy path broken") + _, err := sdk.GetPullRequest("test-project", 1) + + assert.Errorf(t, err, "fetching pull requests failed", "Incorrect edge case is throwing errors") + assert.Errorf(t, err, "Some simulated error", "Unexpected error cause") + }) + + t.Run("Unknown PR", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + Template: "PR-%d", + } + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } + + _, err := sdk.GetPullRequest("test-project", 1337) + + assert.Errorf(t, err, "no pull request found with name 'PR-1337'") + + t.Cleanup(func() { + settings.Pattern = nil + }) + }) } -func TestGetMeasuresRequestBuildingFailure(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) +func TestGetMeasures(t *testing.T) { + t.Run("Success", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } + + actual, err := sdk.GetMeasures("test-project", "PR-1") + + assert.Nil(t, err, "Successful data retrieval broken and throws error") + assert.IsType(t, &MeasuresResponse{}, actual, "Happy path broken") }) - expected := fmt.Errorf("Some simulated error") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return nil, expected - }, - } - _, err := sdk.GetMeasures("test-project", "PR-1") + t.Run("Building failure", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) + }) + expected := fmt.Errorf("Some simulated error") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return nil, expected + }, + } - assert.Equal(t, expected, err, "Unexpected error instance returned") + _, err := sdk.GetMeasures("test-project", "PR-1") + + assert.Equal(t, expected, err, "Unexpected error instance returned") + }) + + t.Run("Request error", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) + }) + expected := fmt.Errorf("Some simulated error") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: expected, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } + + _, err := sdk.GetMeasures("test-project", "PR-1") + + assert.Equal(t, expected, err) + }) + + t.Run("Errors in response", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"errors":[{"msg":"Component 'non-existing-project' of pull request 'PR-1' not found"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } + + _, err := sdk.GetMeasures("non-existing-project", "PR-1") + + assert.Errorf(t, err, "Component 'non-existing-project' of pull request 'PR-1' not found", "Response error parsing broken") + }) } -func TestGetMeasuresRequestError(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) - }) - expected := fmt.Errorf("Some simulated error") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: expected, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } +func TestComposeGiteaComment(t *testing.T) { + t.Run("Success", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) + }) + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return httptest.NewRequest(method, target, body), nil + }, + } - _, err := sdk.GetMeasures("test-project", "PR-1") + actual, err := sdk.ComposeGiteaComment(&CommentComposeData{ + Key: "test-project", + PRName: "PR-1", + Url: "https://sonarqube.example.com", + QualityGate: "OK", + }) - assert.Equal(t, expected, err) -} - -func TestGetMeasuresErrorsInResponse(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"errors":[{"msg":"Component 'non-existing-project' of pull request 'PR-1' not found"}]}`)) - }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - - _, err := sdk.GetMeasures("non-existing-project", "PR-1") - - assert.Errorf(t, err, "Component 'non-existing-project' of pull request 'PR-1' not found", "Response error parsing broken") -} - -func TestComposeGiteaCommentSuccess(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) - }) - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return httptest.NewRequest(method, target, body), nil - }, - } - - actual, err := sdk.ComposeGiteaComment(&CommentComposeData{ - Key: "test-project", - PRName: "PR-1", - Url: "https://sonarqube.example.com", - QualityGate: "OK", + assert.Nil(t, err, "Successful comment composing throwing errors") + assert.Contains(t, actual, ":white_check_mark:", "Happy path [Quality Gate] broken") + assert.Contains(t, actual, "| Metric | Current |", "Happy path [Metrics Header] broken") + assert.Contains(t, actual, "| Bugs | 10 |", "Happy path [Metrics Values] broken") + assert.Contains(t, actual, "https://sonarqube.example.com", "Happy path [Link] broken") + assert.Contains(t, actual, "/sq-bot review", "Happy path [Command] broken") }) - assert.Nil(t, err, "Successful comment composing throwing errors") - assert.Contains(t, actual, ":white_check_mark:", "Happy path [Quality Gate] broken") - assert.Contains(t, actual, "| Metric | Current |", "Happy path [Metrics Header] broken") - assert.Contains(t, actual, "| Bugs | 10 |", "Happy path [Metrics Values] broken") - assert.Contains(t, actual, "https://sonarqube.example.com", "Happy path [Link] broken") - assert.Contains(t, actual, "/sq-bot review", "Happy path [Command] broken") -} + t.Run("Error", func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) + }) + expected := fmt.Errorf("Expected error from GetMeasures") + sdk := &SonarQubeSdk{ + token: "test-token", + client: &ClientMock{ + handler: handler, + recoder: httptest.NewRecorder(), + responseError: nil, + }, + bodyReader: io.ReadAll, + httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { + return nil, expected + }, + } -func TestComposeGiteaCommentError(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`)) + _, err := sdk.ComposeGiteaComment(&CommentComposeData{ + Key: "test-project", + PRName: "PR-1", + Url: "https://sonarqube.example.com", + QualityGate: "OK", + }) + + assert.Errorf(t, err, expected.Error(), "Undetected error while composing comment") }) - expected := fmt.Errorf("Expected error from GetMeasures") - sdk := &SonarQubeSdk{ - token: "test-token", - client: &ClientMock{ - handler: handler, - recoder: httptest.NewRecorder(), - responseError: nil, - }, - bodyReader: io.ReadAll, - httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { - return nil, expected - }, - } - - _, err := sdk.ComposeGiteaComment(&CommentComposeData{ - Key: "test-project", - PRName: "PR-1", - Url: "https://sonarqube.example.com", - QualityGate: "OK", - }) - - assert.Errorf(t, err, expected.Error(), "Undetected error while composing comment") } func TestNew(t *testing.T) { diff --git a/internal/settings/settings_test.go b/internal/settings/settings_test.go index 9513e2e..187731d 100644 --- a/internal/settings/settings_test.go +++ b/internal/settings/settings_test.go @@ -10,8 +10,9 @@ import ( "github.com/stretchr/testify/assert" ) -var defaultConfig []byte = []byte( - `gitea: +func defaultConfig() []byte { + return []byte( + `gitea: url: https://example.com/gitea token: value: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565 @@ -34,6 +35,7 @@ namingPattern: regex: "^PR-(\\d+)$" template: "PR-%d" `) +} func WriteConfigFile(t *testing.T, content []byte) string { dir := os.TempDir() @@ -48,78 +50,157 @@ func WriteConfigFile(t *testing.T, content []byte) string { return config } -func TestLoadWithMissingFile(t *testing.T) { - assert.Panics(t, func() { Load(path.Join(os.TempDir(), "config.yaml")) }, "No panic while reading missing file") -} +func TestLoad(t *testing.T) { + t.Run("Missing file", func(t *testing.T) { + assert.Panics(t, func() { Load(path.Join(os.TempDir(), "config.yaml")) }, "No panic while reading missing file") + }) -func TestLoadWithExistingFile(t *testing.T) { - c := WriteConfigFile(t, defaultConfig) + t.Run("Existing file", func(t *testing.T) { + c := WriteConfigFile(t, defaultConfig()) + assert.NotPanics(t, func() { Load(c) }, "Unexpected panic while reading existing file") + }) - assert.NotPanics(t, func() { Load(c) }, "Unexpected panic while reading existing file") -} + t.Run("File references", func(t *testing.T) { + giteaWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-gitea") + _ = ioutil.WriteFile(giteaWebhookSecretFile, []byte(`gitea-totally-secret`), 0444) -func TestLoadGiteaStructure(t *testing.T) { - c := WriteConfigFile(t, defaultConfig) - Load(c) + giteaTokenFile := path.Join(os.TempDir(), "token-secret-gitea") + _ = ioutil.WriteFile(giteaTokenFile, []byte(`d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565`), 0444) - expected := GiteaConfig{ - Url: "https://example.com/gitea", - Token: &Token{ - Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565", - }, - Webhook: &Webhook{ - Secret: "haxxor-gitea-secret", - }, - } + sonarqubeWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-sonarqube") + _ = ioutil.WriteFile(sonarqubeWebhookSecretFile, []byte(`sonarqube-totally-secret`), 0444) - assert.EqualValues(t, expected, Gitea) -} + sonarqubeTokenFile := path.Join(os.TempDir(), "token-secret-sonarqube") + _ = ioutil.WriteFile(sonarqubeTokenFile, []byte(`a09eb5785b25bb2cbacf48808a677a0709f02d8e`), 0444) -func TestLoadGiteaStructureInjectedEnvs(t *testing.T) { - os.Setenv("PRBOT_GITEA_WEBHOOK_SECRET", "injected-webhook-secret") - os.Setenv("PRBOT_GITEA_TOKEN_VALUE", "injected-token") - c := WriteConfigFile(t, defaultConfig) - Load(c) + c := WriteConfigFile(t, []byte( + `gitea: + url: https://example.com/gitea + token: + value: fake-gitea-token +sonarqube: + url: https://example.com/sonarqube + token: + value: fake-sonarqube-token +projects: + - sonarqube: + key: gitea-sonarqube-bot + gitea: + owner: example-organization + name: pr-bot +`)) + os.Setenv("PRBOT_GITEA_WEBHOOK_SECRETFILE", giteaWebhookSecretFile) + os.Setenv("PRBOT_GITEA_TOKEN_FILE", giteaTokenFile) + os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE", sonarqubeWebhookSecretFile) + os.Setenv("PRBOT_SONARQUBE_TOKEN_FILE", sonarqubeTokenFile) - expected := GiteaConfig{ - Url: "https://example.com/gitea", - Token: &Token{ - Value: "injected-token", - }, - Webhook: &Webhook{ - Secret: "injected-webhook-secret", - }, - } + expectedGitea := GiteaConfig{ + Url: "https://example.com/gitea", + Token: &Token{ + Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565", + file: giteaTokenFile, + }, + Webhook: &Webhook{ + Secret: "gitea-totally-secret", + secretFile: giteaWebhookSecretFile, + }, + } - assert.EqualValues(t, expected, Gitea) + expectedSonarQube := SonarQubeConfig{ + Url: "https://example.com/sonarqube", + Token: &Token{ + Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e", + file: sonarqubeTokenFile, + }, + Webhook: &Webhook{ + Secret: "sonarqube-totally-secret", + secretFile: sonarqubeWebhookSecretFile, + }, + AdditionalMetrics: []string{}, + } - t.Cleanup(func() { - os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRET") - os.Unsetenv("PRBOT_GITEA_TOKEN_VALUE") + Load(c) + assert.EqualValues(t, expectedGitea, Gitea) + assert.EqualValues(t, expectedSonarQube, SonarQube) + + t.Cleanup(func() { + os.Remove(giteaWebhookSecretFile) + os.Remove(giteaTokenFile) + os.Remove(sonarqubeWebhookSecretFile) + os.Remove(sonarqubeTokenFile) + os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRETFILE") + os.Unsetenv("PRBOT_GITEA_TOKEN_FILE") + os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE") + os.Unsetenv("PRBOT_SONARQUBE_TOKEN_FILE") + }) }) } -func TestLoadSonarQubeStructure(t *testing.T) { - c := WriteConfigFile(t, defaultConfig) - Load(c) +func TestLoadGitea(t *testing.T) { + t.Run("Default", func(t *testing.T) { + c := WriteConfigFile(t, defaultConfig()) + Load(c) - expected := SonarQubeConfig{ - Url: "https://example.com/sonarqube", - Token: &Token{ - Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e", - }, - Webhook: &Webhook{ - Secret: "haxxor-sonarqube-secret", - }, - } + expected := GiteaConfig{ + Url: "https://example.com/gitea", + Token: &Token{ + Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565", + }, + Webhook: &Webhook{ + Secret: "haxxor-gitea-secret", + }, + } - assert.EqualValues(t, expected, SonarQube) - assert.EqualValues(t, expected.GetMetricsList(), "bugs,vulnerabilities,code_smells") + assert.EqualValues(t, expected, Gitea) + }) + + t.Run("Injected envs", func(t *testing.T) { + os.Setenv("PRBOT_GITEA_WEBHOOK_SECRET", "injected-webhook-secret") + os.Setenv("PRBOT_GITEA_TOKEN_VALUE", "injected-token") + c := WriteConfigFile(t, defaultConfig()) + Load(c) + + expected := GiteaConfig{ + Url: "https://example.com/gitea", + Token: &Token{ + Value: "injected-token", + }, + Webhook: &Webhook{ + Secret: "injected-webhook-secret", + }, + } + + assert.EqualValues(t, expected, Gitea) + + t.Cleanup(func() { + os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRET") + os.Unsetenv("PRBOT_GITEA_TOKEN_VALUE") + }) + }) } -func TestLoadSonarQubeStructureWithAdditionalMetrics(t *testing.T) { - c := WriteConfigFile(t, []byte( - `gitea: +func TestLoadSonarQube(t *testing.T) { + t.Run("Default", func(t *testing.T) { + c := WriteConfigFile(t, defaultConfig()) + Load(c) + + expected := SonarQubeConfig{ + Url: "https://example.com/sonarqube", + Token: &Token{ + Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e", + }, + Webhook: &Webhook{ + Secret: "haxxor-sonarqube-secret", + }, + } + + assert.EqualValues(t, expected, SonarQube) + assert.EqualValues(t, expected.GetMetricsList(), "bugs,vulnerabilities,code_smells") + }) + + t.Run("Additional metrics", func(t *testing.T) { + c := WriteConfigFile(t, []byte( + `gitea: url: https://example.com/gitea token: value: fake-gitea-token @@ -135,147 +216,74 @@ projects: owner: example-organization name: pr-bot `)) - Load(c) + Load(c) - expected := SonarQubeConfig{ - Url: "https://example.com/sonarqube", - Token: &Token{ - Value: "fake-sonarqube-token", - }, - Webhook: &Webhook{ - Secret: "", - }, - AdditionalMetrics: []string{ - "new_security_hotspots", - }, - } + expected := SonarQubeConfig{ + Url: "https://example.com/sonarqube", + Token: &Token{ + Value: "fake-sonarqube-token", + }, + Webhook: &Webhook{ + Secret: "", + }, + AdditionalMetrics: []string{ + "new_security_hotspots", + }, + } - assert.EqualValues(t, expected, SonarQube) - assert.EqualValues(t, expected.AdditionalMetrics, []string{"new_security_hotspots"}) - assert.EqualValues(t, "bugs,vulnerabilities,code_smells,new_security_hotspots", SonarQube.GetMetricsList()) -} + assert.EqualValues(t, expected, SonarQube) + assert.EqualValues(t, expected.AdditionalMetrics, []string{"new_security_hotspots"}) + assert.EqualValues(t, "bugs,vulnerabilities,code_smells,new_security_hotspots", SonarQube.GetMetricsList()) + }) -func TestLoadSonarQubeStructureInjectedEnvs(t *testing.T) { - os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRET", "injected-webhook-secret") - os.Setenv("PRBOT_SONARQUBE_TOKEN_VALUE", "injected-token") - c := WriteConfigFile(t, defaultConfig) - Load(c) + t.Run("Injected envs", func(t *testing.T) { + os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRET", "injected-webhook-secret") + os.Setenv("PRBOT_SONARQUBE_TOKEN_VALUE", "injected-token") + c := WriteConfigFile(t, defaultConfig()) + Load(c) - expected := SonarQubeConfig{ - Url: "https://example.com/sonarqube", - Token: &Token{ - Value: "injected-token", - }, - Webhook: &Webhook{ - Secret: "injected-webhook-secret", - }, - } + expected := SonarQubeConfig{ + Url: "https://example.com/sonarqube", + Token: &Token{ + Value: "injected-token", + }, + Webhook: &Webhook{ + Secret: "injected-webhook-secret", + }, + } - assert.EqualValues(t, expected, SonarQube) + assert.EqualValues(t, expected, SonarQube) - t.Cleanup(func() { - os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRET") - os.Unsetenv("PRBOT_SONARQUBE_TOKEN_VALUE") + t.Cleanup(func() { + os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRET") + os.Unsetenv("PRBOT_SONARQUBE_TOKEN_VALUE") + }) }) } -func TestLoadStructureWithFileReferenceResolving(t *testing.T) { - giteaWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-gitea") - _ = ioutil.WriteFile(giteaWebhookSecretFile, []byte(`gitea-totally-secret`), 0444) +func TestLoadProjects(t *testing.T) { + t.Run("Default", func(t *testing.T) { + c := WriteConfigFile(t, defaultConfig()) + Load(c) - giteaTokenFile := path.Join(os.TempDir(), "token-secret-gitea") - _ = ioutil.WriteFile(giteaTokenFile, []byte(`d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565`), 0444) + expectedProjects := []Project{ + { + SonarQube: struct{ Key string }{ + Key: "gitea-sonarqube-bot", + }, + Gitea: GiteaRepository{ + Owner: "example-organization", + Name: "pr-bot", + }, + }, + } - sonarqubeWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-sonarqube") - _ = ioutil.WriteFile(sonarqubeWebhookSecretFile, []byte(`sonarqube-totally-secret`), 0444) - - sonarqubeTokenFile := path.Join(os.TempDir(), "token-secret-sonarqube") - _ = ioutil.WriteFile(sonarqubeTokenFile, []byte(`a09eb5785b25bb2cbacf48808a677a0709f02d8e`), 0444) - - c := WriteConfigFile(t, []byte( - `gitea: - url: https://example.com/gitea - token: - value: fake-gitea-token -sonarqube: - url: https://example.com/sonarqube - token: - value: fake-sonarqube-token -projects: - - sonarqube: - key: gitea-sonarqube-bot - gitea: - owner: example-organization - name: pr-bot -`)) - os.Setenv("PRBOT_GITEA_WEBHOOK_SECRETFILE", giteaWebhookSecretFile) - os.Setenv("PRBOT_GITEA_TOKEN_FILE", giteaTokenFile) - os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE", sonarqubeWebhookSecretFile) - os.Setenv("PRBOT_SONARQUBE_TOKEN_FILE", sonarqubeTokenFile) - - expectedGitea := GiteaConfig{ - Url: "https://example.com/gitea", - Token: &Token{ - Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565", - file: giteaTokenFile, - }, - Webhook: &Webhook{ - Secret: "gitea-totally-secret", - secretFile: giteaWebhookSecretFile, - }, - } - - expectedSonarQube := SonarQubeConfig{ - Url: "https://example.com/sonarqube", - Token: &Token{ - Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e", - file: sonarqubeTokenFile, - }, - Webhook: &Webhook{ - Secret: "sonarqube-totally-secret", - secretFile: sonarqubeWebhookSecretFile, - }, - AdditionalMetrics: []string{}, - } - - Load(c) - assert.EqualValues(t, expectedGitea, Gitea) - assert.EqualValues(t, expectedSonarQube, SonarQube) - - t.Cleanup(func() { - os.Remove(giteaWebhookSecretFile) - os.Remove(giteaTokenFile) - os.Remove(sonarqubeWebhookSecretFile) - os.Remove(sonarqubeTokenFile) - os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRETFILE") - os.Unsetenv("PRBOT_GITEA_TOKEN_FILE") - os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE") - os.Unsetenv("PRBOT_SONARQUBE_TOKEN_FILE") + assert.EqualValues(t, expectedProjects, Projects) }) -} -func TestLoadProjectsStructure(t *testing.T) { - c := WriteConfigFile(t, defaultConfig) - Load(c) - - expectedProjects := []Project{ - { - SonarQube: struct{ Key string }{ - Key: "gitea-sonarqube-bot", - }, - Gitea: GiteaRepository{ - Owner: "example-organization", - Name: "pr-bot", - }, - }, - } - - assert.EqualValues(t, expectedProjects, Projects) -} - -func TestLoadProjectsStructureWithNoMapping(t *testing.T) { - invalidConfig := []byte( - `gitea: + t.Run("Empty mapping", func(t *testing.T) { + invalidConfig := []byte( + `gitea: url: https://example.com/gitea token: value: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565 @@ -289,26 +297,28 @@ sonarqube: secret: haxxor-sonarqube-secret projects: [] `) - c := WriteConfigFile(t, invalidConfig) + c := WriteConfigFile(t, invalidConfig) - assert.Panics(t, func() { Load(c) }, "No panic for empty project mapping that is required") + assert.Panics(t, func() { Load(c) }, "No panic for empty project mapping that is required") + }) } -func TestLoadNamingPatternStructure(t *testing.T) { - c := WriteConfigFile(t, defaultConfig) - Load(c) +func TestLoadNamingPattern(t *testing.T) { + t.Run("Default", func(t *testing.T) { + c := WriteConfigFile(t, defaultConfig()) + Load(c) - expected := &PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - Template: "PR-%d", - } + expected := &PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + Template: "PR-%d", + } - assert.EqualValues(t, expected, Pattern) -} + assert.EqualValues(t, expected, Pattern) + }) -func TestLoadNamingPatternStructureWithInternalDefaults(t *testing.T) { - c := WriteConfigFile(t, []byte( - `gitea: + t.Run("Internal defaults", func(t *testing.T) { + c := WriteConfigFile(t, []byte( + `gitea: url: https://example.com/gitea token: value: fake-gitea-token @@ -324,48 +334,49 @@ projects: owner: example-organization name: pr-bot `)) - Load(c) + Load(c) - expected := &PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - Template: "PR-%d", - } + expected := &PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + Template: "PR-%d", + } - assert.EqualValues(t, expected, Pattern) -} + assert.EqualValues(t, expected, Pattern) + }) -func TestLoadNamingPatternStructureInjectedEnvs(t *testing.T) { - os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest") - os.Setenv("PRBOT_NAMINGPATTERN_TEMPLATE", "test-%d-pullrequest") - c := WriteConfigFile(t, defaultConfig) - Load(c) + t.Run("Injected envs", func(t *testing.T) { + os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest") + os.Setenv("PRBOT_NAMINGPATTERN_TEMPLATE", "test-%d-pullrequest") + c := WriteConfigFile(t, defaultConfig()) + Load(c) - expected := &PatternConfig{ - RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`), - Template: "test-%d-pullrequest", - } + expected := &PatternConfig{ + RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`), + Template: "test-%d-pullrequest", + } - assert.EqualValues(t, expected, Pattern) + assert.EqualValues(t, expected, Pattern) - t.Cleanup(func() { - os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX") - os.Unsetenv("PRBOT_NAMINGPATTERN_TEMPLATE") - }) -} - -func TestLoadNamingPatternStructureMixedInput(t *testing.T) { - os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest") - c := WriteConfigFile(t, defaultConfig) - Load(c) - - expected := &PatternConfig{ - RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`), - Template: "PR-%d", - } - - assert.EqualValues(t, expected, Pattern) - - t.Cleanup(func() { - os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX") + t.Cleanup(func() { + os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX") + os.Unsetenv("PRBOT_NAMINGPATTERN_TEMPLATE") + }) + }) + + t.Run("Mixed input", func(t *testing.T) { + os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest") + c := WriteConfigFile(t, defaultConfig()) + Load(c) + + expected := &PatternConfig{ + RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`), + Template: "PR-%d", + } + + assert.EqualValues(t, expected, Pattern) + + t.Cleanup(func() { + os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX") + }) }) } diff --git a/internal/webhooks/sonarqube/webhook_test.go b/internal/webhooks/sonarqube/webhook_test.go index 58c555d..ae84321 100644 --- a/internal/webhooks/sonarqube/webhook_test.go +++ b/internal/webhooks/sonarqube/webhook_test.go @@ -10,47 +10,49 @@ import ( ) func TestNewWebhook(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - } + t.Run("Success", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + } - raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": { "sonar.analysis.sqbot": "a84442009c09b1adc278b6bb80a3853419f54007" } }`) - response, ok := New(raw) + raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": { "sonar.analysis.sqbot": "a84442009c09b1adc278b6bb80a3853419f54007" } }`) + response, ok := New(raw) - assert.NotNil(t, response) - assert.Equal(t, 1337, response.PRIndex) - assert.Equal(t, "a84442009c09b1adc278b6bb80a3853419f54007", response.Properties.OriginalCommit) - assert.True(t, ok) + assert.NotNil(t, response) + assert.Equal(t, 1337, response.PRIndex) + assert.Equal(t, "a84442009c09b1adc278b6bb80a3853419f54007", response.Properties.OriginalCommit) + assert.True(t, ok) - t.Cleanup(func() { - settings.Pattern = nil + t.Cleanup(func() { + settings.Pattern = nil + }) }) -} -func TestNewWebhookInvalidJSON(t *testing.T) { - raw := []byte(`{ "serverUrl": ["invalid-server-url-content"] }`) - _, ok := New(raw) + t.Run("Invalid JSON", func(t *testing.T) { + raw := []byte(`{ "serverUrl": ["invalid-server-url-content"] }`) + _, ok := New(raw) - assert.False(t, ok) -} + assert.False(t, ok) + }) -func TestNewWebhookInvalidBranchName(t *testing.T) { - settings.Pattern = &settings.PatternConfig{ - RegExp: regexp.MustCompile(`^PR-(\d+)$`), - } + t.Run("Invalid branch name", func(t *testing.T) { + settings.Pattern = &settings.PatternConfig{ + RegExp: regexp.MustCompile(`^PR-(\d+)$`), + } - raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "invalid", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`) - _, ok := New(raw) + raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "invalid", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`) + _, ok := New(raw) - assert.False(t, ok) + assert.False(t, ok) - t.Cleanup(func() { - settings.Pattern = nil + t.Cleanup(func() { + settings.Pattern = nil + }) }) } func TestWebhookGetRevision(t *testing.T) { - t.Run("Default revision", func(t *testing.T) { + t.Run("Default", func(t *testing.T) { w := Webhook{ Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76", } @@ -58,7 +60,7 @@ func TestWebhookGetRevision(t *testing.T) { assert.Equal(t, w.Revision, w.GetRevision()) }) - t.Run("Default revision due to incomplete properties", func(t *testing.T) { + t.Run("Incomplete properties", func(t *testing.T) { w := Webhook{ Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76", Properties: &properties{},