Repository: xanzy/go-gitlab
Pull request #1206 (2021-09-04 14:40:57)

Added Tests for Namespaces
According to Docs: https://docs.gitlab.com/13.12/ee/api/namespaces.html and https://docs.gitlab.com/ee/api/namespaces.html


Patch:
diff --git a/README.md b/README.md
index beb003c..3c8e401 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,6 @@ to add new and/or missing endpoints. Currently the following services are suppor
 - [x] Notes (comments)
 - [x] Notification Settings
 - [x] Open Source License Templates
-- [x] Pages
 - [x] Pages Domains
 - [x] Personal Access Tokens
 - [x] Pipeline Schedules
diff --git a/event_webhook_types.go b/event_webhook_types.go
index 040100f..638b14b 100644
--- a/event_webhook_types.go
+++ b/event_webhook_types.go
@@ -499,7 +499,6 @@ type MergeEvent struct {
 		TimeEstimate             int          `json:"time_estimate"`
 		Source                   *Repository  `json:"source"`
 		Target                   *Repository  `json:"target"`
-		HeadPipelineID           *int         `json:"head_pipeline_id"`
 		LastCommit               struct {
 			ID        string     `json:"id"`
 			Message   string     `json:"message"`
@@ -623,19 +622,17 @@ func (p *MergeParams) UnmarshalJSON(b []byte) error {
 type PipelineEvent struct {
 	ObjectKind       string `json:"object_kind"`
 	ObjectAttributes struct {
-		ID             int      `json:"id"`
-		Ref            string   `json:"ref"`
-		Tag            bool     `json:"tag"`
-		SHA            string   `json:"sha"`
-		BeforeSHA      string   `json:"before_sha"`
-		Source         string   `json:"source"`
-		Status         string   `json:"status"`
-		DetailedStatus string   `json:"detailed_status"`
-		Stages         []string `json:"stages"`
-		CreatedAt      string   `json:"created_at"`
-		FinishedAt     string   `json:"finished_at"`
-		Duration       int      `json:"duration"`
-		QueuedDuration int      `json:"queued_duration"`
+		ID         int      `json:"id"`
+		Ref        string   `json:"ref"`
+		Tag        bool     `json:"tag"`
+		SHA        string   `json:"sha"`
+		BeforeSHA  string   `json:"before_sha"`
+		Source     string   `json:"source"`
+		Status     string   `json:"status"`
+		Stages     []string `json:"stages"`
+		CreatedAt  string   `json:"created_at"`
+		FinishedAt string   `json:"finished_at"`
+		Duration   int      `json:"duration"`
 	} `json:"object_attributes"`
 	MergeRequest struct {
 		ID                 int    `json:"id"`
diff --git a/gitlab.go b/gitlab.go
index 3dc0426..ecd18fc 100644
--- a/gitlab.go
+++ b/gitlab.go
@@ -58,7 +58,6 @@ type authType int
 // GitLab API docs: https://docs.gitlab.com/ce/api/
 const (
 	basicAuth authType = iota
-	jobToken
 	oAuthToken
 	privateToken
 )
@@ -151,7 +150,6 @@ type Client struct {
 	Notes                 *NotesService
 	NotificationSettings  *NotificationSettingsService
 	Packages              *PackagesService
-	Pages                 *PagesService
 	PagesDomains          *PagesDomainsService
 	PipelineSchedules     *PipelineSchedulesService
 	PipelineTriggers      *PipelineTriggersService
@@ -231,18 +229,6 @@ func NewBasicAuthClient(username, password string, options ...ClientOptionFunc)
 	return client, nil
 }
 
-// NewJobClient returns a new GitLab API client. To use API methods which require
-// authentication, provide a valid job token.
-func NewJobClient(token string, options ...ClientOptionFunc) (*Client, error) {
-	client, err := newClient(options...)
-	if err != nil {
-		return nil, err
-	}
-	client.authType = jobToken
-	client.token = token
-	return client, nil
-}
-
 // NewOAuthClient returns a new GitLab API client. To use API methods which
 // require authentication, provide a valid oauth token.
 func NewOAuthClient(token string, options ...ClientOptionFunc) (*Client, error) {
@@ -338,7 +324,6 @@ func newClient(options ...ClientOptionFunc) (*Client, error) {
 	c.Notes = &NotesService{client: c}
 	c.NotificationSettings = &NotificationSettingsService{client: c}
 	c.Packages = &PackagesService{client: c}
-	c.Pages = &PagesService{client: c}
 	c.PagesDomains = &PagesDomainsService{client: c}
 	c.PipelineSchedules = &PipelineSchedulesService{client: c}
 	c.PipelineTriggers = &PipelineTriggersService{client: c}
@@ -660,8 +645,6 @@ func (c *Client) Do(req *retryablehttp.Request, v interface{}) (*Response, error
 			}
 		}
 		req.Header.Set("Authorization", "Bearer "+basicAuthToken)
-	case jobToken:
-		req.Header.Set("JOB-TOKEN", c.token)
 	case oAuthToken:
 		req.Header.Set("Authorization", "Bearer "+c.token)
 	case privateToken:
diff --git a/namespaces.go b/namespaces.go
index 420da15..a47c770 100644
--- a/namespaces.go
+++ b/namespaces.go
@@ -19,6 +19,7 @@ package gitlab
 import (
 	"fmt"
 	"net/http"
+	"time"
 )
 
 // NamespacesService handles communication with the namespace related methods
@@ -33,13 +34,21 @@ type NamespacesService struct {
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html
 type Namespace struct {
-	ID                          int    `json:"id"`
-	Name                        string `json:"name"`
-	Path                        string `json:"path"`
-	Kind                        string `json:"kind"`
-	FullPath                    string `json:"full_path"`
-	ParentID                    int    `json:"parent_id"`
-	MembersCountWithDescendants int    `json:"members_count_with_descendants"`
+	ID                          int        `json:"id"`
+	Name                        string     `json:"name"`
+	Path                        string     `json:"path"`
+	Kind                        string     `json:"kind"`
+	FullPath                    string     `json:"full_path"`
+	ParentID                    int        `json:"parent_id"`
+	AvatarURL                   *string    `json:"avatar_url"`
+	WebURL                      string     `json:"web_url"`
+	MembersCountWithDescendants int        `json:"members_count_with_descendants"`
+	BillableMembersCount        int        `json:"billable_members_count"`
+	Plan                        string     `json:"plan"`
+	TrialEndsOn                 *time.Time `json:"trial_ends_on"`
+	Trial                       bool       `json:"trial"`
+	MaxSeatsUsed                *int       `json:"max_seats_used"`
+	SeatsInUse                  *int       `json:"seats_in_use"`
 }
 
 func (n Namespace) String() string {
@@ -51,7 +60,8 @@ func (n Namespace) String() string {
 // GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html#list-namespaces
 type ListNamespacesOptions struct {
 	ListOptions
-	Search *string `url:"search,omitempty" json:"search,omitempty"`
+	Search    *string `url:"search,omitempty" json:"search,omitempty"`
+	OwnedOnly *bool   `url:"owned_only,omitempty" json:"owned_only,omitempty"`
 }
 
 // ListNamespaces gets a list of projects accessible by the authenticated user.
@@ -121,3 +131,45 @@ func (s *NamespacesService) GetNamespace(id interface{}, options ...RequestOptio
 
 	return n, resp, err
 }
+
+// NamespaceExistance represents a namespace exists result.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/namespaces.html#get-existence-of-a-namespace
+type NamespaceExistance struct {
+	Exists   bool     `json:"exists"`
+	Suggests []string `json:"suggests"`
+}
+
+// NamespaceExistsOptions represents the available NamespaceExists() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/namespaces.html#get-existence-of-a-namespace
+type NamespaceExistsOptions struct {
+	ParentID *int `url:"parent_id,omitempty" json:"parent_id,omitempty"`
+}
+
+// NamespaceExists checks the existence of a namespace.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/namespaces.html#get-existence-of-a-namespace
+func (s *NamespacesService) NamespaceExists(id interface{}, opt *NamespaceExistsOptions, options ...RequestOptionFunc) (*NamespaceExistance, *Response, error) {
+	namespace, err := parseID(id)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("namespaces/%s/exists", namespace)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(NamespaceExistance)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
diff --git a/pages.go b/pages.go
deleted file mode 100644
index 167551b..0000000
--- a/pages.go
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Copyright 2021, Sander van Harmelen
-//
-// 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"
-)
-
-type PagesService struct {
-	client *Client
-}
-
-// UnpublishPages unpublished pages. The user must have admin privileges.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ee/api/pages.html#unpublish-pages
-func (s *PagesService) UnpublishPages(gid interface{}, options ...RequestOptionFunc) (*Response, error) {
-	page, err := parseID(gid)
-	if err != nil {
-		return nil, err
-	}
-	u := fmt.Sprintf("projects/%s/pages", pathEscape(page))
-
-	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
-	if err != nil {
-		return nil, err
-	}
-
-	return s.client.Do(req, nil)
-}
diff --git a/protected_branches.go b/protected_branches.go
index 11ef66c..af3e7f2 100644
--- a/protected_branches.go
+++ b/protected_branches.go
@@ -138,7 +138,6 @@ type ProtectRepositoryBranchesOptions struct {
 type BranchPermissionOptions struct {
 	UserID      *int              `url:"user_id,omitempty" json:"user_id,omitempty"`
 	GroupID     *int              `url:"group_id,omitempty" json:"group_id,omitempty"`
-	DeployKeyID *int              `url:"deploy_key_id,omitempty" json:"deploy_key_id,omitempty"`
 	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
 }
 
diff --git a/types.go b/types.go
index 9b95aad..e1a1ed9 100644
--- a/types.go
+++ b/types.go
@@ -478,8 +478,8 @@ type SquashOptionValue string
 const (
 	SquashOptionNever      SquashOptionValue = "never"
 	SquashOptionAlways     SquashOptionValue = "always"
-	SquashOptionDefaultOff SquashOptionValue = "default_off"
-	SquashOptionDefaultOn  SquashOptionValue = "default_on"
+	SquashOptionDefaultOff SquashOptionValue = "default_on"
+	SquashOptionDefaultOn  SquashOptionValue = "default_off"
 )
 
 // SquashOption is a helper routine that allocates a new SquashOptionValue


Test patch:
diff --git a/event_webhook_types_test.go b/event_webhook_types_test.go
index 02fd894..14d6683 100644
--- a/event_webhook_types_test.go
+++ b/event_webhook_types_test.go
@@ -301,15 +301,7 @@ func TestPipelineEventUnmarshal(t *testing.T) {
 	}
 
 	if event.ObjectAttributes.ID != 31 {
-		t.Errorf("ObjectAttributes.ID is %v, want %v", event.ObjectAttributes.ID, 1977)
-	}
-
-	if event.ObjectAttributes.DetailedStatus != "passed" {
-		t.Errorf("ObjectAttributes.DetailedStatus is %v, want %v", event.ObjectAttributes.DetailedStatus, "passed")
-	}
-
-	if event.ObjectAttributes.QueuedDuration != 12 {
-		t.Errorf("ObjectAttributes.QueuedDuration is %v, want %v", event.ObjectAttributes.QueuedDuration, 12)
+		t.Errorf("ObjectAttributes is %v, want %v", event.ObjectAttributes.ID, 1977)
 	}
 
 	if event.User.ID != 42 {
diff --git a/namespaces_test.go b/namespaces_test.go
new file mode 100644
index 0000000..80be019
--- /dev/null
+++ b/namespaces_test.go
@@ -0,0 +1,279 @@
+//
+// Copyright 2021, Sander van Harmelen
+//
+// 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"
+	"reflect"
+	"testing"
+)
+
+func TestListNamespaces(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/namespaces", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `[
+			{
+			  "id": 1,
+			  "name": "user1",
+			  "path": "user1",
+			  "kind": "user",
+			  "full_path": "user1",
+			  "avatar_url": "https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+			  "web_url": "https://gitlab.example.com/user1",
+			  "billable_members_count": 1,
+			  "plan": "default",
+			  "trial_ends_on": null,
+			  "trial": false
+			},
+			{
+			  "id": 2,
+			  "name": "group1",
+			  "path": "group1",
+			  "kind": "group",
+			  "full_path": "group1",
+			  "web_url": "https://gitlab.example.com/groups/group1",
+			  "members_count_with_descendants": 2,
+			  "billable_members_count": 2,
+			  "plan": "default",
+			  "trial_ends_on": null,
+			  "trial": false
+			},
+			{
+			  "id": 3,
+			  "name": "bar",
+			  "path": "bar",
+			  "kind": "group",
+			  "full_path": "foo/bar",
+			  "parent_id": 9,
+			  "web_url": "https://gitlab.example.com/groups/foo/bar",
+			  "members_count_with_descendants": 5,
+			  "billable_members_count": 5,
+			  "plan": "default",
+			  "trial_ends_on": null,
+			  "trial": false
+			}
+		  ]`)
+	})
+
+	testCases := []struct {
+		event     string
+		search    *string
+		ownedOnly *bool
+	}{
+		{"with_nothing", nil, nil},
+		{"with_search", String("foobar"), nil},
+		{"with_owned_only", nil, Bool(false)},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.event, func(t *testing.T) {
+			namespaces, _, err := client.Namespaces.ListNamespaces(&ListNamespacesOptions{Search: tc.search, OwnedOnly: tc.ownedOnly})
+			if err != nil {
+				t.Errorf("Namespaces.ListNamespaces returned error: %v", err)
+			}
+
+			want := []*Namespace{
+				{
+					ID:                   1,
+					Name:                 "user1",
+					Path:                 "user1",
+					Kind:                 "user",
+					FullPath:             "user1",
+					AvatarURL:            String("https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon"),
+					WebURL:               "https://gitlab.example.com/user1",
+					Plan:                 "default",
+					BillableMembersCount: 1,
+					TrialEndsOn:          nil,
+					Trial:                false,
+				},
+				{
+					ID:                          2,
+					Name:                        "group1",
+					Path:                        "group1",
+					Kind:                        "group",
+					FullPath:                    "group1",
+					AvatarURL:                   nil,
+					WebURL:                      "https://gitlab.example.com/groups/group1",
+					MembersCountWithDescendants: 2,
+					BillableMembersCount:        2,
+					Plan:                        "default",
+					TrialEndsOn:                 nil,
+					Trial:                       false,
+				},
+				{
+					ID:                          3,
+					Name:                        "bar",
+					Path:                        "bar",
+					Kind:                        "group",
+					FullPath:                    "foo/bar",
+					ParentID:                    9,
+					AvatarURL:                   nil,
+					WebURL:                      "https://gitlab.example.com/groups/foo/bar",
+					MembersCountWithDescendants: 5,
+					BillableMembersCount:        5,
+					Plan:                        "default",
+					TrialEndsOn:                 nil,
+					Trial:                       false,
+				},
+			}
+
+			if !reflect.DeepEqual(namespaces, want) {
+				t.Errorf("Namespaces.ListNamespaces returned \ngot:\n%v\nwant:\n%v", Stringify(namespaces), Stringify(want))
+			}
+		})
+	}
+}
+
+func TestGetNamespace(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/namespaces/2", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `{
+			"id": 2,
+			"name": "group1",
+			"path": "group1",
+			"kind": "group",
+			"full_path": "group1",
+			"avatar_url": null,
+			"web_url": "https://gitlab.example.com/groups/group1",
+			"members_count_with_descendants": 2,
+			"billable_members_count": 2,
+			"max_seats_used": 0,
+			"seats_in_use": 0,
+			"plan": "default",
+			"trial_ends_on": null,
+			"trial": false
+		  }`)
+	})
+
+	namespace, _, err := client.Namespaces.GetNamespace(2)
+	if err != nil {
+		t.Errorf("Namespaces.GetNamespace returned error: %v", err)
+	}
+
+	want := &Namespace{
+		ID:                          2,
+		Name:                        "group1",
+		Path:                        "group1",
+		Kind:                        "group",
+		FullPath:                    "group1",
+		AvatarURL:                   nil,
+		WebURL:                      "https://gitlab.example.com/groups/group1",
+		MembersCountWithDescendants: 2,
+		BillableMembersCount:        2,
+		MaxSeatsUsed:                Int(0),
+		SeatsInUse:                  Int(0),
+		Plan:                        "default",
+		TrialEndsOn:                 nil,
+		Trial:                       false,
+	}
+
+	if !reflect.DeepEqual(namespace, want) {
+		t.Errorf("Namespaces.ListNamespaces returned \ngot:\n%v\nwant:\n%v", Stringify(namespace), Stringify(want))
+	}
+}
+
+func TestNamespaceExists(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/namespaces/my-group/exists", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `{
+			"exists": true,
+			"suggests": [
+				"my-group1"
+			]
+		}`)
+	})
+
+	opt := &NamespaceExistsOptions{
+		ParentID: Int(1),
+	}
+	exists, _, err := client.Namespaces.NamespaceExists("my-group", opt)
+	if err != nil {
+		t.Errorf("Namespaces.NamespaceExists returned error: %v", err)
+	}
+
+	want := &NamespaceExistance{
+		Exists:   true,
+		Suggests: []string{"my-group1"},
+	}
+	if !reflect.DeepEqual(exists, want) {
+		t.Errorf("Namespaces.NamespaceExists returned \ngot:\n%v\nwant:\n%v", Stringify(exists), Stringify(want))
+	}
+}
+
+func TestSearchNamespace(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/namespaces", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		fmt.Fprintf(w, `[
+			{
+			  "id": 4,
+			  "name": "twitter",
+			  "path": "twitter",
+			  "kind": "group",
+			  "full_path": "twitter",
+			  "avatar_url": null,
+			  "web_url": "https://gitlab.example.com/groups/twitter",
+			  "members_count_with_descendants": 2,
+			  "billable_members_count": 2,
+			  "max_seats_used": 0,
+			  "seats_in_use": 0,
+			  "plan": "default",
+			  "trial_ends_on": null,
+			  "trial": false
+			}
+		  ]`)
+	})
+
+	namespaces, _, err := client.Namespaces.SearchNamespace("?search=twitter")
+	if err != nil {
+		t.Errorf("Namespaces.SearchNamespaces returned error: %v", err)
+	}
+
+	want := []*Namespace{
+		{
+			ID:                          4,
+			Name:                        "twitter",
+			Path:                        "twitter",
+			Kind:                        "group",
+			FullPath:                    "twitter",
+			AvatarURL:                   nil,
+			WebURL:                      "https://gitlab.example.com/groups/twitter",
+			MembersCountWithDescendants: 2,
+			BillableMembersCount:        2,
+			MaxSeatsUsed:                Int(0),
+			SeatsInUse:                  Int(0),
+			Plan:                        "default",
+			TrialEndsOn:                 nil,
+			Trial:                       false,
+		},
+	}
+	if !reflect.DeepEqual(namespaces, want) {
+		t.Errorf("Namespaces.SearchNamespaces returned \ngot:\n%v\nwant:\n%v", Stringify(namespaces), Stringify(want))
+	}
+}
diff --git a/pages_test.go b/pages_test.go
deleted file mode 100644
index 1d92ffb..0000000
--- a/pages_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-// Copyright 2021, Sander van Harmelen
-//
-// 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 (
-	"net/http"
-	"testing"
-)
-
-func TestUnpublishPages(t *testing.T) {
-	mux, server, client := setup(t)
-	defer teardown(server)
-
-	mux.HandleFunc("/api/v4/projects/2/pages", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, http.MethodDelete)
-	})
-
-	_, err := client.Pages.UnpublishPages(2)
-	if err != nil {
-		t.Errorf("Pages.UnpublishPages returned error: %v", err)
-	}
-}
diff --git a/testdata/webhooks/pipeline.json b/testdata/webhooks/pipeline.json
index 55b3d50..2b7ad47 100644
--- a/testdata/webhooks/pipeline.json
+++ b/testdata/webhooks/pipeline.json
@@ -8,7 +8,6 @@
      "before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",
      "source": "merge_request_event",
      "status": "success",
-     "detailed_status": "passed",
      "stages":[
         "build",
         "test",
@@ -17,7 +16,6 @@
      "created_at": "2016-08-12 15:23:28 UTC",
      "finished_at": "2016-08-12 15:26:29 UTC",
      "duration": 63,
-     "queued_duration": 12,
      "variables": [
        {
          "key": "NESTOR_PROD_ENVIRONMENT",