Repository: xanzy/go-gitlab
Pull request #1410 (2022-03-12 08:09:07)

Implement API to get single project-level approval rule

timofurrer: @svanharmelen by any chance possible to get this merged and have a release with the lately merged MRs? Would be awesome 🎉 
svanharmelen: @timofurrer if you can tweak that one little thingy I'm good merging this one...

Patch:
diff --git a/deploy_tokens.go b/deploy_tokens.go
index f851023..1b9943d 100644
--- a/deploy_tokens.go
+++ b/deploy_tokens.go
@@ -95,31 +95,6 @@ func (s *DeployTokensService) ListProjectDeployTokens(pid interface{}, opt *List
 	return ts, resp, err
 }
 
-// GetProjectDeployToken gets a single deploy token.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ee/api/deploy_tokens.html#get-a-project-deploy-token
-func (s *DeployTokensService) GetProjectDeployToken(pid interface{}, deployToken int, options ...RequestOptionFunc) (*DeployToken, *Response, error) {
-	project, err := parseID(pid)
-	if err != nil {
-		return nil, nil, err
-	}
-	u := fmt.Sprintf("projects/%s/deploy_tokens/%d", PathEscape(project), deployToken)
-
-	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	t := new(DeployToken)
-	resp, err := s.client.Do(req, t)
-	if err != nil {
-		return nil, resp, err
-	}
-
-	return t, resp, err
-}
-
 // CreateProjectDeployTokenOptions represents the available CreateProjectDeployToken() options.
 //
 // GitLab API docs:
@@ -207,31 +182,6 @@ func (s *DeployTokensService) ListGroupDeployTokens(gid interface{}, opt *ListGr
 	return ts, resp, err
 }
 
-// GetGroupDeployToken gets a single deploy token.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ee/api/deploy_tokens.html#get-a-group-deploy-token
-func (s *DeployTokensService) GetGroupDeployToken(gid interface{}, deployToken int, options ...RequestOptionFunc) (*DeployToken, *Response, error) {
-	group, err := parseID(gid)
-	if err != nil {
-		return nil, nil, err
-	}
-	u := fmt.Sprintf("groups/%s/deploy_tokens/%d", PathEscape(group), deployToken)
-
-	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	t := new(DeployToken)
-	resp, err := s.client.Do(req, t)
-	if err != nil {
-		return nil, resp, err
-	}
-
-	return t, resp, err
-}
-
 // CreateGroupDeployTokenOptions represents the available CreateGroupDeployToken() options.
 //
 // GitLab API docs:
diff --git a/environments.go b/environments.go
index 7e0b267..d881293 100644
--- a/environments.go
+++ b/environments.go
@@ -19,7 +19,6 @@ package gitlab
 import (
 	"fmt"
 	"net/http"
-	"time"
 )
 
 // EnvironmentsService handles communication with the environment related methods
@@ -40,8 +39,6 @@ type Environment struct {
 	State          string      `json:"state"`
 	ExternalURL    string      `json:"external_url"`
 	Project        *Project    `json:"project"`
-	CreatedAt      *time.Time  `json:"created_at"`
-	UpdatedAt      *time.Time  `json:"updated_at"`
 	LastDeployment *Deployment `json:"last_deployment"`
 }
 
diff --git a/projects.go b/projects.go
index 7549e49..3f753c2 100644
--- a/projects.go
+++ b/projects.go
@@ -1749,6 +1749,31 @@ func (s *ProjectsService) GetProjectApprovalRules(pid interface{}, options ...Re
 	return par, resp, err
 }
 
+// GetProjectApprovalRule gets the project level approvers.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-a-single-project-level-rule
+func (s *ProjectsService) GetProjectApprovalRule(pid interface{}, ruleID int, options ...RequestOptionFunc) (*ProjectApprovalRule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approval_rules/%d", PathEscape(project), ruleID)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	par := new(ProjectApprovalRule)
+	resp, err := s.client.Do(req, &par)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return par, resp, err
+}
+
 // CreateProjectLevelRuleOptions represents the available CreateProjectApprovalRule()
 // options.
 //
diff --git a/users.go b/users.go
index 4e04e3a..5c7e2b0 100644
--- a/users.go
+++ b/users.go
@@ -468,26 +468,6 @@ func (s *UsersService) GetSSHKey(key int, options ...RequestOptionFunc) (*SSHKey
 	return k, resp, err
 }
 
-// GetSSHKeyForUser gets a single key for a given user.
-//
-// GitLab API docs: https://docs.gitlab.com/ee/api/users.html#single-ssh-key-for-given-user
-func (s *UsersService) GetSSHKeyForUser(user int, key int, options ...RequestOptionFunc) (*SSHKey, *Response, error) {
-	u := fmt.Sprintf("users/%d/keys/%d", user, key)
-
-	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	k := new(SSHKey)
-	resp, err := s.client.Do(req, k)
-	if err != nil {
-		return nil, resp, err
-	}
-
-	return k, resp, err
-}
-
 // AddSSHKeyOptions represents the available AddSSHKey() options.
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-ssh-key


Test patch:
diff --git a/deploy_tokens_test.go b/deploy_tokens_test.go
index 480f6dc..500c475 100644
--- a/deploy_tokens_test.go
+++ b/deploy_tokens_test.go
@@ -118,49 +118,6 @@ func TestListProjectDeployTokens(t *testing.T) {
 	}
 }
 
-func TestGetProjectDeployTokens(t *testing.T) {
-	mux, server, client := setup(t)
-	defer teardown(server)
-
-	mux.HandleFunc("/api/v4/projects/1/deploy_tokens/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, http.MethodGet)
-		fmt.Fprint(w, `
-{
-  "id": 1,
-  "name": "MyToken",
-  "username": "gitlab+deploy-token-1",
-  "expires_at": "2020-02-14T00:00:00.000Z",
-  "scopes": [
-    "read_repository",
-    "read_registry"
-  ]
-}
-`)
-	})
-
-	deployToken, _, err := client.DeployTokens.GetProjectDeployToken(1, 1)
-	if err != nil {
-		t.Errorf("DeployTokens.GetProjectDeployToken returned an error: %v", err)
-	}
-
-	wantExpiresAt := time.Date(2020, 02, 14, 0, 0, 0, 0, time.UTC)
-
-	want := &DeployToken{
-		ID:        1,
-		Name:      "MyToken",
-		Username:  "gitlab+deploy-token-1",
-		ExpiresAt: &wantExpiresAt,
-		Scopes: []string{
-			"read_repository",
-			"read_registry",
-		},
-	}
-
-	if !reflect.DeepEqual(want, deployToken) {
-		t.Errorf("DeployTokens.GetProjectDeployToken returned %+v, want %+v", deployToken, want)
-	}
-}
-
 func TestCreateProjectDeployToken(t *testing.T) {
 	mux, server, client := setup(t)
 	defer teardown(server)
@@ -280,49 +237,6 @@ func TestListGroupDeployTokens(t *testing.T) {
 	}
 }
 
-func TestGetGroupDeployTokens(t *testing.T) {
-	mux, server, client := setup(t)
-	defer teardown(server)
-
-	mux.HandleFunc("/api/v4/groups/1/deploy_tokens/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, http.MethodGet)
-		fmt.Fprint(w, `
-{
-  "id": 1,
-  "name": "MyToken",
-  "username": "gitlab+deploy-token-1",
-  "expires_at": "2020-02-14T00:00:00.000Z",
-  "scopes": [
-    "read_repository",
-    "read_registry"
-  ]
-}
-`)
-	})
-
-	deployToken, _, err := client.DeployTokens.GetGroupDeployToken(1, 1)
-	if err != nil {
-		t.Errorf("DeployTokens.GetGroupDeployToken returned an error: %v", err)
-	}
-
-	wantExpiresAt := time.Date(2020, 02, 14, 0, 0, 0, 0, time.UTC)
-
-	want := &DeployToken{
-		ID:        1,
-		Name:      "MyToken",
-		Username:  "gitlab+deploy-token-1",
-		ExpiresAt: &wantExpiresAt,
-		Scopes: []string{
-			"read_repository",
-			"read_registry",
-		},
-	}
-
-	if !reflect.DeepEqual(want, deployToken) {
-		t.Errorf("DeployTokens.GetGroupDeployToken returned %+v, want %+v", deployToken, want)
-	}
-}
-
 func TestCreateGroupDeployToken(t *testing.T) {
 	mux, server, client := setup(t)
 	defer teardown(server)
diff --git a/environments_test.go b/environments_test.go
index 77df7ac..ebc76a9 100644
--- a/environments_test.go
+++ b/environments_test.go
@@ -23,7 +23,6 @@ import (
 	"net/http"
 	"reflect"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -35,17 +34,7 @@ func TestListEnvironments(t *testing.T) {
 	mux.HandleFunc("/api/v4/projects/1/environments", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, http.MethodGet)
 		testURL(t, r, "/api/v4/projects/1/environments?name=review%2Ffix-foo&page=1&per_page=10")
-		fmt.Fprint(w, `[
-			{
-				"id": 1,
-				"name": "review/fix-foo",
-				"slug": "review-fix-foo-dfjre3",
-				"external_url": "https://review-fix-foo-dfjre3.example.gitlab.com",
-				"state": "stopped",
-				"created_at": "2013-10-02T10:12:29Z",
-				"updated_at": "2013-12-02T10:12:29Z"
-			}
-		]`)
+		fmt.Fprint(w, `[{"id": 1,"name": "review/fix-foo", "slug": "review-fix-foo-dfjre3", "external_url": "https://review-fix-foo-dfjre3.example.gitlab.com"}]`)
 	})
 
 	envs, _, err := client.Environments.ListEnvironments(1, &ListEnvironmentsOptions{Name: String("review/fix-foo"), ListOptions: ListOptions{Page: 1, PerPage: 10}})
@@ -53,17 +42,7 @@ func TestListEnvironments(t *testing.T) {
 		log.Fatal(err)
 	}
 
-	createdAtWant, _ := time.Parse(timeLayout, "2013-10-02T10:12:29Z")
-	updatedAtWant, _ := time.Parse(timeLayout, "2013-12-02T10:12:29Z")
-	want := []*Environment{{
-		ID:          1,
-		Name:        "review/fix-foo",
-		Slug:        "review-fix-foo-dfjre3",
-		ExternalURL: "https://review-fix-foo-dfjre3.example.gitlab.com",
-		State:       "stopped",
-		CreatedAt:   &createdAtWant,
-		UpdatedAt:   &updatedAtWant,
-	}}
+	want := []*Environment{{ID: 1, Name: "review/fix-foo", Slug: "review-fix-foo-dfjre3", ExternalURL: "https://review-fix-foo-dfjre3.example.gitlab.com"}}
 	if !reflect.DeepEqual(want, envs) {
 		t.Errorf("Environments.ListEnvironments returned %+v, want %+v", envs, want)
 	}
@@ -75,15 +54,7 @@ func TestGetEnvironment(t *testing.T) {
 
 	mux.HandleFunc("/api/v4/projects/1/environments/5949167", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, http.MethodGet)
-		fmt.Fprint(w, `{
-			"id": 1,
-			"name": "review/fix-foo",
-			"slug": "review-fix-foo-dfjre3",
-			"external_url": "https://review-fix-foo-dfjre3.example.gitlab.com",
-			"state": "stopped",
-			"created_at": "2013-10-02T10:12:29Z",
-			"updated_at": "2013-12-02T10:12:29Z"
-		}`)
+		fmt.Fprint(w, `{"id":1,"name":"test/test"}`)
 	})
 
 	env, _, err := client.Environments.GetEnvironment(1, 5949167)
@@ -91,17 +62,7 @@ func TestGetEnvironment(t *testing.T) {
 		t.Errorf("Environemtns.GetEnvironment returned error: %v", err)
 	}
 
-	createdAtWant, _ := time.Parse(timeLayout, "2013-10-02T10:12:29Z")
-	updatedAtWant, _ := time.Parse(timeLayout, "2013-12-02T10:12:29Z")
-	want := &Environment{
-		ID:          1,
-		Name:        "review/fix-foo",
-		Slug:        "review-fix-foo-dfjre3",
-		ExternalURL: "https://review-fix-foo-dfjre3.example.gitlab.com",
-		State:       "stopped",
-		CreatedAt:   &createdAtWant,
-		UpdatedAt:   &updatedAtWant,
-	}
+	want := &Environment{ID: 1, Name: "test/test"}
 	if !reflect.DeepEqual(want, env) {
 		t.Errorf("Environments.GetEnvironment returned %+v, want %+v", env, want)
 	}
diff --git a/projects_test.go b/projects_test.go
index 63c82ad..f495b3f 100644
--- a/projects_test.go
+++ b/projects_test.go
@@ -952,6 +952,178 @@ func TestGetProjectApprovalRules(t *testing.T) {
 	}
 }
 
+func TestGetProjectApprovalRule(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/1/approval_rules/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprint(w, `{
+			"id": 1,
+			"name": "security",
+			"rule_type": "regular",
+			"eligible_approvers": [
+				{
+					"id": 5,
+					"name": "John Doe",
+					"username": "jdoe",
+					"state": "active",
+					"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+					"web_url": "http://localhost/jdoe"
+				},
+				{
+					"id": 50,
+					"name": "Group Member 1",
+					"username": "group_member_1",
+					"state": "active",
+					"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+					"web_url": "http://localhost/group_member_1"
+				}
+			],
+			"approvals_required": 3,
+			"users": [
+				{
+					"id": 5,
+					"name": "John Doe",
+					"username": "jdoe",
+					"state": "active",
+					"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+					"web_url": "http://localhost/jdoe"
+				}
+			],
+			"groups": [
+				{
+					"id": 5,
+					"name": "group1",
+					"path": "group1",
+					"description": "",
+					"visibility": "public",
+					"lfs_enabled": false,
+					"avatar_url": null,
+					"web_url": "http://localhost/groups/group1",
+					"request_access_enabled": false,
+					"full_name": "group1",
+					"full_path": "group1",
+					"parent_id": null,
+					"ldap_cn": null,
+					"ldap_access": null
+				}
+			],
+			"protected_branches": [
+					{
+					"id": 1,
+					"name": "master",
+					"push_access_levels": [
+						{
+						"access_level": 30,
+						"access_level_description": "Developers + Maintainers"
+						}
+					],
+					"merge_access_levels": [
+						{
+						"access_level": 30,
+						"access_level_description": "Developers + Maintainers"
+						}
+					],
+					"unprotect_access_levels": [
+						{
+						"access_level": 40,
+						"access_level_description": "Maintainers"
+						}
+					],
+					"code_owner_approval_required": false
+					}
+			],
+			"contains_hidden_groups": false
+		}`)
+	})
+
+	approvals, _, err := client.Projects.GetProjectApprovalRule(1, 1)
+	if err != nil {
+		t.Errorf("Projects.GetProjectApprovalRule returned error: %v", err)
+	}
+
+	want := &ProjectApprovalRule{
+		ID:       1,
+		Name:     "security",
+		RuleType: "regular",
+		EligibleApprovers: []*BasicUser{
+			{
+				ID:        5,
+				Name:      "John Doe",
+				Username:  "jdoe",
+				State:     "active",
+				AvatarURL: "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+				WebURL:    "http://localhost/jdoe",
+			},
+			{
+				ID:        50,
+				Name:      "Group Member 1",
+				Username:  "group_member_1",
+				State:     "active",
+				AvatarURL: "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+				WebURL:    "http://localhost/group_member_1",
+			},
+		},
+		ApprovalsRequired: 3,
+		Users: []*BasicUser{
+			{
+				ID:        5,
+				Name:      "John Doe",
+				Username:  "jdoe",
+				State:     "active",
+				AvatarURL: "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+				WebURL:    "http://localhost/jdoe",
+			},
+		},
+		Groups: []*Group{
+			{
+				ID:                   5,
+				Name:                 "group1",
+				Path:                 "group1",
+				Description:          "",
+				Visibility:           PublicVisibility,
+				LFSEnabled:           false,
+				AvatarURL:            "",
+				WebURL:               "http://localhost/groups/group1",
+				RequestAccessEnabled: false,
+				FullName:             "group1",
+				FullPath:             "group1",
+			},
+		},
+		ProtectedBranches: []*ProtectedBranch{
+			{
+				ID:   1,
+				Name: "master",
+				PushAccessLevels: []*BranchAccessDescription{
+					{
+						AccessLevel:            30,
+						AccessLevelDescription: "Developers + Maintainers",
+					},
+				},
+				MergeAccessLevels: []*BranchAccessDescription{
+					{
+						AccessLevel:            30,
+						AccessLevelDescription: "Developers + Maintainers",
+					},
+				},
+				UnprotectAccessLevels: []*BranchAccessDescription{
+					{
+						AccessLevel:            40,
+						AccessLevelDescription: "Maintainers",
+					},
+				},
+				AllowForcePush:            false,
+				CodeOwnerApprovalRequired: false,
+			},
+		},
+	}
+
+	if !reflect.DeepEqual(want, approvals) {
+		t.Errorf("Projects.GetProjectApprovalRule returned %+v, want %+v", approvals, want)
+	}
+}
+
 func TestCreateProjectApprovalRule(t *testing.T) {
 	mux, server, client := setup(t)
 	defer teardown(server)
@@ -1161,13 +1333,13 @@ func TestCreateProjectApprovalRuleEligibleApprovers(t *testing.T) {
 	}
 
 	want := &ProjectApprovalRule{
-		ID:       1,
-		Name:     "Any name",
-		RuleType: "any_approver",
+		ID:                1,
+		Name:              "Any name",
+		RuleType:          "any_approver",
 		EligibleApprovers: []*BasicUser{},
 		ApprovalsRequired: 1,
-		Users: []*BasicUser{},
-		Groups: []*Group{},
+		Users:             []*BasicUser{},
+		Groups:            []*Group{},
 		ProtectedBranches: []*ProtectedBranch{},
 	}
 
diff --git a/users_test.go b/users_test.go
index edfc400..ddb9f7e 100644
--- a/users_test.go
+++ b/users_test.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"reflect"
 	"testing"
 	"time"
 
@@ -613,38 +612,3 @@ func TestGetMemberships(t *testing.T) {
 	want := []*UserMembership{{SourceID: 1, SourceName: "Project one", SourceType: "Project", AccessLevel: 20}, {SourceID: 3, SourceName: "Group three", SourceType: "Namespace", AccessLevel: 20}}
 	assert.Equal(t, want, memberships)
 }
-
-func TestGetSingleSSHKeyForUser(t *testing.T) {
-	mux, server, client := setup(t)
-	defer teardown(server)
-
-	mux.HandleFunc("/api/v4/users/1/keys/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, http.MethodGet)
-		fmt.Fprint(w, `
-		{
-			"id": 1,
-			"title": "Public key",
-			"key": "ssh-rsa AAAA...",
-			"created_at": "2014-08-01T14:47:39.080Z"
-		  }
-`)
-	})
-
-	sshKey, _, err := client.Users.GetSSHKeyForUser(1, 1)
-	if err != nil {
-		t.Errorf("Users.GetSSHKeyForUser returned an error: %v", err)
-	}
-
-	wantCreatedAt := time.Date(2014, 8, 1, 14, 47, 39, 80000000, time.UTC)
-
-	want := &SSHKey{
-		ID:        1,
-		Title:     "Public key",
-		Key:       "ssh-rsa AAAA...",
-		CreatedAt: &wantCreatedAt,
-	}
-
-	if !reflect.DeepEqual(want, sshKey) {
-		t.Errorf("Users.GetSSHKeyForUser returned %+v, want %+v", sshKey, want)
-	}
-}