Repository: xanzy/go-gitlab
Pull request #1474 (2022-05-14 11:31:30)

Adding resource milestone events
This PR adds support for gitlabs Resource milestone events API: https://docs.gitlab.com/ee/api/resource_milestone_events.html

Fixes issue #1473


Patch:
diff --git a/gitlab.go b/gitlab.go
index 5bc1eb5..82d97ba 100644
--- a/gitlab.go
+++ b/gitlab.go
@@ -101,107 +101,108 @@ type Client struct {
 	UserAgent string
 
 	// Services used for talking to different parts of the GitLab API.
-	AccessRequests         *AccessRequestsService
-	Applications           *ApplicationsService
-	AuditEvents            *AuditEventsService
-	Avatar                 *AvatarRequestsService
-	AwardEmoji             *AwardEmojiService
-	Boards                 *IssueBoardsService
-	Branches               *BranchesService
-	BroadcastMessage       *BroadcastMessagesService
-	ClusterAgents          *ClusterAgentsService
-	CIYMLTemplate          *CIYMLTemplatesService
-	Commits                *CommitsService
-	ContainerRegistry      *ContainerRegistryService
-	CustomAttribute        *CustomAttributesService
-	DeployKeys             *DeployKeysService
-	DeployTokens           *DeployTokensService
-	Deployments            *DeploymentsService
-	Discussions            *DiscussionsService
-	Environments           *EnvironmentsService
-	EpicIssues             *EpicIssuesService
-	Epics                  *EpicsService
-	Events                 *EventsService
-	ExternalStatusChecks   *ExternalStatusChecksService
-	Features               *FeaturesService
-	FreezePeriods          *FreezePeriodsService
-	GenericPackages        *GenericPackagesService
-	GeoNodes               *GeoNodesService
-	GitIgnoreTemplates     *GitIgnoreTemplatesService
-	GroupAccessTokens      *GroupAccessTokensService
-	GroupBadges            *GroupBadgesService
-	GroupCluster           *GroupClustersService
-	GroupImportExport      *GroupImportExportService
-	GroupIssueBoards       *GroupIssueBoardsService
-	GroupIterations        *GroupIterationsService
-	GroupLabels            *GroupLabelsService
-	GroupMembers           *GroupMembersService
-	GroupMilestones        *GroupMilestonesService
-	GroupVariables         *GroupVariablesService
-	GroupWikis             *GroupWikisService
-	Groups                 *GroupsService
-	InstanceCluster        *InstanceClustersService
-	InstanceVariables      *InstanceVariablesService
-	Invites                *InvitesService
-	IssueLinks             *IssueLinksService
-	Issues                 *IssuesService
-	IssuesStatistics       *IssuesStatisticsService
-	Jobs                   *JobsService
-	Keys                   *KeysService
-	Labels                 *LabelsService
-	License                *LicenseService
-	LicenseTemplates       *LicenseTemplatesService
-	ManagedLicenses        *ManagedLicensesService
-	Markdown               *MarkdownService
-	MergeRequestApprovals  *MergeRequestApprovalsService
-	MergeRequests          *MergeRequestsService
-	Milestones             *MilestonesService
-	Namespaces             *NamespacesService
-	Notes                  *NotesService
-	NotificationSettings   *NotificationSettingsService
-	Packages               *PackagesService
-	Pages                  *PagesService
-	PagesDomains           *PagesDomainsService
-	PersonalAccessTokens   *PersonalAccessTokensService
-	PipelineSchedules      *PipelineSchedulesService
-	PipelineTriggers       *PipelineTriggersService
-	Pipelines              *PipelinesService
-	PlanLimits             *PlanLimitsService
-	ProjectBadges          *ProjectBadgesService
-	ProjectAccessTokens    *ProjectAccessTokensService
-	ProjectCluster         *ProjectClustersService
-	ProjectImportExport    *ProjectImportExportService
-	ProjectIterations      *ProjectIterationsService
-	ProjectMembers         *ProjectMembersService
-	ProjectMirrors         *ProjectMirrorService
-	ProjectSnippets        *ProjectSnippetsService
-	ProjectVariables       *ProjectVariablesService
-	ProjectVulnerabilities *ProjectVulnerabilitiesService
-	Projects               *ProjectsService
-	ProtectedBranches      *ProtectedBranchesService
-	ProtectedEnvironments  *ProtectedEnvironmentsService
-	ProtectedTags          *ProtectedTagsService
-	ReleaseLinks           *ReleaseLinksService
-	Releases               *ReleasesService
-	Repositories           *RepositoriesService
-	RepositoryFiles        *RepositoryFilesService
-	RepositorySubmodules   *RepositorySubmodulesService
-	ResourceLabelEvents    *ResourceLabelEventsService
-	ResourceStateEvents    *ResourceStateEventsService
-	Runners                *RunnersService
-	Search                 *SearchService
-	Services               *ServicesService
-	Settings               *SettingsService
-	Sidekiq                *SidekiqService
-	Snippets               *SnippetsService
-	SystemHooks            *SystemHooksService
-	Tags                   *TagsService
-	Todos                  *TodosService
-	Topics                 *TopicsService
-	Users                  *UsersService
-	Validate               *ValidateService
-	Version                *VersionService
-	Wikis                  *WikisService
+	AccessRequests          *AccessRequestsService
+	Applications            *ApplicationsService
+	AuditEvents             *AuditEventsService
+	Avatar                  *AvatarRequestsService
+	AwardEmoji              *AwardEmojiService
+	Boards                  *IssueBoardsService
+	Branches                *BranchesService
+	BroadcastMessage        *BroadcastMessagesService
+	CIYMLTemplate           *CIYMLTemplatesService
+	ClusterAgents           *ClusterAgentsService
+	Commits                 *CommitsService
+	ContainerRegistry       *ContainerRegistryService
+	CustomAttribute         *CustomAttributesService
+	DeployKeys              *DeployKeysService
+	DeployTokens            *DeployTokensService
+	Deployments             *DeploymentsService
+	Discussions             *DiscussionsService
+	Environments            *EnvironmentsService
+	EpicIssues              *EpicIssuesService
+	Epics                   *EpicsService
+	Events                  *EventsService
+	ExternalStatusChecks    *ExternalStatusChecksService
+	Features                *FeaturesService
+	FreezePeriods           *FreezePeriodsService
+	GenericPackages         *GenericPackagesService
+	GeoNodes                *GeoNodesService
+	GitIgnoreTemplates      *GitIgnoreTemplatesService
+	GroupAccessTokens       *GroupAccessTokensService
+	GroupBadges             *GroupBadgesService
+	GroupCluster            *GroupClustersService
+	GroupImportExport       *GroupImportExportService
+	GroupIssueBoards        *GroupIssueBoardsService
+	GroupIterations         *GroupIterationsService
+	GroupLabels             *GroupLabelsService
+	GroupMembers            *GroupMembersService
+	GroupMilestones         *GroupMilestonesService
+	GroupVariables          *GroupVariablesService
+	GroupWikis              *GroupWikisService
+	Groups                  *GroupsService
+	InstanceCluster         *InstanceClustersService
+	InstanceVariables       *InstanceVariablesService
+	Invites                 *InvitesService
+	IssueLinks              *IssueLinksService
+	Issues                  *IssuesService
+	IssuesStatistics        *IssuesStatisticsService
+	Jobs                    *JobsService
+	Keys                    *KeysService
+	Labels                  *LabelsService
+	License                 *LicenseService
+	LicenseTemplates        *LicenseTemplatesService
+	ManagedLicenses         *ManagedLicensesService
+	Markdown                *MarkdownService
+	MergeRequestApprovals   *MergeRequestApprovalsService
+	MergeRequests           *MergeRequestsService
+	Milestones              *MilestonesService
+	Namespaces              *NamespacesService
+	Notes                   *NotesService
+	NotificationSettings    *NotificationSettingsService
+	Packages                *PackagesService
+	Pages                   *PagesService
+	PagesDomains            *PagesDomainsService
+	PersonalAccessTokens    *PersonalAccessTokensService
+	PipelineSchedules       *PipelineSchedulesService
+	PipelineTriggers        *PipelineTriggersService
+	Pipelines               *PipelinesService
+	PlanLimits              *PlanLimitsService
+	ProjectAccessTokens     *ProjectAccessTokensService
+	ProjectBadges           *ProjectBadgesService
+	ProjectCluster          *ProjectClustersService
+	ProjectImportExport     *ProjectImportExportService
+	ProjectIterations       *ProjectIterationsService
+	ProjectMembers          *ProjectMembersService
+	ProjectMirrors          *ProjectMirrorService
+	ProjectSnippets         *ProjectSnippetsService
+	ProjectVariables        *ProjectVariablesService
+	ProjectVulnerabilities  *ProjectVulnerabilitiesService
+	Projects                *ProjectsService
+	ProtectedBranches       *ProtectedBranchesService
+	ProtectedEnvironments   *ProtectedEnvironmentsService
+	ProtectedTags           *ProtectedTagsService
+	ReleaseLinks            *ReleaseLinksService
+	Releases                *ReleasesService
+	Repositories            *RepositoriesService
+	RepositoryFiles         *RepositoryFilesService
+	RepositorySubmodules    *RepositorySubmodulesService
+	ResourceLabelEvents     *ResourceLabelEventsService
+	ResourceMilestoneEvents *ResourceMilestoneEventsService
+	ResourceStateEvents     *ResourceStateEventsService
+	Runners                 *RunnersService
+	Search                  *SearchService
+	Services                *ServicesService
+	Settings                *SettingsService
+	Sidekiq                 *SidekiqService
+	Snippets                *SnippetsService
+	SystemHooks             *SystemHooksService
+	Tags                    *TagsService
+	Todos                   *TodosService
+	Topics                  *TopicsService
+	Users                   *UsersService
+	Validate                *ValidateService
+	Version                 *VersionService
+	Wikis                   *WikisService
 }
 
 // ListOptions specifies the optional parameters to various List methods that
@@ -387,6 +388,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) {
 	c.RepositoryFiles = &RepositoryFilesService{client: c}
 	c.RepositorySubmodules = &RepositorySubmodulesService{client: c}
 	c.ResourceLabelEvents = &ResourceLabelEventsService{client: c}
+	c.ResourceMilestoneEvents = &ResourceMilestoneEventsService{client: c}
 	c.ResourceStateEvents = &ResourceStateEventsService{client: c}
 	c.Runners = &RunnersService{client: c}
 	c.Search = &SearchService{client: c}
diff --git a/resource_milestone_events.go b/resource_milestone_events.go
new file mode 100644
index 0000000..9c08830
--- /dev/null
+++ b/resource_milestone_events.go
@@ -0,0 +1,155 @@
+//
+// Copyright 2022, Mai Lapyst
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+)
+
+// ResourceMilestoneEventsService handles communication with the event related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/resource_milestone_events.html
+type ResourceMilestoneEventsService struct {
+	client *Client
+}
+
+// MilestoneEvent represents a resource milestone event.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/resource_milestone_events.html
+type MilestoneEvent struct {
+	ID           int        `json:"id"`
+	User         *BasicUser `json:"user"`
+	CreatedAt    *time.Time `json:"created_at"`
+	ResourceType string     `json:"resource_type"`
+	ResourceID   int        `json:"resource_id"`
+	Milestone    *Milestone `json:"milestone"`
+	Action       string     `json:"action"`
+}
+
+// ListMilestoneEventsOptions represents the options for all resource state events
+// list methods.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_milestone_events.html#list-project-issue-milestone-events
+type ListMilestoneEventsOptions struct {
+	ListOptions
+}
+
+// ListIssueMilestoneEvents retrieves resource milestone events for the specified
+// project and issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_milestone_events.html#list-project-issue-milestone-events
+func (s *ResourceMilestoneEventsService) ListIssueMilestoneEvents(pid interface{}, issue int, opt *ListMilestoneEventsOptions, options ...RequestOptionFunc) ([]*MilestoneEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/resource_milestone_events", PathEscape(project), issue)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var mes []*MilestoneEvent
+	resp, err := s.client.Do(req, &mes)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mes, resp, err
+}
+
+// GetIssueMilestoneEvent gets a single issue milestone event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_milestone_events.html#get-single-issue-milestone-event
+func (s *ResourceMilestoneEventsService) GetIssueMilestoneEvent(pid interface{}, issue int, event int, options ...RequestOptionFunc) (*MilestoneEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/resource_milestone_events/%d", PathEscape(project), issue, event)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	me := new(MilestoneEvent)
+	resp, err := s.client.Do(req, me)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return me, resp, err
+}
+
+// ListMergeMilestoneEvents retrieves resource milestone events for the specified
+// project and merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_milestone_events.html#list-project-merge-request-milestone-events
+func (s *ResourceMilestoneEventsService) ListMergeMilestoneEvents(pid interface{}, request int, opt *ListMilestoneEventsOptions, options ...RequestOptionFunc) ([]*MilestoneEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/resource_milestone_events", PathEscape(project), request)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var mes []*MilestoneEvent
+	resp, err := s.client.Do(req, &mes)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mes, resp, err
+}
+
+// GetMergeRequestMilestoneEvent gets a single merge request milestone event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_milestone_events.html#get-single-merge-request-milestone-event
+func (s *ResourceMilestoneEventsService) GetMergeRequestMilestoneEvent(pid interface{}, request int, event int, options ...RequestOptionFunc) (*MilestoneEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/resource_milestone_events/%d", PathEscape(project), request, event)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	me := new(MilestoneEvent)
+	resp, err := s.client.Do(req, me)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return me, resp, err
+}


Test patch:
diff --git a/resource_milestone_events_test.go b/resource_milestone_events_test.go
new file mode 100644
index 0000000..54d4dbd
--- /dev/null
+++ b/resource_milestone_events_test.go
@@ -0,0 +1,259 @@
+package gitlab
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func ListIssueMilestoneEvents(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/5/issues/11/resource_milestone_events", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `[
+		  {
+			"id": 142,
+			"user": {
+			  "id": 1,
+			  "name": "Administrator",
+			  "username": "root",
+			  "state": "active",
+			  "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			  "web_url": "http://gitlab.example.com/root"
+			},
+			"resource_type": "Issue",
+			"resource_id": 11,
+			"milestone": {
+			  "id": 61,
+			  "iid": 9,
+			  "project_id": 7,
+			  "title": "v1.2",
+			  "description": "Ipsum Lorem",
+			  "state": "active",
+			  "web_url": "http://gitlab.example.com:3000/group/project/-/milestones/9"
+			},
+			"action": "add"
+		  }
+		]`)
+	})
+
+	opt := &ListMilestoneEventsOptions{ListOptions{Page: 1, PerPage: 10}}
+
+	mes, _, err := client.ResourceMilestoneEvents.ListIssueMilestoneEvents(5, 11, opt)
+	require.NoError(t, err)
+
+	want := []*MilestoneEvent{{
+		ID: 142,
+		User: &BasicUser{
+			ID:        1,
+			Username:  "root",
+			Name:      "Administrator",
+			State:     "active",
+			AvatarURL: "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			WebURL:    "http://gitlab.example.com/root",
+		},
+		ResourceType: "Issue",
+		ResourceID:   11,
+		Milestone: &Milestone{
+			ID:          61,
+			IID:         9,
+			ProjectID:   7,
+			Title:       "v1.2",
+			Description: "Ipsum Lorem",
+			State:       "active",
+			WebURL:      "http://gitlab.example.com:3000/group/project/-/milestones/9",
+		},
+		Action: "add",
+	}}
+	require.Equal(t, want, mes)
+}
+
+func GetIssueMilestoneEvent(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/5/issues/11/resource_milestone_events/143", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `
+			{
+			  "id": 143,
+			  "user": {
+				"id": 1,
+				"name": "Administrator",
+				"username": "root",
+				"state": "active",
+				"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+				"web_url": "http://gitlab.example.com/root"
+			  },
+			  "resource_type": "Issue",
+			  "resource_id": 11,
+			  "milestone": {
+			    "id": 61,
+			    "iid": 9,
+			    "project_id": 7,
+			    "title": "v1.2",
+			    "description": "Ipsum Lorem",
+			    "state": "active",
+			    "web_url": "http://gitlab.example.com:3000/group/project/-/milestones/9"
+			  },
+			  "action": "remove"
+			}`,
+		)
+	})
+
+	me, _, err := client.ResourceMilestoneEvents.GetIssueMilestoneEvent(5, 11, 143)
+	require.NoError(t, err)
+
+	want := &MilestoneEvent{
+		ID: 143,
+		User: &BasicUser{
+			ID:        1,
+			Username:  "root",
+			Name:      "Administrator",
+			State:     "active",
+			AvatarURL: "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			WebURL:    "http://gitlab.example.com/root",
+		},
+		ResourceType: "Issue",
+		ResourceID:   11,
+		Milestone: &Milestone{
+			ID:          61,
+			IID:         9,
+			ProjectID:   7,
+			Title:       "v1.2",
+			Description: "Ipsum Lorem",
+			State:       "active",
+			WebURL:      "http://gitlab.example.com:3000/group/project/-/milestones/9",
+		},
+		Action: "remove",
+	}
+	require.Equal(t, want, me)
+}
+
+func ListMergeMilestoneEvents(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/5/merge_requests/11/resource_milestone_events", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `[
+		  {
+			"id": 142,
+			"user": {
+			  "id": 1,
+			  "name": "Administrator",
+			  "username": "root",
+			  "state": "active",
+			  "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			  "web_url": "http://gitlab.example.com/root"
+			},
+			"resource_type": "MergeRequest",
+			"resource_id": 11,
+			"milestone": {
+			  "id": 61,
+			  "iid": 9,
+			  "project_id": 7,
+			  "title": "v1.2",
+			  "description": "Ipsum Lorem",
+			  "state": "active",
+			  "web_url": "http://gitlab.example.com:3000/group/project/-/milestones/9"
+			},
+			"action": "add"
+		  }]`,
+		)
+	})
+
+	opt := &ListMilestoneEventsOptions{ListOptions{Page: 1, PerPage: 10}}
+
+	ses, _, err := client.ResourceMilestoneEvents.ListMergeMilestoneEvents(5, 11, opt)
+	require.NoError(t, err)
+
+	want := []*MilestoneEvent{{
+		ID: 142,
+		User: &BasicUser{
+			ID:        1,
+			Username:  "root",
+			Name:      "Administrator",
+			State:     "active",
+			AvatarURL: "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			WebURL:    "http://gitlab.example.com/root",
+		},
+		ResourceType: "MergeRequest",
+		ResourceID:   11,
+		Milestone: &Milestone{
+			ID:          61,
+			IID:         9,
+			ProjectID:   7,
+			Title:       "v1.2",
+			Description: "Ipsum Lorem",
+			State:       "active",
+			WebURL:      "http://gitlab.example.com:3000/group/project/-/milestones/9",
+		},
+		Action: "add",
+	}}
+	require.Equal(t, want, ses)
+}
+
+func GetMergeRequestMilestoneEvent(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/5/merge_requests/11/resource_milestone_events/120", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `{
+		  "id": 120,
+		  "user": {
+			"id": 1,
+			"name": "Administrator",
+			"username": "root",
+			"state": "active",
+			"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			"web_url": "http://gitlab.example.com/root"
+		  },
+		  "resource_type": "MergeRequest",
+		  "resource_id": 11,
+          "milestone": {
+            "id": 61,
+            "iid": 9,
+            "project_id": 7,
+            "title": "v1.2",
+            "description": "Ipsum Lorem",
+            "state": "active",
+            "web_url": "http://gitlab.example.com:3000/group/project/-/milestones/9"
+          },
+          "action": "remove"
+		}`)
+	})
+
+	me, _, err := client.ResourceMilestoneEvents.GetMergeRequestMilestoneEvent(5, 11, 120)
+	require.NoError(t, err)
+
+	want := &MilestoneEvent{
+		ID: 120,
+		User: &BasicUser{
+			ID:        1,
+			Username:  "root",
+			Name:      "Administrator",
+			State:     "active",
+			AvatarURL: "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			WebURL:    "http://gitlab.example.com/root",
+		},
+		ResourceType: "MergeRequest",
+		ResourceID:   11,
+		Milestone: &Milestone{
+			ID:          61,
+			IID:         9,
+			ProjectID:   7,
+			Title:       "v1.2",
+			Description: "Ipsum Lorem",
+			State:       "active",
+			WebURL:      "http://gitlab.example.com:3000/group/project/-/milestones/9",
+		},
+		Action: "remove",
+	}
+	require.Equal(t, want, me)
+}