Skip to content

Commit

Permalink
Add list, delete method to projects, add readme and changelog entry
Browse files Browse the repository at this point in the history
  • Loading branch information
hs26gill committed Nov 1, 2022
1 parent 9055a41 commit c52575e
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

## Enhancements

* Add `Projects` interface with `list`, `read`, `update`, `delete` methods and integration tests by @hs26gill and @mwudka [#564](https://github.com/hashicorp/go-tfe/pull/564)
* Add `NotificationTriggerAssessmentCheckFailed` notification trigger type by @rexredinger [#549](https://github.com/hashicorp/go-tfe/pull/549)
* Add `RemoteTFEVersion()` to the `Client` interface, which exposes the `X-TFE-Version` header set by a remote TFE instance by @sebasslash [#563](https://github.com/hashicorp/go-tfe/pull/563)
* Validate the module version as a version instead of an ID [#409](https://github.com/hashicorp/go-tfe/pull/409)
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -93,6 +93,7 @@ This API client covers most of the existing Terraform Cloud API calls and is upd
- [x] Providers
- [x] Provider Provider Versions and Platforms
- [x] GPG Keys
- [x] Projects
- [x] Runs
- [x] Run Tasks
- [ ] Run Tasks Integration
Expand Down
2 changes: 2 additions & 0 deletions errors.go
Expand Up @@ -84,6 +84,8 @@ var (

ErrInvalidProjectID = errors.New("invalid value for project ID")

ErrInvalidPagination = errors.New("invalid value for page size or number")

ErrInvalidRunTaskCategory = errors.New(`category must be "task"`)

ErrInvalidRunTaskID = errors.New("invalid value for run task ID")
Expand Down
20 changes: 19 additions & 1 deletion helper_test.go
Expand Up @@ -1874,7 +1874,7 @@ func createProject(t *testing.T, client *Client, org *Organization) (*Project, f

ctx := context.Background()
p, err := client.Projects.Create(ctx, org.Name, ProjectCreateOptions{
Name: String("test_project"),
Name: String(randomStringWithoutSpecialChar(t)),
})
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -1997,6 +1997,24 @@ func randomString(t *testing.T) string {
return v
}

func randomStringWithoutSpecialChar(t *testing.T) string {
v, err := uuid.GenerateUUID()
if err != nil {
t.Fatal(err)
}
uuidWithoutHyphens := strings.Replace(v, "-", "", -1)
return uuidWithoutHyphens
}

func containsProject(pl []*Project, str string) bool {
for _, p := range pl {
if p.Name == str {
return true
}
}
return false
}

func randomSemver(t *testing.T) string {
return fmt.Sprintf("%d.%d.%d", rand.Intn(99)+3, rand.Intn(99)+1, rand.Intn(99)+1)
}
Expand Down
49 changes: 36 additions & 13 deletions projects.go
Expand Up @@ -3,7 +3,6 @@ package tfe
import (
"context"
"fmt"
"log"
"net/url"
)

Expand All @@ -16,7 +15,7 @@ var _ Projects = (*projects)(nil)
// TFE API docs: (TODO: ADD DOCS URL)
type Projects interface {
// List all projects in the given organization
//List(ctx context.Context, organization string, options *ProjectListOptions) (*ProjectList, error)
List(ctx context.Context, organization string, options *ProjectListOptions) (*ProjectList, error)

// Create a new project.
Create(ctx context.Context, organization string, options ProjectCreateOptions) (*Project, error)
Expand Down Expand Up @@ -51,12 +50,10 @@ type Project struct {
Organization *Organization `jsonapi:"relation,organization"`
}

//// ProjectListOptions represents the options for listing projects
//type ProjectListOptions struct {
// ListOptions
//
// // Add more list options here
//}
// ProjectListOptions represents the options for listing projects
type ProjectListOptions struct {
ListOptions
}

// ProjectCreateOptions represents the options for creating a project
type ProjectCreateOptions struct {
Expand All @@ -83,9 +80,37 @@ type ProjectUpdateOptions struct {
}

// List all projects.
//func List(ctx context.Context, options *ProjectListOptions) (*ProjectList, error) {
// panic("not yet implemented")
//}
func (s *projects) List(ctx context.Context, organization string, options *ProjectListOptions) (*ProjectList, error) {
if !validStringID(&organization) {
return nil, ErrInvalidOrg
}

if err := options.valid(); err != nil {
return nil, err
}

u := fmt.Sprintf("organizations/%s/projects", url.QueryEscape(organization))
req, err := s.client.NewRequest("GET", u, options)
if err != nil {
return nil, err
}

p := &ProjectList{}
err = req.Do(ctx, p)
if err != nil {
return nil, err
}

return p, nil
}

func (o *ProjectListOptions) valid() error {
if o == nil || o.PageNumber == 0 || o.PageSize == 0 {
return ErrInvalidPagination
}

return nil
}

// Create a project with the given options
func (s *projects) Create(ctx context.Context, organization string, options ProjectCreateOptions) (*Project, error) {
Expand All @@ -105,8 +130,6 @@ func (s *projects) Create(ctx context.Context, organization string, options Proj

p := &Project{}
err = req.Do(ctx, p)
log.Println(ctx)
log.Println(p)
if err != nil {
return nil, err
}
Expand Down
95 changes: 80 additions & 15 deletions projects_integration_test.go
Expand Up @@ -11,20 +11,52 @@ import (
"github.com/stretchr/testify/require"
)

//func TestProjectsList(t *testing.T) {
// skipIfNotCINode(t)
//
// client := testClient(t)
// ctx := context.Background()
//
// // Create your test helper resources here
// t.Run("test not yet implemented", func(t *testing.T) {
// require.NotNil(t, nil)
// })
//}
func TestProjectsList(t *testing.T) {
skipIfNotCINode(t)
skipIfBeta(t)

client := testClient(t)
ctx := context.Background()

orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

pTest1, pTestCleanup := createProject(t, client, orgTest)
defer pTestCleanup()

pTest2, pTestCleanup := createProject(t, client, orgTest)
defer pTestCleanup()

t.Run("with invalid options", func(t *testing.T) {
pl, err := client.Projects.List(ctx, orgTest.Name, nil)
assert.Nil(t, pl)
assert.EqualError(t, err, ErrInvalidPagination.Error())
})

t.Run("with list options", func(t *testing.T) {
pl, err := client.Projects.List(ctx, orgTest.Name, &ProjectListOptions{
ListOptions: ListOptions{
PageNumber: 1,
PageSize: 100,
},
})
require.NoError(t, err)
assert.Contains(t, pl.Items, pTest1)
assert.Contains(t, pl.Items, pTest2)
assert.Equal(t, true, containsProject(pl.Items, "Default Project"))
assert.Equal(t, 3, len(pl.Items))
})

t.Run("without a valid organization", func(t *testing.T) {
pl, err := client.Projects.List(ctx, badIdentifier, nil)
assert.Nil(t, pl)
assert.EqualError(t, err, ErrInvalidOrg.Error())
})
}

func TestProjectsRead(t *testing.T) {
skipIfNotCINode(t)
skipIfBeta(t)

client := testClient(t)
ctx := context.Background()
Expand Down Expand Up @@ -57,14 +89,14 @@ func TestProjectsRead(t *testing.T) {

func TestProjectsCreate(t *testing.T) {
skipIfNotCINode(t)
skipIfBeta(t)

client := testClient(t)
ctx := context.Background()

orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

// Create your test helper resources here
t.Run("with valid options", func(t *testing.T) {
options := ProjectCreateOptions{
Name: String("foo"),
Expand All @@ -73,7 +105,6 @@ func TestProjectsCreate(t *testing.T) {
w, err := client.Projects.Create(ctx, orgTest.Name, options)
require.NoError(t, err)

// Get a refreshed view from the API.
refreshed, err := client.Projects.Read(ctx, w.ID)
require.NoError(t, err)

Expand All @@ -87,13 +118,13 @@ func TestProjectsCreate(t *testing.T) {
})

t.Run("when options is missing name", func(t *testing.T) {
w, err := client.Projects.Create(ctx, "foo", ProjectCreateOptions{})
w, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{})
assert.Nil(t, w)
assert.EqualError(t, err, ErrRequiredName.Error())
})

t.Run("when options has an invalid name", func(t *testing.T) {
w, err := client.Projects.Create(ctx, "foo", ProjectCreateOptions{
w, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{
Name: String(badIdentifier),
})
assert.Nil(t, w)
Expand All @@ -111,6 +142,7 @@ func TestProjectsCreate(t *testing.T) {

func TestProjectsUpdate(t *testing.T) {
skipIfNotCINode(t)
skipIfBeta(t)

client := testClient(t)
ctx := context.Background()
Expand Down Expand Up @@ -148,3 +180,36 @@ func TestProjectsUpdate(t *testing.T) {
assert.EqualError(t, err, ErrInvalidProjectID.Error())
})
}

func TestProjectsDelete(t *testing.T) {
skipIfNotCINode(t)
skipIfBeta(t)

client := testClient(t)
ctx := context.Background()

orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

pTest, pTestCleanup := createProject(t, client, orgTest)
defer pTestCleanup()

t.Run("with valid options", func(t *testing.T) {
err := client.Projects.Delete(ctx, pTest.ID)
require.NoError(t, err)

// Try loading the project - it should fail.
_, err = client.Projects.Read(ctx, pTest.ID)
assert.Equal(t, err, ErrResourceNotFound)
})

t.Run("when the project does not exist", func(t *testing.T) {
err := client.Projects.Delete(ctx, pTest.ID)
assert.Equal(t, err, ErrResourceNotFound)
})

t.Run("when the project ID is invalid", func(t *testing.T) {
err := client.Projects.Delete(ctx, badIdentifier)
assert.EqualError(t, err, ErrInvalidProjectID.Error())
})
}

0 comments on commit c52575e

Please sign in to comment.