From 384a8be6273fcac02e7b697893d5228919da7ef1 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Fri, 11 Mar 2022 13:36:38 +0100 Subject: [PATCH] Add complete Tags support --- .github/workflows/go.yml | 14 ++-- README.md | 5 ++ export_test.go | 2 + newreleases.go | 6 ++ newreleases_test.go | 2 +- projects.go | 12 ++- projects_test.go | 30 ++++++- releases_test.go | 4 +- tags.go | 63 +++++++++++++++ tags_test.go | 165 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 288 insertions(+), 15 deletions(-) create mode 100644 tags.go create mode 100644 tags_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ea034a1..94ea62f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.15 + go-version: 1.17 - name: Checkout uses: actions/checkout@v1 @@ -31,13 +31,11 @@ jobs: ${{ runner.OS }}-build- ${{ runner.OS }}- - - name: Set up GolangCI-Lint - if: matrix.os == 'ubuntu-latest' - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.34.1 - - - name: GolangCI-Lint - if: matrix.os == 'ubuntu-latest' - run: golangci-lint run + - name: Lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.43.0 + args: --timeout 10m - name: Vet if: matrix.os == 'ubuntu-latest' diff --git a/README.md b/README.md index 048e752..140be9e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,11 @@ This client implements all NewReleases API features. - Get added Mattermost webhooks - Get added Rocket.Chat webhooks - Get added custom Webhooks +- List tags +- Get tag +- Add tag +- Update tag +- Delete tag - Get auth keys ## Examples diff --git a/export_test.go b/export_test.go index 6c8c87d..7ee918f 100644 --- a/export_test.go +++ b/export_test.go @@ -6,3 +6,5 @@ package newreleases const UserAgent = userAgent + +type TagOptionsRequest = tagOptionsRequest diff --git a/newreleases.go b/newreleases.go index b02bb77..fec6f7f 100644 --- a/newreleases.go +++ b/newreleases.go @@ -50,6 +50,7 @@ type Client struct { MattermostWebhooks *MattermostWebhooksService RocketchatWebhooks *RocketchatWebhooksService Webhooks *WebhooksService + Tags *TagsService } // ClientOptions holds optional parameters for the Client. @@ -96,6 +97,7 @@ func newClient(httpClient *http.Client) (c *Client) { c.MattermostWebhooks = (*MattermostWebhooksService)(&c.service) c.RocketchatWebhooks = (*RocketchatWebhooksService)(&c.service) c.Webhooks = (*WebhooksService)(&c.service) + c.Tags = (*TagsService)(&c.service) return c } @@ -252,6 +254,10 @@ type service struct { // returns a pointer to it. func Bool(v bool) (p *bool) { return &v } +// String is a helper routine that allocates a new string value to store v and +// returns a pointer to it. +func String(v string) (p *string) { return &v } + // roundTripperFunc type is an adapter to allow the use of ordinary functions as // http.RoundTripper interfaces. If f is a function with the appropriate // signature, roundTripperFunc(f) is a http.RoundTripper that calls f. diff --git a/newreleases_test.go b/newreleases_test.go index d1dcf1d..ceff3be 100644 --- a/newreleases_test.go +++ b/newreleases_test.go @@ -88,7 +88,7 @@ func newStaticHandler(body string) http.HandlerFunc { } } -func newPagedStaticHndler(pages ...string) http.HandlerFunc { +func newPagedStaticHandler(pages ...string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { page := 1 if p := r.URL.Query().Get("page"); p != "" { diff --git a/projects.go b/projects.go index 0b8e934..898b1e7 100644 --- a/projects.go +++ b/projects.go @@ -34,6 +34,8 @@ type Project struct { Exclusions []Exclusion `json:"exclude_version_regexp,omitempty"` ExcludePrereleases bool `json:"exclude_prereleases,omitempty"` ExcludeUpdated bool `json:"exclude_updated,omitempty"` + Note string `json:"note,omitempty"` + TagIDs []string `json:"tags,omitempty"` } // EmailNotification enumerates available options for email notifications. @@ -67,6 +69,7 @@ type ProjectListOptions struct { Order ProjectListOrder Reverse bool Provider string + TagID string } // ProjectListOrder enumerates available project list orders. @@ -99,6 +102,9 @@ func (s *ProjectsService) List(ctx context.Context, o ProjectListOptions) (proje if o.Reverse { q.Set("reverse", "") } + if o.TagID != "" { + q.Set("tag", string(o.TagID)) + } if query := q.Encode(); query != "" { query = strings.ReplaceAll(query, "reverse=", "reverse") path += "?" + query @@ -147,8 +153,8 @@ func (s *ProjectsService) get(ctx context.Context, projectRef string) (project * // If any of the fields have nil value, the option is not set by Add method or // changed by UpdateByID or UpdateByName methods. When using update methods, // removing all elements must be done by setting an initialized slice, not a nil -// slice. For boolean pointer methods, there is a convenient function Bool that -// returns boolean pointer by passing a regular bool value. +// slice. For boolean pointer methods, there is a convenient functions Bool and +// String that return boolean pointer by passing a regular value. type ProjectOptions struct { EmailNotification *EmailNotification `json:"email_notification"` SlackIDs []string `json:"slack_channels"` @@ -162,6 +168,8 @@ type ProjectOptions struct { Exclusions []Exclusion `json:"exclude_version_regexp"` ExcludePrereleases *bool `json:"exclude_prereleases"` ExcludeUpdated *bool `json:"exclude_updated"` + Note *string `json:"note"` + TagIDs []string `json:"tags"` } // Add adds a new project to be tracked. diff --git a/projects_test.go b/projects_test.go index 45577bd..10a0841 100644 --- a/projects_test.go +++ b/projects_test.go @@ -21,7 +21,7 @@ func TestProjectsService_List(t *testing.T) { client, mux, _, teardown := newClient(t, "") defer teardown() - mux.HandleFunc("/v1/projects", requireMethod("GET", newPagedStaticHndler(projectsServiceList...))) + mux.HandleFunc("/v1/projects", requireMethod("GET", newPagedStaticHandler(projectsServiceList...))) for i, page := range projectsServiceListWant { name := "page " + strconv.Itoa(i+1) @@ -90,7 +90,7 @@ func TestProjectsService_List_provider(t *testing.T) { client, mux, _, teardown := newClient(t, "") defer teardown() - mux.HandleFunc("/v1/projects/github", requireMethod("GET", newPagedStaticHndler(projectsServiceList...))) + mux.HandleFunc("/v1/projects/github", requireMethod("GET", newPagedStaticHandler(projectsServiceList...))) for i, page := range projectsServiceListWant { name := "page " + strconv.Itoa(i+1) @@ -108,6 +108,29 @@ func TestProjectsService_List_provider(t *testing.T) { } } +func TestProjectsService_List_tagID(t *testing.T) { + client, mux, _, teardown := newClient(t, "") + defer teardown() + + mux.HandleFunc("/v1/projects", requireMethod("GET", func(w http.ResponseWriter, r *http.Request) { + if tagID := r.URL.Query().Get("tag"); tagID != "12345678" { + w.WriteHeader(http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", jsonContentType) + fmt.Fprintln(w, struct{}{}) + })) + + got, _, err := client.Projects.List(context.Background(), newreleases.ProjectListOptions{ + Page: 1, + TagID: "12345678", + }) + if err != nil { + t.Fatal(err) + } + + assertEqual(t, "", got, []newreleases.Project(nil)) +} func TestProjectsService_List_providerOrder(t *testing.T) { client, mux, _, teardown := newClient(t, "") defer teardown() @@ -414,6 +437,7 @@ var ( Exclusions: []newreleases.Exclusion{{Value: "^1.9", Inverse: true}}, ExcludePrereleases: true, ExcludeUpdated: false, + Note: "great stuff", } projectOptions = &newreleases.ProjectOptions{ EmailNotification: &newreleases.EmailNotificationHourly, @@ -428,6 +452,8 @@ var ( Exclusions: []newreleases.Exclusion{{Value: "^1.9", Inverse: true}}, ExcludePrereleases: newreleases.Bool(true), ExcludeUpdated: newreleases.Bool(false), + Note: newreleases.String("great stuff"), + TagIDs: []string{"tag1", "tag2"}, } ) diff --git a/releases_test.go b/releases_test.go index 4cae227..0eeb206 100644 --- a/releases_test.go +++ b/releases_test.go @@ -18,7 +18,7 @@ func TestReleasesService_ListByProjectID(t *testing.T) { client, mux, _, teardown := newClient(t, "") defer teardown() - mux.HandleFunc("/v1/projects/8wdvh4w9bhsvzclz4ynaqpcpvg/releases", requireMethod("GET", newPagedStaticHndler(releasesServiceList...))) + mux.HandleFunc("/v1/projects/8wdvh4w9bhsvzclz4ynaqpcpvg/releases", requireMethod("GET", newPagedStaticHandler(releasesServiceList...))) for i, page := range releasesServiceListWant { name := "page " + strconv.Itoa(i+1) @@ -37,7 +37,7 @@ func TestReleasesService_ListByProjectName(t *testing.T) { client, mux, _, teardown := newClient(t, "") defer teardown() - mux.HandleFunc("/v1/projects/github/nodejs/node/releases", requireMethod("GET", newPagedStaticHndler(releasesServiceList...))) + mux.HandleFunc("/v1/projects/github/nodejs/node/releases", requireMethod("GET", newPagedStaticHandler(releasesServiceList...))) for i, page := range releasesServiceListWant { name := "page " + strconv.Itoa(i+1) diff --git a/tags.go b/tags.go new file mode 100644 index 0000000..8ccce49 --- /dev/null +++ b/tags.go @@ -0,0 +1,63 @@ +// Copyright (c) 2022, NewReleases Go client AUTHORS. +// All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package newreleases + +import ( + "context" + "net/http" +) + +// TagsService provides information about project Tags. +type TagsService service + +// Tag holds the information about tag ID and its descriptive name. +type Tag struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// Get returns the tag by its ID. +func (s *TagsService) Get(ctx context.Context, id string) (tag *Tag, err error) { + err = s.client.request(ctx, http.MethodGet, "v1/tags/"+id, nil, &tag) + return tag, err +} + +// List returns all tags. +func (s *TagsService) List(ctx context.Context) (tags []Tag, err error) { + + type tagsResponse struct { + Tags []Tag `json:"tags"` + } + + var r tagsResponse + err = s.client.request(ctx, http.MethodGet, "v1/tags", nil, &r) + return r.Tags, err +} + +type tagOptionsRequest struct { + Name string `json:"name"` +} + +// Add adds a new tag. +func (s *TagsService) Add(ctx context.Context, name string) (tag *Tag, err error) { + err = s.client.request(ctx, http.MethodPost, "v1/tags", tagOptionsRequest{ + Name: name, + }, &tag) + return tag, err +} + +// Update changes the name of the tag referenced by the ID. +func (s *TagsService) Update(ctx context.Context, id, name string) (tag *Tag, err error) { + err = s.client.request(ctx, http.MethodPost, "v1/tags/"+id, tagOptionsRequest{ + Name: name, + }, &tag) + return tag, err +} + +// Delete removes the tag by its ID. +func (s *TagsService) Delete(ctx context.Context, id string) error { + return s.client.request(ctx, http.MethodDelete, "v1/tags/"+id, nil, nil) +} diff --git a/tags_test.go b/tags_test.go new file mode 100644 index 0000000..5255fc3 --- /dev/null +++ b/tags_test.go @@ -0,0 +1,165 @@ +// Copyright (c) 2022, NewReleases Go client AUTHORS. +// All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package newreleases_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + + "newreleases.io/newreleases" +) + +func TestTagsService_Get(t *testing.T) { + client, mux, _, teardown := newClient(t, "") + defer teardown() + + mux.HandleFunc("/v1/tags/db733f1254b9", requireMethod("GET", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", jsonContentType) + fmt.Fprintln(w, `{ + "id": "db733f1254b9", + "name": "Awesome" + }`) + })) + + got, err := client.Tags.Get(context.Background(), "db733f1254b9") + if err != nil { + t.Fatal(err) + } + + assertEqual(t, "", got, &newreleases.Tag{ + ID: "db733f1254b9", + Name: "Awesome", + }) +} + +func TestTagsService_List(t *testing.T) { + + var ( + tagsServiceList = ` + { + "tags": [ + { + "id": "db733f1254b9", + "name": "Awesome" + } + ] + } + ` + tagsServiceListWant = []newreleases.Tag{ + { + ID: "db733f1254b9", + Name: "Awesome", + }, + } + ) + + client, mux, _, teardown := newClient(t, "") + defer teardown() + + mux.HandleFunc("/v1/tags", requireMethod("GET", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", jsonContentType) + fmt.Fprintln(w, tagsServiceList) + })) + + got, err := client.Tags.List(context.Background()) + if err != nil { + t.Fatal(err) + } + + assertEqual(t, "", got, tagsServiceListWant) +} + +func TestTagsService_Add(t *testing.T) { + client, mux, _, teardown := newClient(t, "") + defer teardown() + + tagOptions := &newreleases.TagOptionsRequest{ + Name: "awesome", + } + + tagWant := &newreleases.Tag{ + ID: "db733f1254b9", + Name: "awesome", + } + + mux.HandleFunc("/v1/tags", requireMethod("POST", func(w http.ResponseWriter, r *http.Request) { + var o *newreleases.TagOptionsRequest + if err := json.NewDecoder(r.Body).Decode(&o); err != nil { + panic(err) + } + if !reflect.DeepEqual(tagOptions, o) { + w.WriteHeader(http.StatusBadRequest) + return + } + d, err := json.Marshal(tagWant) + if err != nil { + panic(err) + } + w.Header().Set("Content-Type", jsonContentType) + _, _ = w.Write(d) + })) + + got, err := client.Tags.Add(context.Background(), "awesome") + if err != nil { + t.Fatal(err) + } + + assertEqual(t, "", got, tagWant) +} + +func TestTagsService_Update(t *testing.T) { + client, mux, _, teardown := newClient(t, "") + defer teardown() + + tagOptions := &newreleases.TagOptionsRequest{ + Name: "new name", + } + + tagWant := &newreleases.Tag{ + ID: "db733f1254b9", + Name: "new name", + } + + mux.HandleFunc("/v1/tags/db733f1254b9", requireMethod("POST", func(w http.ResponseWriter, r *http.Request) { + var o *newreleases.TagOptionsRequest + if err := json.NewDecoder(r.Body).Decode(&o); err != nil { + panic(err) + } + if !reflect.DeepEqual(tagOptions, o) { + w.WriteHeader(http.StatusBadRequest) + return + } + d, err := json.Marshal(tagWant) + if err != nil { + panic(err) + } + w.Header().Set("Content-Type", jsonContentType) + _, _ = w.Write(d) + })) + + got, err := client.Tags.Update(context.Background(), "db733f1254b9", "new name") + if err != nil { + t.Fatal(err) + } + + assertEqual(t, "", got, tagWant) +} + +func TestTagsService_Delete(t *testing.T) { + client, mux, _, teardown := newClient(t, "") + defer teardown() + + mux.HandleFunc("/v1/tags/db733f1254b9", requireMethod("DELETE", func(w http.ResponseWriter, r *http.Request) {})) + + err := client.Tags.Delete(context.Background(), "db733f1254b9") + if err != nil { + t.Fatal(err) + } +}