diff --git a/release/github.go b/release/github.go index f3ef1ae..99b1791 100644 --- a/release/github.go +++ b/release/github.go @@ -167,12 +167,3 @@ func (r *GitHubRelease) List(ctx context.Context, maxLength int) ([]string, erro } return versions, nil } - -func tagNameToVersion(tagName string) string { - // if a tagName starts with `v`, remove it. - if tagName[0] == 'v' { - return tagName[1:] - } - - return tagName -} diff --git a/release/gitlab.go b/release/gitlab.go index 9fe0d59..6de5ac4 100644 --- a/release/gitlab.go +++ b/release/gitlab.go @@ -2,7 +2,6 @@ package release import ( "context" - "errors" "fmt" "net/url" "strings" @@ -15,6 +14,9 @@ import ( type GitLabAPI interface { // ProjectGetLatestRelease fetches the latest published release for the project. ProjectGetLatestRelease(ctx context.Context, owner, project string) (*gitlab.Release, *gitlab.Response, error) + + // ProjectListReleases gets a pagenated of releases accessible by the authenticated user. + ProjectListReleases(ctx context.Context, owner, project string, opt *gitlab.ListReleasesOptions) ([]*gitlab.Release, *gitlab.Response, error) } // GitLabConfig is a set of configurations for GitLabRelease.. @@ -71,6 +73,11 @@ func (c *GitLabClient) ProjectGetLatestRelease(ctx context.Context, owner, proje return latest, response, err } +// ProjectListReleases gets a pagenated of releases accessible by the authenticated user. +func (c *GitLabClient) ProjectListReleases(ctx context.Context, owner, project string, opt *gitlab.ListReleasesOptions) ([]*gitlab.Release, *gitlab.Response, error) { + return c.client.Releases.ListReleases(owner+"/"+project, opt, gitlab.WithContext(ctx)) +} + // GitLabRelease is a release implementation which provides version information with GitLab Release. type GitLabRelease struct { // api is an instance of GitLabAPI interface. @@ -120,17 +127,34 @@ func (r *GitLabRelease) Latest(ctx context.Context) (string, error) { } // Use TagName because some releases do not have Name. - tagName := release.TagName - - // if a tagName starts with `v`, remove it. - if tagName[0] == 'v' { - return tagName[1:], nil - } + v := tagNameToVersion(release.TagName) - return tagName, nil + return v, nil } // List returns a list of versions. func (r *GitLabRelease) List(ctx context.Context, maxLength int) ([]string, error) { - return nil, errors.New("not impplemented yet") + versions := []string{} + opt := &gitlab.ListReleasesOptions{} + for { + releases, resp, err := r.api.ProjectListReleases(ctx, r.owner, r.project, opt) + + if err != nil { + return versions, fmt.Errorf("failed to list releases for %s/%s: %s", r.owner, r.project, err) + } + + for _, release := range releases { + v := tagNameToVersion(release.TagName) + versions = append(versions, v) + } + if resp.NextPage == 0 || len(versions) >= maxLength { + break + } + opt.Page = resp.NextPage + } + + if maxLength < len(versions) { + return versions[:maxLength], nil + } + return versions, nil } diff --git a/release/gitlab_test.go b/release/gitlab_test.go index 6868deb..f8542b7 100644 --- a/release/gitlab_test.go +++ b/release/gitlab_test.go @@ -3,6 +3,7 @@ package release import ( "context" "errors" + "reflect" "testing" "github.com/davecgh/go-spew/spew" @@ -11,9 +12,10 @@ import ( // mockGitLabClient is a mock GitLabAPI implementation. type mockGitLabClient struct { - projectRelease *gitlab.Release - response *gitlab.Response - err error + projectRelease *gitlab.Release + projectReleases []*gitlab.Release + response *gitlab.Response + err error } // ProjectGetLatestRelease returns the latest release for the mockGitLabClient. @@ -21,6 +23,11 @@ func (c *mockGitLabClient) ProjectGetLatestRelease(ctx context.Context, owner, p return c.projectRelease, c.response, c.err } +// ProjectListReleases returns a list of releases for the mockGitLabClient. +func (c *mockGitLabClient) ProjectListReleases(ctx context.Context, owner, repo string, opt *gitlab.ListReleasesOptions) ([]*gitlab.Release, *gitlab.Response, error) { + return c.projectReleases, c.response, c.err +} + // Test of NewGitLabClient(config GitLabConfig) func TestNewGitLabClient(t *testing.T) { cases := []struct { @@ -220,3 +227,83 @@ func TestGitLabReleaseLatest(t *testing.T) { } } } + +// Test of GitLabRelease.List(ctx context.Context, maxLength int) +func TestGitLabReleaseList(t *testing.T) { + tagv := []string{"v0.3.0", "v0.2.0", "v0.1.0"} + tag := []string{"0.3.0", "0.2.0", "0.1.0"} + cases := []struct { + client *mockGitLabClient + maxLength int + want []string + ok bool + }{ // test len(versions) < maxLength + { + client: &mockGitLabClient{ + projectReleases: []*gitlab.Release{ + &gitlab.Release{TagName: tagv[0]}, + &gitlab.Release{TagName: tagv[1]}, + &gitlab.Release{TagName: tagv[2]}, + }, + response: &gitlab.Response{}, + err: nil, + }, + maxLength: 5, + want: tag, + ok: true, + }, + // test len(versions) > maxLength + { + client: &mockGitLabClient{ + projectReleases: []*gitlab.Release{ + &gitlab.Release{TagName: tagv[0]}, + &gitlab.Release{TagName: tagv[1]}, + &gitlab.Release{TagName: tagv[2]}, + }, + response: &gitlab.Response{}, + err: nil, + }, + maxLength: 2, + want: tag[:2], + ok: true, + }, + // test unreachable/invalid project + { + client: &mockGitLabClient{ + projectReleases: nil, + response: &gitlab.Response{}, + // Actual error response type is *gitlab.ErrorResponse, + // but we are not interested in the internal structure. + err: errors.New(`GET https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab/releases: 404 Not Found []`), + }, + want: []string{}, + ok: false, + }, + } + + source := "gitlab-org/gitlab" + for _, tc := range cases { + // Set a mock client + config := GitLabConfig{ + api: tc.client, + } + r, err := NewGitLabRelease(source, config) + if err != nil { + t.Fatalf("failed to NewGitLabRelease(%s, %#v): %s", source, config, err) + } + + got, err := r.List(context.Background(), tc.maxLength) + + if tc.ok && err != nil { + t.Errorf("(*GitLabRelease).List() with r = %s, maxLength = %d returns unexpected err: %+v", spew.Sdump(r), tc.maxLength, err) + } + + if !tc.ok && err == nil { + t.Errorf("(*GitLabRelease).List() with r = %s, maxLength = %d expects to return an error, but no error", spew.Sdump(r), tc.maxLength) + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("(*GitLabRelease).List() with r = %s, maxLength = %d returns %s, but want = %s", spew.Sdump(r), tc.maxLength, got, tc.want) + } + } +} diff --git a/release/version.go b/release/version.go new file mode 100644 index 0000000..b3f8b45 --- /dev/null +++ b/release/version.go @@ -0,0 +1,10 @@ +package release + +func tagNameToVersion(tagName string) string { + // if a tagName starts with `v`, remove it. + if tagName[0] == 'v' { + return tagName[1:] + } + + return tagName +}