Loading testing/workitems_mock.go +45 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,51 @@ func (c *MockWorkItemsServiceInterfaceGetWorkItemCall) DoAndReturn(f func(string return c } // ListWorkItemTypes mocks base method. func (m *MockWorkItemsServiceInterface) ListWorkItemTypes(namespacePath string, opt *gitlab.ListWorkItemTypesOptions, options ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error) { m.ctrl.T.Helper() varargs := []any{namespacePath, opt} for _, a := range options { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "ListWorkItemTypes", varargs...) ret0, _ := ret[0].([]gitlab.WorkItemType) ret1, _ := ret[1].(*gitlab.Response) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } // ListWorkItemTypes indicates an expected call of ListWorkItemTypes. func (mr *MockWorkItemsServiceInterfaceMockRecorder) ListWorkItemTypes(namespacePath, opt any, options ...any) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { mr.mock.ctrl.T.Helper() varargs := append([]any{namespacePath, opt}, options...) call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWorkItemTypes", reflect.TypeOf((*MockWorkItemsServiceInterface)(nil).ListWorkItemTypes), varargs...) return &MockWorkItemsServiceInterfaceListWorkItemTypesCall{Call: call} } // MockWorkItemsServiceInterfaceListWorkItemTypesCall wrap *gomock.Call type MockWorkItemsServiceInterfaceListWorkItemTypesCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) Return(arg0 []gitlab.WorkItemType, arg1 *gitlab.Response, arg2 error) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) Do(f func(string, *gitlab.ListWorkItemTypesOptions, ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error)) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) DoAndReturn(f func(string, *gitlab.ListWorkItemTypesOptions, ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error)) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { c.Call = c.Call.DoAndReturn(f) return c } // ListWorkItems mocks base method. func (m *MockWorkItemsServiceInterface) ListWorkItems(fullPath string, opt *gitlab.ListWorkItemsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.WorkItem, *gitlab.Response, error) { m.ctrl.T.Helper() Loading workitems.go +124 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ type ( ListWorkItems(fullPath string, opt *ListWorkItemsOptions, options ...RequestOptionFunc) ([]*WorkItem, *Response, error) UpdateWorkItem(fullPath string, iid int64, opt *UpdateWorkItemOptions, options ...RequestOptionFunc) (*WorkItem, *Response, error) DeleteWorkItem(fullPath string, iid int64, options ...RequestOptionFunc) (*Response, error) ListWorkItemTypes(namespacePath string, opt *ListWorkItemTypesOptions, options ...RequestOptionFunc) ([]WorkItemType, *Response, error) } // WorkItemsService handles communication with the work item related methods Loading Loading @@ -532,6 +533,103 @@ func (s *WorkItemsService) ListWorkItems(fullPath string, opt *ListWorkItemsOpti return ret, resp, nil } var listWorkItemTypesTemplate = template.Must(template.New("listWorkItemTypes").Parse(` query ListWorkItemTypes( $namespacePath: ID!, $name: IssueType, $onlyAvailable: Boolean, $after: String, $before: String, $first: Int, $last: Int ) { namespace(fullPath: $namespacePath) { workItemTypes( name: $name, onlyAvailable: $onlyAvailable, after: $after, before: $before, first: $first, last: $last ) { nodes { id name enabled } pageInfo { endCursor hasNextPage startCursor hasPreviousPage } } } } `)) // ListWorkItemTypes lists all work item types (system-defined and custom) // for a given namespace. // // GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype // // Experimental: The Work Items API is a work in progress and may introduce // breaking changes even between minor versions. func (s *WorkItemsService) ListWorkItemTypes( namespacePath string, opt *ListWorkItemTypesOptions, options ...RequestOptionFunc, ) ([]WorkItemType, *Response, error) { var queryBuilder strings.Builder if opt == nil { opt = &ListWorkItemTypesOptions{} } if err := listWorkItemTypesTemplate.Execute(&queryBuilder, nil); err != nil { return nil, nil, err } vars := map[string]any{ "namespacePath": namespacePath, "name": opt.Name, "onlyAvailable": opt.OnlyAvailable, "after": opt.After, "before": opt.Before, "first": opt.First, "last": opt.Last, } query := GraphQLQuery{ Query: queryBuilder.String(), Variables: vars, } var result struct { Data struct { Namespace struct { WorkItemTypes connectionGQL[WorkItemType] `json:"workItemTypes"` } `json:"namespace"` } GenericGraphQLErrors } resp, err := s.client.GraphQL.Do(query, &result, options...) if err != nil { return nil, resp, err } if len(result.Errors) != 0 { return nil, resp, &GraphQLResponseError{ Err: errors.New("GraphQL query failed"), Errors: result.GenericGraphQLErrors, } } resp.PageInfo = &result.Data.Namespace.WorkItemTypes.PageInfo return result.Data.Namespace.WorkItemTypes.Nodes, resp, nil } // CreateWorkItemOptions represents the available CreateWorkItem() options. // // GitLab API docs: Loading Loading @@ -1538,6 +1636,32 @@ func (w *workItemWidgetWeightGQL) unwrap() *int64 { return w.Weight } // WorkItemType represents a GitLab work item type. // // GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype // // Experimental: The Work Items API is a work in progress and may introduce // breaking changes even between minor versions. type WorkItemType struct { ID WorkItemTypeID `json:"id"` Name string `json:"name"` Enabled bool `json:"enabled"` } // ListWorkItemTypesOptions specifies the optional parameters to the // WorkItemsService.ListWorkItemTypes method. // // Experimental: The Work Items API is a work in progress and may introduce // breaking changes even between minor versions. type ListWorkItemTypesOptions struct { Name *string `json:"name,omitempty"` OnlyAvailable *bool `json:"onlyAvailable,omitempty"` After *string `json:"after,omitempty"` Before *string `json:"before,omitempty"` First *int64 `json:"first,omitempty"` Last *int64 `json:"last,omitempty"` } // WorkItemTypeID represents the global ID of a work item type. // // GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype Loading workitems_test.go +148 −0 Original line number Diff line number Diff line Loading @@ -1424,3 +1424,151 @@ func validateSchema(schema *graphql.Schema, query GraphQLQuery) error { return errs } func setupWorkItemTypesHandler(t *testing.T, mux *http.ServeMux, schema *graphql.Schema, response io.Reader) { t.Helper() mux.HandleFunc("/api/graphql", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() testMethod(t, r, http.MethodPost) var q GraphQLQuery if err := json.NewDecoder(r.Body).Decode(&q); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := validateSchema(schema, q); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") _, _ = io.Copy(w, response) }) } func TestListWorkItemTypes_SystemAndCustomTypes(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [ {"id": "gid://gitlab/WorkItems::Type/1", "name": "Issue", "enabled": true}, {"id": "gid://gitlab/WorkItems::Type/5", "name": "Task", "enabled": true}, {"id": "gid://gitlab/WorkItems::Type/99", "name": "CustomType", "enabled": true} ], "pageInfo": { "endCursor": "cursor123", "hasNextPage": false, "startCursor": "cursor000", "hasPreviousPage": false } } } } }`)) got, resp, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", &ListWorkItemTypesOptions{}) require.NoError(t, err) assert.Equal(t, []WorkItemType{ {ID: WorkItemTypeIssue, Name: "Issue", Enabled: true}, {ID: WorkItemTypeTask, Name: "Task", Enabled: true}, {ID: "gid://gitlab/WorkItems::Type/99", Name: "CustomType", Enabled: true}, }, got) assert.Equal(t, &PageInfo{ EndCursor: "cursor123", HasNextPage: false, StartCursor: "cursor000", HasPreviousPage: false, }, resp.PageInfo) } func TestListWorkItemTypes_FilterByName(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [ {"id": "gid://gitlab/WorkItems::Type/1", "name": "Issue", "enabled": true} ], "pageInfo": { "endCursor": "", "hasNextPage": false, "startCursor": "", "hasPreviousPage": false } } } } }`)) got, _, err := client.WorkItems.ListWorkItemTypes( "gitlab-org/gitlab", &ListWorkItemTypesOptions{Name: Ptr("ISSUE")}, ) require.NoError(t, err) assert.Equal(t, []WorkItemType{ {ID: WorkItemTypeIssue, Name: "Issue", Enabled: true}, }, got) } func TestListWorkItemTypes_EmptyResponse(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [], "pageInfo": { "endCursor": "", "hasNextPage": false, "startCursor": "", "hasPreviousPage": false } } } } }`)) got, _, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", &ListWorkItemTypesOptions{}) require.NoError(t, err) assert.Equal(t, []WorkItemType{}, got) } func TestListWorkItemTypes_NilOptDoesNotPanic(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [], "pageInfo": { "endCursor": "", "hasNextPage": false, "startCursor": "", "hasPreviousPage": false } } } } }`)) got, _, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", nil) require.NoError(t, err) assert.Equal(t, []WorkItemType{}, got) } Loading
testing/workitems_mock.go +45 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,51 @@ func (c *MockWorkItemsServiceInterfaceGetWorkItemCall) DoAndReturn(f func(string return c } // ListWorkItemTypes mocks base method. func (m *MockWorkItemsServiceInterface) ListWorkItemTypes(namespacePath string, opt *gitlab.ListWorkItemTypesOptions, options ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error) { m.ctrl.T.Helper() varargs := []any{namespacePath, opt} for _, a := range options { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "ListWorkItemTypes", varargs...) ret0, _ := ret[0].([]gitlab.WorkItemType) ret1, _ := ret[1].(*gitlab.Response) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } // ListWorkItemTypes indicates an expected call of ListWorkItemTypes. func (mr *MockWorkItemsServiceInterfaceMockRecorder) ListWorkItemTypes(namespacePath, opt any, options ...any) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { mr.mock.ctrl.T.Helper() varargs := append([]any{namespacePath, opt}, options...) call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWorkItemTypes", reflect.TypeOf((*MockWorkItemsServiceInterface)(nil).ListWorkItemTypes), varargs...) return &MockWorkItemsServiceInterfaceListWorkItemTypesCall{Call: call} } // MockWorkItemsServiceInterfaceListWorkItemTypesCall wrap *gomock.Call type MockWorkItemsServiceInterfaceListWorkItemTypesCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) Return(arg0 []gitlab.WorkItemType, arg1 *gitlab.Response, arg2 error) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) Do(f func(string, *gitlab.ListWorkItemTypesOptions, ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error)) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn func (c *MockWorkItemsServiceInterfaceListWorkItemTypesCall) DoAndReturn(f func(string, *gitlab.ListWorkItemTypesOptions, ...gitlab.RequestOptionFunc) ([]gitlab.WorkItemType, *gitlab.Response, error)) *MockWorkItemsServiceInterfaceListWorkItemTypesCall { c.Call = c.Call.DoAndReturn(f) return c } // ListWorkItems mocks base method. func (m *MockWorkItemsServiceInterface) ListWorkItems(fullPath string, opt *gitlab.ListWorkItemsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.WorkItem, *gitlab.Response, error) { m.ctrl.T.Helper() Loading
workitems.go +124 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ type ( ListWorkItems(fullPath string, opt *ListWorkItemsOptions, options ...RequestOptionFunc) ([]*WorkItem, *Response, error) UpdateWorkItem(fullPath string, iid int64, opt *UpdateWorkItemOptions, options ...RequestOptionFunc) (*WorkItem, *Response, error) DeleteWorkItem(fullPath string, iid int64, options ...RequestOptionFunc) (*Response, error) ListWorkItemTypes(namespacePath string, opt *ListWorkItemTypesOptions, options ...RequestOptionFunc) ([]WorkItemType, *Response, error) } // WorkItemsService handles communication with the work item related methods Loading Loading @@ -532,6 +533,103 @@ func (s *WorkItemsService) ListWorkItems(fullPath string, opt *ListWorkItemsOpti return ret, resp, nil } var listWorkItemTypesTemplate = template.Must(template.New("listWorkItemTypes").Parse(` query ListWorkItemTypes( $namespacePath: ID!, $name: IssueType, $onlyAvailable: Boolean, $after: String, $before: String, $first: Int, $last: Int ) { namespace(fullPath: $namespacePath) { workItemTypes( name: $name, onlyAvailable: $onlyAvailable, after: $after, before: $before, first: $first, last: $last ) { nodes { id name enabled } pageInfo { endCursor hasNextPage startCursor hasPreviousPage } } } } `)) // ListWorkItemTypes lists all work item types (system-defined and custom) // for a given namespace. // // GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype // // Experimental: The Work Items API is a work in progress and may introduce // breaking changes even between minor versions. func (s *WorkItemsService) ListWorkItemTypes( namespacePath string, opt *ListWorkItemTypesOptions, options ...RequestOptionFunc, ) ([]WorkItemType, *Response, error) { var queryBuilder strings.Builder if opt == nil { opt = &ListWorkItemTypesOptions{} } if err := listWorkItemTypesTemplate.Execute(&queryBuilder, nil); err != nil { return nil, nil, err } vars := map[string]any{ "namespacePath": namespacePath, "name": opt.Name, "onlyAvailable": opt.OnlyAvailable, "after": opt.After, "before": opt.Before, "first": opt.First, "last": opt.Last, } query := GraphQLQuery{ Query: queryBuilder.String(), Variables: vars, } var result struct { Data struct { Namespace struct { WorkItemTypes connectionGQL[WorkItemType] `json:"workItemTypes"` } `json:"namespace"` } GenericGraphQLErrors } resp, err := s.client.GraphQL.Do(query, &result, options...) if err != nil { return nil, resp, err } if len(result.Errors) != 0 { return nil, resp, &GraphQLResponseError{ Err: errors.New("GraphQL query failed"), Errors: result.GenericGraphQLErrors, } } resp.PageInfo = &result.Data.Namespace.WorkItemTypes.PageInfo return result.Data.Namespace.WorkItemTypes.Nodes, resp, nil } // CreateWorkItemOptions represents the available CreateWorkItem() options. // // GitLab API docs: Loading Loading @@ -1538,6 +1636,32 @@ func (w *workItemWidgetWeightGQL) unwrap() *int64 { return w.Weight } // WorkItemType represents a GitLab work item type. // // GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype // // Experimental: The Work Items API is a work in progress and may introduce // breaking changes even between minor versions. type WorkItemType struct { ID WorkItemTypeID `json:"id"` Name string `json:"name"` Enabled bool `json:"enabled"` } // ListWorkItemTypesOptions specifies the optional parameters to the // WorkItemsService.ListWorkItemTypes method. // // Experimental: The Work Items API is a work in progress and may introduce // breaking changes even between minor versions. type ListWorkItemTypesOptions struct { Name *string `json:"name,omitempty"` OnlyAvailable *bool `json:"onlyAvailable,omitempty"` After *string `json:"after,omitempty"` Before *string `json:"before,omitempty"` First *int64 `json:"first,omitempty"` Last *int64 `json:"last,omitempty"` } // WorkItemTypeID represents the global ID of a work item type. // // GitLab API docs: https://docs.gitlab.com/api/graphql/reference/#workitemtype Loading
workitems_test.go +148 −0 Original line number Diff line number Diff line Loading @@ -1424,3 +1424,151 @@ func validateSchema(schema *graphql.Schema, query GraphQLQuery) error { return errs } func setupWorkItemTypesHandler(t *testing.T, mux *http.ServeMux, schema *graphql.Schema, response io.Reader) { t.Helper() mux.HandleFunc("/api/graphql", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() testMethod(t, r, http.MethodPost) var q GraphQLQuery if err := json.NewDecoder(r.Body).Decode(&q); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := validateSchema(schema, q); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") _, _ = io.Copy(w, response) }) } func TestListWorkItemTypes_SystemAndCustomTypes(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [ {"id": "gid://gitlab/WorkItems::Type/1", "name": "Issue", "enabled": true}, {"id": "gid://gitlab/WorkItems::Type/5", "name": "Task", "enabled": true}, {"id": "gid://gitlab/WorkItems::Type/99", "name": "CustomType", "enabled": true} ], "pageInfo": { "endCursor": "cursor123", "hasNextPage": false, "startCursor": "cursor000", "hasPreviousPage": false } } } } }`)) got, resp, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", &ListWorkItemTypesOptions{}) require.NoError(t, err) assert.Equal(t, []WorkItemType{ {ID: WorkItemTypeIssue, Name: "Issue", Enabled: true}, {ID: WorkItemTypeTask, Name: "Task", Enabled: true}, {ID: "gid://gitlab/WorkItems::Type/99", Name: "CustomType", Enabled: true}, }, got) assert.Equal(t, &PageInfo{ EndCursor: "cursor123", HasNextPage: false, StartCursor: "cursor000", HasPreviousPage: false, }, resp.PageInfo) } func TestListWorkItemTypes_FilterByName(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [ {"id": "gid://gitlab/WorkItems::Type/1", "name": "Issue", "enabled": true} ], "pageInfo": { "endCursor": "", "hasNextPage": false, "startCursor": "", "hasPreviousPage": false } } } } }`)) got, _, err := client.WorkItems.ListWorkItemTypes( "gitlab-org/gitlab", &ListWorkItemTypesOptions{Name: Ptr("ISSUE")}, ) require.NoError(t, err) assert.Equal(t, []WorkItemType{ {ID: WorkItemTypeIssue, Name: "Issue", Enabled: true}, }, got) } func TestListWorkItemTypes_EmptyResponse(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [], "pageInfo": { "endCursor": "", "hasNextPage": false, "startCursor": "", "hasPreviousPage": false } } } } }`)) got, _, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", &ListWorkItemTypesOptions{}) require.NoError(t, err) assert.Equal(t, []WorkItemType{}, got) } func TestListWorkItemTypes_NilOptDoesNotPanic(t *testing.T) { t.Parallel() schema := loadSchema(t) mux, client := setup(t) setupWorkItemTypesHandler(t, mux, schema, strings.NewReader(`{ "data": { "namespace": { "workItemTypes": { "nodes": [], "pageInfo": { "endCursor": "", "hasNextPage": false, "startCursor": "", "hasPreviousPage": false } } } } }`)) got, _, err := client.WorkItems.ListWorkItemTypes("gitlab-org/gitlab", nil) require.NoError(t, err) assert.Equal(t, []WorkItemType{}, got) }