Repository: xanzy/go-gitlab
Pull request #1809 (2023-10-02 03:12:43)

Align project notification events
This PR adds support for several notification events that are present on the group/project level per the API documentation here: https://docs.gitlab.com/ee/api/notification_settings.html#update-groupproject-level-notification-settings

I also added a test for the project API testing that nested events returned properly.


Patch:
diff --git a/notifications.go b/notifications.go
index da02415..92c2cb1 100644
--- a/notifications.go
+++ b/notifications.go
@@ -45,18 +45,24 @@ type NotificationSettings struct {
 // GitLab API docs:
 // https://docs.gitlab.com/ee/api/notification_settings.html#valid-notification-levels
 type NotificationEvents struct {
-	CloseIssue           bool `json:"close_issue"`
-	CloseMergeRequest    bool `json:"close_merge_request"`
-	FailedPipeline       bool `json:"failed_pipeline"`
-	MergeMergeRequest    bool `json:"merge_merge_request"`
-	NewIssue             bool `json:"new_issue"`
-	NewMergeRequest      bool `json:"new_merge_request"`
-	NewNote              bool `json:"new_note"`
-	ReassignIssue        bool `json:"reassign_issue"`
-	ReassignMergeRequest bool `json:"reassign_merge_request"`
-	ReopenIssue          bool `json:"reopen_issue"`
-	ReopenMergeRequest   bool `json:"reopen_merge_request"`
-	SuccessPipeline      bool `json:"success_pipeline"`
+	CloseIssue                bool `json:"close_issue"`
+	CloseMergeRequest         bool `json:"close_merge_request"`
+	FailedPipeline            bool `json:"failed_pipeline"`
+	FixedPipeline             bool `json:"fixed_pipeline"`
+	IssueDue                  bool `json:"issue_due"`
+	MergeWhenPipelineSucceeds bool `json:"merge_when_pipeline_succeeds"`
+	MergeMergeRequest         bool `json:"merge_merge_request"`
+	MovedProject              bool `json:"moved_project"`
+	NewIssue                  bool `json:"new_issue"`
+	NewMergeRequest           bool `json:"new_merge_request"`
+	NewEpic                   bool `json:"new_epic"`
+	NewNote                   bool `json:"new_note"`
+	PushToMergeRequest        bool `json:"push_to_merge_request"`
+	ReassignIssue             bool `json:"reassign_issue"`
+	ReassignMergeRequest      bool `json:"reassign_merge_request"`
+	ReopenIssue               bool `json:"reopen_issue"`
+	ReopenMergeRequest        bool `json:"reopen_merge_request"`
+	SuccessPipeline           bool `json:"success_pipeline"`
 }
 
 func (ns NotificationSettings) String() string {
@@ -87,20 +93,26 @@ func (s *NotificationSettingsService) GetGlobalSettings(options ...RequestOption
 // NotificationSettingsOptions represents the available options that can be passed
 // to the API when updating the notification settings.
 type NotificationSettingsOptions struct {
-	Level                *NotificationLevelValue `url:"level,omitempty" json:"level,omitempty"`
-	NotificationEmail    *string                 `url:"notification_email,omitempty" json:"notification_email,omitempty"`
-	CloseIssue           *bool                   `url:"close_issue,omitempty" json:"close_issue,omitempty"`
-	CloseMergeRequest    *bool                   `url:"close_merge_request,omitempty" json:"close_merge_request,omitempty"`
-	FailedPipeline       *bool                   `url:"failed_pipeline,omitempty" json:"failed_pipeline,omitempty"`
-	MergeMergeRequest    *bool                   `url:"merge_merge_request,omitempty" json:"merge_merge_request,omitempty"`
-	NewIssue             *bool                   `url:"new_issue,omitempty" json:"new_issue,omitempty"`
-	NewMergeRequest      *bool                   `url:"new_merge_request,omitempty" json:"new_merge_request,omitempty"`
-	NewNote              *bool                   `url:"new_note,omitempty" json:"new_note,omitempty"`
-	ReassignIssue        *bool                   `url:"reassign_issue,omitempty" json:"reassign_issue,omitempty"`
-	ReassignMergeRequest *bool                   `url:"reassign_merge_request,omitempty" json:"reassign_merge_request,omitempty"`
-	ReopenIssue          *bool                   `url:"reopen_issue,omitempty" json:"reopen_issue,omitempty"`
-	ReopenMergeRequest   *bool                   `url:"reopen_merge_request,omitempty" json:"reopen_merge_request,omitempty"`
-	SuccessPipeline      *bool                   `url:"success_pipeline,omitempty" json:"success_pipeline,omitempty"`
+	Level                     *NotificationLevelValue `url:"level,omitempty" json:"level,omitempty"`
+	NotificationEmail         *string                 `url:"notification_email,omitempty" json:"notification_email,omitempty"`
+	CloseIssue                *bool                   `url:"close_issue,omitempty" json:"close_issue,omitempty"`
+	CloseMergeRequest         *bool                   `url:"close_merge_request,omitempty" json:"close_merge_request,omitempty"`
+	FailedPipeline            *bool                   `url:"failed_pipeline,omitempty" json:"failed_pipeline,omitempty"`
+	FixedPipeline             *bool                   `url:"fixed_pipeline,omitempty" json:"fixed_pipeline,omitempty"`
+	IssueDue                  *bool                   `url:"issue_due,omitempty" json:"issue_due,omitempty"`
+	MergeMergeRequest         *bool                   `url:"merge_merge_request,omitempty" json:"merge_merge_request,omitempty"`
+	MergeWhenPipelineSucceeds *bool                   `url:"merge_when_pipeline_succeeds,omitempty" json:"merge_when_pipeline_succeeds,omitempty"`
+	MovedProject              *bool                   `url:"moved_project,omitempty" json:"moved_project,omitempty"`
+	NewEpic                   *bool                   `url:"new_epic,omitempty" json:"new_epic,omitempty"`
+	NewIssue                  *bool                   `url:"new_issue,omitempty" json:"new_issue,omitempty"`
+	NewMergeRequest           *bool                   `url:"new_merge_request,omitempty" json:"new_merge_request,omitempty"`
+	NewNote                   *bool                   `url:"new_note,omitempty" json:"new_note,omitempty"`
+	PushToMergeRequest        *bool                   `url:"push_to_merge_request,omitempty" json:"push_to_merge_request,omitempty"`
+	ReassignIssue             *bool                   `url:"reassign_issue,omitempty" json:"reassign_issue,omitempty"`
+	ReassignMergeRequest      *bool                   `url:"reassign_merge_request,omitempty" json:"reassign_merge_request,omitempty"`
+	ReopenIssue               *bool                   `url:"reopen_issue,omitempty" json:"reopen_issue,omitempty"`
+	ReopenMergeRequest        *bool                   `url:"reopen_merge_request,omitempty" json:"reopen_merge_request,omitempty"`
+	SuccessPipeline           *bool                   `url:"success_pipeline,omitempty" json:"success_pipeline,omitempty"`
 }
 
 // UpdateGlobalSettings updates current notification settings and email address.


Test patch:
diff --git a/notifications_test.go b/notifications_test.go
index 33e0224..1b971fc 100644
--- a/notifications_test.go
+++ b/notifications_test.go
@@ -17,7 +17,9 @@
 package gitlab
 
 import (
+	"encoding/json"
 	"fmt"
+	"io"
 	"net/http"
 	"reflect"
 	"testing"
@@ -47,3 +49,151 @@ func TestGetGlobalSettings(t *testing.T) {
 		t.Errorf("NotificationSettings.GetGlobalSettings returned %+v, want %+v", settings, want)
 	}
 }
+
+func TestGetProjectSettings(t *testing.T) {
+	mux, client := setup(t)
+
+	mux.HandleFunc("/api/v4/projects/1/notification_settings", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `{
+		"level":"custom",
+		"events":{
+			"new_note":true,
+			"new_issue":true,
+			"reopen_issue":true,
+			"close_issue":true,
+			"reassign_issue":true,
+			"issue_due":true,
+			"new_merge_request":true,
+			"push_to_merge_request":true,
+			"reopen_merge_request":true,
+			"close_merge_request":true,
+			"reassign_merge_request":true,
+			"merge_merge_request":true,
+			"failed_pipeline":true,
+			"fixed_pipeline":true,
+			"success_pipeline":true,
+			"moved_project":true,
+			"merge_when_pipeline_succeeds":true,
+			"new_epic":true
+			}
+		}`)
+	})
+
+	settings, _, err := client.NotificationSettings.GetSettingsForProject(1)
+	if err != nil {
+		t.Errorf("NotifcationSettings.GetSettingsForProject returned error: %v", err)
+	}
+
+	want := &NotificationSettings{
+		Level: 5, //custom
+		Events: &NotificationEvents{
+			NewEpic:                   true,
+			NewNote:                   true,
+			NewIssue:                  true,
+			ReopenIssue:               true,
+			CloseIssue:                true,
+			ReassignIssue:             true,
+			IssueDue:                  true,
+			NewMergeRequest:           true,
+			PushToMergeRequest:        true,
+			ReopenMergeRequest:        true,
+			CloseMergeRequest:         true,
+			ReassignMergeRequest:      true,
+			MergeMergeRequest:         true,
+			FailedPipeline:            true,
+			FixedPipeline:             true,
+			SuccessPipeline:           true,
+			MovedProject:              true,
+			MergeWhenPipelineSucceeds: true,
+		},
+	}
+	if !reflect.DeepEqual(settings, want) {
+		t.Errorf("NotificationSettings.GetSettingsForProject returned %+v, want %+v", settings, want)
+	}
+}
+
+func TestUpdateProjectSettings(t *testing.T) {
+	mux, client := setup(t)
+
+	// Create the request to send
+	var reqBody NotificationSettingsOptions
+	customLevel := notificationLevelTypes["custom"]
+	options := NotificationSettingsOptions{
+		Level:        &customLevel,
+		NewEpic:      Bool(true),
+		MovedProject: Bool(true),
+		CloseIssue:   Bool(true),
+	}
+
+	// Handle the request on the server, and return a fully hydrated response
+	mux.HandleFunc("/api/v4/projects/1/notification_settings", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+
+		// Store the body for later, so we can check only some values are marshaled properly for update
+		body, _ := io.ReadAll(r.Body)
+		json.Unmarshal(body, &reqBody)
+
+		fmt.Fprintf(w, `{
+		"level":"custom",
+		"events":{
+			"new_note":true,
+			"new_issue":true,
+			"reopen_issue":true,
+			"close_issue":true,
+			"reassign_issue":true,
+			"issue_due":true,
+			"new_merge_request":true,
+			"push_to_merge_request":true,
+			"reopen_merge_request":true,
+			"close_merge_request":true,
+			"reassign_merge_request":true,
+			"merge_merge_request":true,
+			"failed_pipeline":true,
+			"fixed_pipeline":true,
+			"success_pipeline":true,
+			"moved_project":true,
+			"merge_when_pipeline_succeeds":true,
+			"new_epic":true
+			}
+		}`)
+	})
+
+	// Make the actual request
+	settings, _, err := client.NotificationSettings.UpdateSettingsForProject(1, &options)
+	if err != nil {
+		t.Errorf("NotifcationSettings.UpdateSettingsForProject returned error: %v", err)
+	}
+
+	// Test the response and the request
+	wantResponse := &NotificationSettings{
+		Level: customLevel,
+		Events: &NotificationEvents{
+			NewEpic:                   true,
+			NewNote:                   true,
+			NewIssue:                  true,
+			ReopenIssue:               true,
+			CloseIssue:                true,
+			ReassignIssue:             true,
+			IssueDue:                  true,
+			NewMergeRequest:           true,
+			PushToMergeRequest:        true,
+			ReopenMergeRequest:        true,
+			CloseMergeRequest:         true,
+			ReassignMergeRequest:      true,
+			MergeMergeRequest:         true,
+			FailedPipeline:            true,
+			FixedPipeline:             true,
+			SuccessPipeline:           true,
+			MovedProject:              true,
+			MergeWhenPipelineSucceeds: true,
+		},
+	}
+
+	if !reflect.DeepEqual(settings, wantResponse) {
+		t.Errorf("NotificationSettings.UpdateSettingsForProject returned for the response %+v, want %+v", settings, wantResponse)
+	}
+	if !reflect.DeepEqual(settings, wantResponse) {
+		t.Errorf("NotificationSettings.UpdateSettingsForProject send for the request %+v, want %+v", reqBody, options)
+	}
+}