update go-tfe to v0.3.16
Signed-off-by: Paul Thrasher <pthrasher@hashicorp.com>
This commit is contained in:
parent
53f977bee2
commit
aece05320b
2
go.mod
2
go.mod
|
@ -55,7 +55,7 @@ require (
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.2
|
github.com/hashicorp/go-retryablehttp v0.5.2
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0
|
github.com/hashicorp/go-rootcerts v1.0.0
|
||||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
|
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
|
||||||
github.com/hashicorp/go-tfe v0.3.14
|
github.com/hashicorp/go-tfe v0.3.16
|
||||||
github.com/hashicorp/go-uuid v1.0.1
|
github.com/hashicorp/go-uuid v1.0.1
|
||||||
github.com/hashicorp/go-version v1.1.0
|
github.com/hashicorp/go-version v1.1.0
|
||||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -189,8 +189,12 @@ github.com/hashicorp/go-slug v0.3.0 h1:L0c+AvH/J64iMNF4VqRaRku2DMTEuHioPVS7kMjWI
|
||||||
github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
||||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
|
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
|
||||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
github.com/hashicorp/go-tfe v0.3.14 h1:1eWmq4RAICGufydNUWu7ahb0gtq24pN9jatD2FkdxdE=
|
github.com/hashicorp/go-tfe v0.3.4 h1:A9pKjZMDTSGozXf2wQlWhBI7QoxCoas14Xg/TSiEAV8=
|
||||||
github.com/hashicorp/go-tfe v0.3.14/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM=
|
github.com/hashicorp/go-tfe v0.3.4/go.mod h1:Vssg8/lwVz+PyJ/nAK97zYmXxxLe28MCIMhKo+rva1o=
|
||||||
|
github.com/hashicorp/go-tfe v0.3.15-0.20190415182703-eb3b9edefdda h1:qWY67Uh98jh4BMYSnLVIXsz2AF1hRiHG4k43wzn1NoI=
|
||||||
|
github.com/hashicorp/go-tfe v0.3.15-0.20190415182703-eb3b9edefdda/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM=
|
||||||
|
github.com/hashicorp/go-tfe v0.3.16 h1:GS2yv580p0co4j3FBVaC6Zahd9mxdCGehhJ0qqzFMH0=
|
||||||
|
github.com/hashicorp/go-tfe v0.3.16/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM=
|
||||||
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppliesRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rTest, rTestCleanup := createAppliedRun(t, client, nil)
|
||||||
|
defer rTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the plan exists", func(t *testing.T) {
|
||||||
|
a, err := client.Applies.Read(ctx, rTest.Apply.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, a.LogReadURL)
|
||||||
|
assert.Equal(t, a.Status, ApplyFinished)
|
||||||
|
assert.NotEmpty(t, a.StatusTimestamps)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the apply does not exist", func(t *testing.T) {
|
||||||
|
a, err := client.Applies.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, a)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid apply ID", func(t *testing.T) {
|
||||||
|
a, err := client.Applies.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, a)
|
||||||
|
assert.EqualError(t, err, "invalid value for apply ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppliesLogs(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rTest, rTestCleanup := createAppliedRun(t, client, nil)
|
||||||
|
defer rTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the log exists", func(t *testing.T) {
|
||||||
|
a, err := client.Applies.Read(ctx, rTest.Apply.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logReader, err := client.Applies.Logs(ctx, a.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(logReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, string(logs), "1 added, 0 changed, 0 destroyed")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the log does not exist", func(t *testing.T) {
|
||||||
|
logs, err := client.Applies.Logs(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, logs)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigurationVersionsList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
cvTest1, cvTest1Cleanup := createConfigurationVersion(t, client, wTest)
|
||||||
|
defer cvTest1Cleanup()
|
||||||
|
cvTest2, cvTest2Cleanup := createConfigurationVersion(t, client, wTest)
|
||||||
|
defer cvTest2Cleanup()
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
options := ConfigurationVersionListOptions{}
|
||||||
|
|
||||||
|
cvl, err := client.ConfigurationVersions.List(ctx, wTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// We need to strip the upload URL as that is a dynamic link.
|
||||||
|
cvTest1.UploadURL = ""
|
||||||
|
cvTest2.UploadURL = ""
|
||||||
|
|
||||||
|
// And for the retrieved configuration versions as well.
|
||||||
|
for _, cv := range cvl.Items {
|
||||||
|
cv.UploadURL = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, cvl.Items, cvTest1)
|
||||||
|
assert.Contains(t, cvl.Items, cvTest2)
|
||||||
|
assert.Equal(t, 1, cvl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, cvl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
options := ConfigurationVersionListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cvl, err := client.ConfigurationVersions.List(ctx, wTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, cvl.Items)
|
||||||
|
assert.Equal(t, 999, cvl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, cvl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
options := ConfigurationVersionListOptions{}
|
||||||
|
|
||||||
|
cvl, err := client.ConfigurationVersions.List(ctx, badIdentifier, options)
|
||||||
|
assert.Nil(t, cvl)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationVersionsCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
cv, err := client.ConfigurationVersions.Create(ctx,
|
||||||
|
wTest.ID,
|
||||||
|
ConfigurationVersionCreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view of the configuration version.
|
||||||
|
refreshed, err := client.ConfigurationVersions.Read(ctx, cv.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*ConfigurationVersion{
|
||||||
|
cv,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Empty(t, item.Error)
|
||||||
|
assert.Equal(t, item.Source, ConfigurationSourceAPI)
|
||||||
|
assert.Equal(t, item.Status, ConfigurationPending)
|
||||||
|
assert.NotEmpty(t, item.UploadURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid workspace id", func(t *testing.T) {
|
||||||
|
cv, err := client.ConfigurationVersions.Create(
|
||||||
|
ctx,
|
||||||
|
badIdentifier,
|
||||||
|
ConfigurationVersionCreateOptions{},
|
||||||
|
)
|
||||||
|
assert.Nil(t, cv)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationVersionsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
cvTest, cvTestCleanup := createConfigurationVersion(t, client, nil)
|
||||||
|
defer cvTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the configuration version exists", func(t *testing.T) {
|
||||||
|
cv, err := client.ConfigurationVersions.Read(ctx, cvTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Don't compare the UploadURL because it will be generated twice in
|
||||||
|
// this test - once at creation of the configuration version, and
|
||||||
|
// again during the GET.
|
||||||
|
cvTest.UploadURL, cv.UploadURL = "", ""
|
||||||
|
|
||||||
|
assert.Equal(t, cvTest, cv)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the configuration version does not exist", func(t *testing.T) {
|
||||||
|
cv, err := client.ConfigurationVersions.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, cv)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid configuration version id", func(t *testing.T) {
|
||||||
|
cv, err := client.ConfigurationVersions.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, cv)
|
||||||
|
assert.EqualError(t, err, "invalid value for configuration version ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigurationVersionsUpload(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
cv, cvCleanup := createConfigurationVersion(t, client, nil)
|
||||||
|
defer cvCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.ConfigurationVersions.Upload(
|
||||||
|
ctx,
|
||||||
|
cv.UploadURL,
|
||||||
|
"test-fixtures/config-version",
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// We do this is a small loop, because it can take a second
|
||||||
|
// before the upload is finished.
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
refreshed, err := client.ConfigurationVersions.Read(ctx, cv.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if refreshed.Status == ConfigurationUploaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 10 {
|
||||||
|
t.Fatal("Timeout waiting for the configuration version to be uploaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid upload URL", func(t *testing.T) {
|
||||||
|
err := client.ConfigurationVersions.Upload(
|
||||||
|
ctx,
|
||||||
|
cv.UploadURL[:len(cv.UploadURL)-10]+"nonexisting",
|
||||||
|
"test-fixtures/config-version",
|
||||||
|
)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid path", func(t *testing.T) {
|
||||||
|
err := client.ConfigurationVersions.Upload(
|
||||||
|
ctx,
|
||||||
|
cv.UploadURL,
|
||||||
|
"nonexisting",
|
||||||
|
)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compile-time proof of interface implementation.
|
||||||
|
var _ CostEstimations = (*costEstimations)(nil)
|
||||||
|
|
||||||
|
// CostEstimations describes all the costEstimation related methods that
|
||||||
|
// the Terraform Enterprise API supports.
|
||||||
|
//
|
||||||
|
// TFE API docs: https://www.terraform.io/docs/enterprise/api/ (TBD)
|
||||||
|
type CostEstimations interface {
|
||||||
|
// Read a costEstimation by its ID.
|
||||||
|
Read(ctx context.Context, costEstimationID string) (*CostEstimation, error)
|
||||||
|
|
||||||
|
// Logs retrieves the logs of a costEstimation.
|
||||||
|
Logs(ctx context.Context, costEstimationID string) (io.Reader, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// costEstimations implements CostEstimations.
|
||||||
|
type costEstimations struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// CostEstimationStatus represents a costEstimation state.
|
||||||
|
type CostEstimationStatus string
|
||||||
|
|
||||||
|
//List all available costEstimation statuses.
|
||||||
|
const (
|
||||||
|
CostEstimationCanceled CostEstimationStatus = "canceled"
|
||||||
|
CostEstimationErrored CostEstimationStatus = "errored"
|
||||||
|
CostEstimationFinished CostEstimationStatus = "finished"
|
||||||
|
CostEstimationQueued CostEstimationStatus = "queued"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CostEstimation represents a Terraform Enterprise costEstimation.
|
||||||
|
type CostEstimation struct {
|
||||||
|
ID string `jsonapi:"primary,cost-estimations"`
|
||||||
|
ErrorMessage string `jsonapi:"attr,error-message"`
|
||||||
|
Status CostEstimationStatus `jsonapi:"attr,status"`
|
||||||
|
StatusTimestamps *CostEstimationStatusTimestamps `jsonapi:"attr,status-timestamps"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CostEstimationStatusTimestamps holds the timestamps for individual costEstimation statuses.
|
||||||
|
type CostEstimationStatusTimestamps struct {
|
||||||
|
CanceledAt time.Time `json:"canceled-at"`
|
||||||
|
ErroredAt time.Time `json:"errored-at"`
|
||||||
|
FinishedAt time.Time `json:"finished-at"`
|
||||||
|
QueuedAt time.Time `json:"queued-at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a costEstimation by its ID.
|
||||||
|
func (s *costEstimations) Read(ctx context.Context, costEstimationID string) (*CostEstimation, error) {
|
||||||
|
if !validStringID(&costEstimationID) {
|
||||||
|
return nil, errors.New("invalid value for cost estimation ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
u := fmt.Sprintf("cost-estimations/%s", url.QueryEscape(costEstimationID))
|
||||||
|
req, err := s.client.newRequest("GET", u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ce := &CostEstimation{}
|
||||||
|
err = s.client.do(ctx, req, ce)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ce, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs retrieves the logs of a costEstimation.
|
||||||
|
func (s *costEstimations) Logs(ctx context.Context, costEstimationID string) (io.Reader, error) {
|
||||||
|
if !validStringID(&costEstimationID) {
|
||||||
|
return nil, errors.New("invalid value for cost estimation ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop until the context is canceled or the cost estimation is finished
|
||||||
|
// running. The cost estimation logs are not streamed and so only available
|
||||||
|
// once the estimation is finished.
|
||||||
|
for {
|
||||||
|
// Get the costEstimation to make sure it exists.
|
||||||
|
ce, err := s.Read(ctx, costEstimationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ce.Status {
|
||||||
|
case CostEstimationQueued:
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u := fmt.Sprintf("cost-estimations/%s/output", url.QueryEscape(costEstimationID))
|
||||||
|
req, err := s.client.newRequest("GET", u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logs := bytes.NewBuffer(nil)
|
||||||
|
err = s.client.do(ctx, req, logs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCostEstimationsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
// Enable cost estimation for the test organization.
|
||||||
|
orgTest, err := client.Organizations.Update(
|
||||||
|
ctx,
|
||||||
|
orgTest.Name,
|
||||||
|
OrganizationUpdateOptions{
|
||||||
|
CostEstimationEnabled: Bool(true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
rTest, _ := createPlannedRun(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("when the costEstimation exists", func(t *testing.T) {
|
||||||
|
ce, err := client.CostEstimations.Read(ctx, rTest.CostEstimation.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ce.Status, CostEstimationFinished)
|
||||||
|
assert.NotEmpty(t, ce.StatusTimestamps)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the costEstimation does not exist", func(t *testing.T) {
|
||||||
|
ce, err := client.CostEstimations.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, ce)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid costEstimation ID", func(t *testing.T) {
|
||||||
|
ce, err := client.CostEstimations.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, ce)
|
||||||
|
assert.EqualError(t, err, "invalid value for cost estimation ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCostEstimationsLogs(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
// Enable cost estimation for the test organization.
|
||||||
|
orgTest, err := client.Organizations.Update(
|
||||||
|
ctx,
|
||||||
|
orgTest.Name,
|
||||||
|
OrganizationUpdateOptions{
|
||||||
|
CostEstimationEnabled: Bool(true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
rTest, _ := createPlannedRun(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("when the log exists", func(t *testing.T) {
|
||||||
|
ce, err := client.CostEstimations.Read(ctx, rTest.CostEstimation.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logReader, err := client.CostEstimations.Logs(ctx, ce.ID)
|
||||||
|
require.NotNil(t, logReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(logReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Skip("log output is likely to change")
|
||||||
|
assert.Contains(t, string(logs), "SKU")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the log does not exist", func(t *testing.T) {
|
||||||
|
logs, err := client.CostEstimations.Logs(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, logs)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := &tfe.Config{
|
||||||
|
Token: "insert-your-token-here",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := tfe.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a context
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Create a new organization
|
||||||
|
options := tfe.OrganizationCreateOptions{
|
||||||
|
Name: tfe.String("example"),
|
||||||
|
Email: tfe.String("info@example.com"),
|
||||||
|
}
|
||||||
|
|
||||||
|
org, err := client.Organizations.Create(ctx, options)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete an organization
|
||||||
|
err = client.Organizations.Delete(ctx, org.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := &tfe.Config{
|
||||||
|
Token: "insert-your-token-here",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := tfe.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a context
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Create a new workspace
|
||||||
|
w, err := client.Workspaces.Create(ctx, "org-name", tfe.WorkspaceCreateOptions{
|
||||||
|
Name: tfe.String("my-app-tst"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the workspace
|
||||||
|
w, err = client.Workspaces.Update(ctx, "org-name", w.Name, tfe.WorkspaceUpdateOptions{
|
||||||
|
AutoApply: tfe.Bool(false),
|
||||||
|
TerraformVersion: tfe.String("0.11.1"),
|
||||||
|
WorkingDirectory: tfe.String("my-app/infra"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,666 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const badIdentifier = "! / nope"
|
||||||
|
|
||||||
|
func testClient(t *testing.T) *Client {
|
||||||
|
client, err := NewClient(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func createConfigurationVersion(t *testing.T, client *Client, w *Workspace) (*ConfigurationVersion, func()) {
|
||||||
|
var wCleanup func()
|
||||||
|
|
||||||
|
if w == nil {
|
||||||
|
w, wCleanup = createWorkspace(t, client, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
cv, err := client.ConfigurationVersions.Create(
|
||||||
|
ctx,
|
||||||
|
w.ID,
|
||||||
|
ConfigurationVersionCreateOptions{AutoQueueRuns: Bool(false)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cv, func() {
|
||||||
|
if wCleanup != nil {
|
||||||
|
wCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUploadedConfigurationVersion(t *testing.T, client *Client, w *Workspace) (*ConfigurationVersion, func()) {
|
||||||
|
cv, cvCleanup := createConfigurationVersion(t, client, w)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
err := client.ConfigurationVersions.Upload(ctx, cv.UploadURL, "test-fixtures/config-version")
|
||||||
|
if err != nil {
|
||||||
|
cvCleanup()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
cv, err = client.ConfigurationVersions.Read(ctx, cv.ID)
|
||||||
|
if err != nil {
|
||||||
|
cvCleanup()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cv.Status == ConfigurationUploaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 10 {
|
||||||
|
cvCleanup()
|
||||||
|
t.Fatal("Timeout waiting for the configuration version to be uploaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cv, cvCleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNotificationConfiguration(t *testing.T, client *Client, w *Workspace) (*NotificationConfiguration, func()) {
|
||||||
|
var wCleanup func()
|
||||||
|
|
||||||
|
if w == nil {
|
||||||
|
w, wCleanup = createWorkspace(t, client, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
nc, err := client.NotificationConfigurations.Create(
|
||||||
|
ctx,
|
||||||
|
w.ID,
|
||||||
|
NotificationConfigurationCreateOptions{
|
||||||
|
DestinationType: NotificationDestination(NotificationDestinationTypeGeneric),
|
||||||
|
Enabled: Bool(false),
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Token: String(randomString(t)),
|
||||||
|
URL: String("http://example.com"),
|
||||||
|
Triggers: []string{NotificationTriggerCreated},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nc, func() {
|
||||||
|
if err := client.NotificationConfigurations.Delete(ctx, nc.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying notification configuration! WARNING: Dangling\n"+
|
||||||
|
"resources may exist! The full error is shown below.\n\n"+
|
||||||
|
"NotificationConfiguration: %s\nError: %s", nc.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wCleanup != nil {
|
||||||
|
wCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPolicySet(t *testing.T, client *Client, org *Organization, policies []*Policy, workspaces []*Workspace) (*PolicySet, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ps, err := client.PolicySets.Create(ctx, org.Name, PolicySetCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Policies: policies,
|
||||||
|
Workspaces: workspaces,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps, func() {
|
||||||
|
if err := client.PolicySets.Delete(ctx, ps.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying policy set! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"PolicySet: %s\nError: %s", ps.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPolicy(t *testing.T, client *Client, org *Organization) (*Policy, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := randomString(t)
|
||||||
|
options := PolicyCreateOptions{
|
||||||
|
Name: String(name),
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Path: String(name + ".sentinel"),
|
||||||
|
Mode: EnforcementMode(EnforcementSoft),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
p, err := client.Policies.Create(ctx, org.Name, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, func() {
|
||||||
|
if err := client.Policies.Delete(ctx, p.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying policy! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"Policy: %s\nError: %s", p.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUploadedPolicy(t *testing.T, client *Client, pass bool, org *Organization) (*Policy, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, pCleanup := createPolicy(t, client, org)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
err := client.Policies.Upload(ctx, p.ID, []byte(fmt.Sprintf("main = rule { %t }", pass)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err = client.Policies.Read(ctx, p.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, func() {
|
||||||
|
pCleanup()
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOAuthClient(t *testing.T, client *Client, org *Organization) (*OAuthClient, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
githubToken := os.Getenv("GITHUB_TOKEN")
|
||||||
|
if githubToken == "" {
|
||||||
|
t.Fatal("Export a valid GITHUB_TOKEN before running this test!")
|
||||||
|
}
|
||||||
|
|
||||||
|
options := OAuthClientCreateOptions{
|
||||||
|
APIURL: String("https://api.github.com"),
|
||||||
|
HTTPURL: String("https://github.com"),
|
||||||
|
OAuthToken: String(githubToken),
|
||||||
|
ServiceProvider: ServiceProvider(ServiceProviderGithub),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
oc, err := client.OAuthClients.Create(ctx, org.Name, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This currently panics as the token will not be there when the client is
|
||||||
|
// created. To get a token, the client needs to be connected through the UI
|
||||||
|
// first. So the test using this (TestOAuthTokensList) is currently disabled.
|
||||||
|
return oc, func() {
|
||||||
|
if err := client.OAuthClients.Delete(ctx, oc.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying OAuth client! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"OAuthClient: %s\nError: %s", oc.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOAuthToken(t *testing.T, client *Client, org *Organization) (*OAuthToken, func()) {
|
||||||
|
ocTest, ocTestCleanup := createOAuthClient(t, client, org)
|
||||||
|
return ocTest.OAuthTokens[0], ocTestCleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOrganization(t *testing.T, client *Client) (*Organization, func()) {
|
||||||
|
ctx := context.Background()
|
||||||
|
org, err := client.Organizations.Create(ctx, OrganizationCreateOptions{
|
||||||
|
Name: String("tst-" + randomString(t)),
|
||||||
|
Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return org, func() {
|
||||||
|
if err := client.Organizations.Delete(ctx, org.Name); err != nil {
|
||||||
|
t.Errorf("Error destroying organization! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"Organization: %s\nError: %s", org.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOrganizationToken(t *testing.T, client *Client, org *Organization) (*OrganizationToken, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
tk, err := client.OrganizationTokens.Generate(ctx, org.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tk, func() {
|
||||||
|
if err := client.OrganizationTokens.Delete(ctx, org.Name); err != nil {
|
||||||
|
t.Errorf("Error destroying organization token! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"OrganizationToken: %s\nError: %s", tk.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {
|
||||||
|
var wCleanup func()
|
||||||
|
|
||||||
|
if w == nil {
|
||||||
|
w, wCleanup = createWorkspace(t, client, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
cv, cvCleanup := createUploadedConfigurationVersion(t, client, w)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
r, err := client.Runs.Create(ctx, RunCreateOptions{
|
||||||
|
ConfigurationVersion: cv,
|
||||||
|
Workspace: w,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, func() {
|
||||||
|
if wCleanup != nil {
|
||||||
|
wCleanup()
|
||||||
|
} else {
|
||||||
|
cvCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPlannedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {
|
||||||
|
r, rCleanup := createRun(t, client, w)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
ctx := context.Background()
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
r, err = client.Runs.Read(ctx, r.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.Status {
|
||||||
|
case RunPlanned, RunCostEstimated, RunPolicyChecked, RunPolicyOverride:
|
||||||
|
return r, rCleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 45 {
|
||||||
|
rCleanup()
|
||||||
|
t.Fatal("Timeout waiting for run to be applied")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAppliedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {
|
||||||
|
r, rCleanup := createPlannedRun(t, client, w)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err := client.Runs.Apply(ctx, r.ID, RunApplyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
r, err = client.Runs.Read(ctx, r.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Status == RunApplied {
|
||||||
|
return r, rCleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 45 {
|
||||||
|
rCleanup()
|
||||||
|
t.Fatal("Timeout waiting for run to be applied")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSSHKey(t *testing.T, client *Client, org *Organization) (*SSHKey, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
key, err := client.SSHKeys.Create(ctx, org.Name, SSHKeyCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, func() {
|
||||||
|
if err := client.SSHKeys.Delete(ctx, key.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying SSH key! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"SSHKey: %s\nError: %s", key.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createStateVersion(t *testing.T, client *Client, serial int64, w *Workspace) (*StateVersion, func()) {
|
||||||
|
var wCleanup func()
|
||||||
|
|
||||||
|
if w == nil {
|
||||||
|
w, wCleanup = createWorkspace(t, client, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := ioutil.ReadFile("test-fixtures/state-version/terraform.tfstate")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, err = client.Workspaces.Lock(ctx, w.ID, WorkspaceLockOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_, err := client.Workspaces.Unlock(ctx, w.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sv, err := client.StateVersions.Create(ctx, w.ID, StateVersionCreateOptions{
|
||||||
|
MD5: String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||||
|
Serial: Int64(serial),
|
||||||
|
State: String(base64.StdEncoding.EncodeToString(state)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sv, func() {
|
||||||
|
// There currently isn't a way to delete a state, so we
|
||||||
|
// can only cleanup by deleting the workspace.
|
||||||
|
if wCleanup != nil {
|
||||||
|
wCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTeam(t *testing.T, client *Client, org *Organization) (*Team, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
tm, err := client.Teams.Create(ctx, org.Name, TeamCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
OrganizationAccess: &OrganizationAccessOptions{ManagePolicies: Bool(true)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tm, func() {
|
||||||
|
if err := client.Teams.Delete(ctx, tm.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying team! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"Team: %s\nError: %s", tm.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTeamAccess(t *testing.T, client *Client, tm *Team, w *Workspace, org *Organization) (*TeamAccess, func()) {
|
||||||
|
var orgCleanup, tmCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tm == nil {
|
||||||
|
tm, tmCleanup = createTeam(t, client, org)
|
||||||
|
}
|
||||||
|
|
||||||
|
if w == nil {
|
||||||
|
w, _ = createWorkspace(t, client, org)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{
|
||||||
|
Access: Access(AccessAdmin),
|
||||||
|
Team: tm,
|
||||||
|
Workspace: w,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ta, func() {
|
||||||
|
if err := client.TeamAccess.Remove(ctx, ta.ID); err != nil {
|
||||||
|
t.Errorf("Error removing team access! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"TeamAccess: %s\nError: %s", ta.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmCleanup != nil {
|
||||||
|
tmCleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTeamToken(t *testing.T, client *Client, tm *Team) (*TeamToken, func()) {
|
||||||
|
var tmCleanup func()
|
||||||
|
|
||||||
|
if tm == nil {
|
||||||
|
tm, tmCleanup = createTeam(t, client, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
tt, err := client.TeamTokens.Generate(ctx, tm.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tt, func() {
|
||||||
|
if err := client.TeamTokens.Delete(ctx, tm.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying team token! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"TeamToken: %s\nError: %s", tm.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmCleanup != nil {
|
||||||
|
tmCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createVariable(t *testing.T, client *Client, w *Workspace) (*Variable, func()) {
|
||||||
|
var wCleanup func()
|
||||||
|
|
||||||
|
if w == nil {
|
||||||
|
w, wCleanup = createWorkspace(t, client, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
v, err := client.Variables.Create(ctx, VariableCreateOptions{
|
||||||
|
Key: String(randomString(t)),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
Category: Category(CategoryTerraform),
|
||||||
|
Workspace: w,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, func() {
|
||||||
|
if err := client.Variables.Delete(ctx, v.ID); err != nil {
|
||||||
|
t.Errorf("Error destroying variable! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"Variable: %s\nError: %s", v.Key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wCleanup != nil {
|
||||||
|
wCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWorkspace(t *testing.T, client *Client, org *Organization) (*Workspace, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
w, err := client.Workspaces.Create(ctx, org.Name, WorkspaceCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, func() {
|
||||||
|
if err := client.Workspaces.Delete(ctx, org.Name, w.Name); err != nil {
|
||||||
|
t.Errorf("Error destroying workspace! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"Workspace: %s\nError: %s", w.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWorkspaceWithVCS(t *testing.T, client *Client, org *Organization) (*Workspace, func()) {
|
||||||
|
var orgCleanup func()
|
||||||
|
|
||||||
|
if org == nil {
|
||||||
|
org, orgCleanup = createOrganization(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
oc, ocCleanup := createOAuthToken(t, client, org)
|
||||||
|
|
||||||
|
githubIdentifier := os.Getenv("GITHUB_IDENTIFIER")
|
||||||
|
if githubIdentifier == "" {
|
||||||
|
t.Fatal("Export a valid GITHUB_IDENTIFIER before running this test!")
|
||||||
|
}
|
||||||
|
|
||||||
|
options := WorkspaceCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
VCSRepo: &VCSRepoOptions{
|
||||||
|
Identifier: String(githubIdentifier),
|
||||||
|
OAuthTokenID: String(oc.ID),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
w, err := client.Workspaces.Create(ctx, org.Name, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, func() {
|
||||||
|
if err := client.Workspaces.Delete(ctx, org.Name, w.Name); err != nil {
|
||||||
|
t.Errorf("Error destroying workspace! WARNING: Dangling resources\n"+
|
||||||
|
"may exist! The full error is shown below.\n\n"+
|
||||||
|
"Workspace: %s\nError: %s", w.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ocCleanup != nil {
|
||||||
|
ocCleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgCleanup != nil {
|
||||||
|
orgCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomString(t *testing.T) string {
|
||||||
|
v, err := uuid.GenerateUUID()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testLogReader(t *testing.T, h http.HandlerFunc) (*httptest.Server, *LogReader) {
|
||||||
|
ts := httptest.NewServer(h)
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Address: ts.URL,
|
||||||
|
Token: "dummy-token",
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logURL, err := url.Parse(ts.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lr := &LogReader{
|
||||||
|
client: client,
|
||||||
|
ctx: context.Background(),
|
||||||
|
logURL: logURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts, lr
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogReader_withMarkersSingle(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logReads := 0
|
||||||
|
ts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logReads++
|
||||||
|
switch {
|
||||||
|
case logReads == 2:
|
||||||
|
w.Write([]byte("\x02Terraform run started - logs - Terraform run finished\x03"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
doneReads := 0
|
||||||
|
lr.done = func() (bool, error) {
|
||||||
|
doneReads++
|
||||||
|
if logReads >= 2 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(lr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "Terraform run started - logs - Terraform run finished"
|
||||||
|
if string(logs) != expected {
|
||||||
|
t.Fatalf("expected %s, got: %s", expected, string(logs))
|
||||||
|
}
|
||||||
|
if doneReads != 1 {
|
||||||
|
t.Fatalf("expected 1 done reads, got %d reads", doneReads)
|
||||||
|
}
|
||||||
|
if logReads != 3 {
|
||||||
|
t.Fatalf("expected 3 log reads, got %d reads", logReads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogReader_withMarkersDouble(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logReads := 0
|
||||||
|
ts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logReads++
|
||||||
|
switch {
|
||||||
|
case logReads == 2:
|
||||||
|
w.Write([]byte("\x02Terraform run started"))
|
||||||
|
case logReads == 3:
|
||||||
|
w.Write([]byte(" - logs - Terraform run finished\x03"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
doneReads := 0
|
||||||
|
lr.done = func() (bool, error) {
|
||||||
|
doneReads++
|
||||||
|
if logReads >= 3 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(lr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "Terraform run started - logs - Terraform run finished"
|
||||||
|
if string(logs) != expected {
|
||||||
|
t.Fatalf("expected %s, got: %s", expected, string(logs))
|
||||||
|
}
|
||||||
|
if doneReads != 1 {
|
||||||
|
t.Fatalf("expected 1 done reads, got %d reads", doneReads)
|
||||||
|
}
|
||||||
|
if logReads != 4 {
|
||||||
|
t.Fatalf("expected 4 log reads, got %d reads", logReads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogReader_withMarkersMulti(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logReads := 0
|
||||||
|
ts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logReads++
|
||||||
|
switch {
|
||||||
|
case logReads == 2:
|
||||||
|
w.Write([]byte("\x02"))
|
||||||
|
case logReads == 3:
|
||||||
|
w.Write([]byte("Terraform run started"))
|
||||||
|
case logReads == 16:
|
||||||
|
w.Write([]byte(" - logs - "))
|
||||||
|
case logReads == 30:
|
||||||
|
w.Write([]byte("Terraform run finished"))
|
||||||
|
case logReads == 31:
|
||||||
|
w.Write([]byte("\x03"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
doneReads := 0
|
||||||
|
lr.done = func() (bool, error) {
|
||||||
|
doneReads++
|
||||||
|
if logReads >= 31 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(lr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "Terraform run started - logs - Terraform run finished"
|
||||||
|
if string(logs) != expected {
|
||||||
|
t.Fatalf("expected %s, got: %s", expected, string(logs))
|
||||||
|
}
|
||||||
|
if doneReads != 3 {
|
||||||
|
t.Fatalf("expected 3 done reads, got %d reads", doneReads)
|
||||||
|
}
|
||||||
|
if logReads != 31 {
|
||||||
|
t.Fatalf("expected 31 log reads, got %d reads", logReads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogReader_withoutMarkers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logReads := 0
|
||||||
|
ts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logReads++
|
||||||
|
switch {
|
||||||
|
case logReads == 2:
|
||||||
|
w.Write([]byte("Terraform run started"))
|
||||||
|
case logReads == 16:
|
||||||
|
w.Write([]byte(" - logs - "))
|
||||||
|
case logReads == 31:
|
||||||
|
w.Write([]byte("Terraform run finished"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
doneReads := 0
|
||||||
|
lr.done = func() (bool, error) {
|
||||||
|
doneReads++
|
||||||
|
if logReads >= 31 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(lr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "Terraform run started - logs - Terraform run finished"
|
||||||
|
if string(logs) != expected {
|
||||||
|
t.Fatalf("expected %s, got: %s", expected, string(logs))
|
||||||
|
}
|
||||||
|
if doneReads != 25 {
|
||||||
|
t.Fatalf("expected 14 done reads, got %d reads", doneReads)
|
||||||
|
}
|
||||||
|
if logReads != 32 {
|
||||||
|
t.Fatalf("expected 32 log reads, got %d reads", logReads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogReader_withoutEndOfTextMarker(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logReads := 0
|
||||||
|
ts, lr := testLogReader(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logReads++
|
||||||
|
switch {
|
||||||
|
case logReads == 2:
|
||||||
|
w.Write([]byte("\x02"))
|
||||||
|
case logReads == 3:
|
||||||
|
w.Write([]byte("Terraform run started"))
|
||||||
|
case logReads == 16:
|
||||||
|
w.Write([]byte(" - logs - "))
|
||||||
|
case logReads == 31:
|
||||||
|
w.Write([]byte("Terraform run finished"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
doneReads := 0
|
||||||
|
lr.done = func() (bool, error) {
|
||||||
|
doneReads++
|
||||||
|
if logReads >= 31 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(lr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "Terraform run started - logs - Terraform run finished"
|
||||||
|
if string(logs) != expected {
|
||||||
|
t.Fatalf("expected %s, got: %s", expected, string(logs))
|
||||||
|
}
|
||||||
|
if doneReads != 3 {
|
||||||
|
t.Fatalf("expected 3 done reads, got %d reads", doneReads)
|
||||||
|
}
|
||||||
|
if logReads != 42 {
|
||||||
|
t.Fatalf("expected 42 log reads, got %d reads", logReads)
|
||||||
|
}
|
||||||
|
}
|
218
vendor/github.com/hashicorp/go-tfe/notification_configuration_test.go
generated
vendored
Normal file
218
vendor/github.com/hashicorp/go-tfe/notification_configuration_test.go
generated
vendored
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNotificationConfigurationList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
ncTest1, _ := createNotificationConfiguration(t, client, wTest)
|
||||||
|
ncTest2, _ := createNotificationConfiguration(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("with a valid workspace", func(t *testing.T) {
|
||||||
|
ncl, err := client.NotificationConfigurations.List(
|
||||||
|
ctx,
|
||||||
|
wTest.ID,
|
||||||
|
NotificationConfigurationListOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, ncl.Items, ncTest1)
|
||||||
|
assert.Contains(t, ncl.Items, ncTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, ncl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, ncl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
ncl, err := client.NotificationConfigurations.List(
|
||||||
|
ctx,
|
||||||
|
wTest.ID,
|
||||||
|
NotificationConfigurationListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, ncl.Items)
|
||||||
|
assert.Equal(t, 999, ncl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, ncl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace", func(t *testing.T) {
|
||||||
|
ncl, err := client.NotificationConfigurations.List(
|
||||||
|
ctx,
|
||||||
|
badIdentifier,
|
||||||
|
NotificationConfigurationListOptions{},
|
||||||
|
)
|
||||||
|
assert.Nil(t, ncl)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotificationConfigurationCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with all required values", func(t *testing.T) {
|
||||||
|
options := NotificationConfigurationCreateOptions{
|
||||||
|
DestinationType: NotificationDestination(NotificationDestinationTypeGeneric),
|
||||||
|
Enabled: Bool(false),
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Token: String(randomString(t)),
|
||||||
|
URL: String("http://example.com"),
|
||||||
|
Triggers: []string{NotificationTriggerCreated},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a required value", func(t *testing.T) {
|
||||||
|
options := NotificationConfigurationCreateOptions{
|
||||||
|
DestinationType: NotificationDestination(NotificationDestinationTypeGeneric),
|
||||||
|
Enabled: Bool(false),
|
||||||
|
Token: String(randomString(t)),
|
||||||
|
URL: String("http://example.com"),
|
||||||
|
Triggers: []string{NotificationTriggerCreated},
|
||||||
|
}
|
||||||
|
|
||||||
|
nc, err := client.NotificationConfigurations.Create(ctx, wTest.ID, options)
|
||||||
|
assert.Nil(t, nc)
|
||||||
|
assert.EqualError(t, err, "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace", func(t *testing.T) {
|
||||||
|
nc, err := client.NotificationConfigurations.Create(ctx, badIdentifier, NotificationConfigurationCreateOptions{})
|
||||||
|
assert.Nil(t, nc)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotificationConfigurationRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ncTest, ncTestCleanup := createNotificationConfiguration(t, client, nil)
|
||||||
|
defer ncTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with a valid ID", func(t *testing.T) {
|
||||||
|
nc, err := client.NotificationConfigurations.Read(ctx, ncTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ncTest.ID, nc.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration does not exist", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Read(ctx, "nonexisting")
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration ID is invalid", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Read(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for notification configuration ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotificationConfigurationUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ncTest, ncTestCleanup := createNotificationConfiguration(t, client, nil)
|
||||||
|
defer ncTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with options", func(t *testing.T) {
|
||||||
|
options := NotificationConfigurationUpdateOptions{
|
||||||
|
Enabled: Bool(true),
|
||||||
|
Name: String("newName"),
|
||||||
|
}
|
||||||
|
|
||||||
|
nc, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, nc.Enabled, true)
|
||||||
|
assert.Equal(t, nc.Name, "newName")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without options", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Update(ctx, ncTest.ID, NotificationConfigurationUpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration does not exist", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Update(ctx, "nonexisting", NotificationConfigurationUpdateOptions{})
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration ID is invalid", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Update(ctx, badIdentifier, NotificationConfigurationUpdateOptions{})
|
||||||
|
assert.EqualError(t, err, "invalid value for notification configuration ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotificationConfigurationDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
ncTest, _ := createNotificationConfiguration(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("with a valid ID", func(t *testing.T) {
|
||||||
|
err := client.NotificationConfigurations.Delete(ctx, ncTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = client.NotificationConfigurations.Read(ctx, ncTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration does not exist", func(t *testing.T) {
|
||||||
|
err := client.NotificationConfigurations.Delete(ctx, "nonexisting")
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.NotificationConfigurations.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for notification configuration ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotificationConfigurationVerify(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ncTest, ncTestCleanup := createNotificationConfiguration(t, client, nil)
|
||||||
|
defer ncTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with a valid ID", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Verify(ctx, ncTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration does not exists", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Verify(ctx, "nonexisting")
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the notification configuration ID is invalid", func(t *testing.T) {
|
||||||
|
_, err := client.NotificationConfigurations.Verify(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for notification configuration ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOAuthClientsList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
ocTest1, _ := createOAuthClient(t, client, orgTest)
|
||||||
|
ocTest2, _ := createOAuthClient(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
options := OAuthClientListOptions{}
|
||||||
|
|
||||||
|
ocl, err := client.OAuthClients.List(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("the OAuth tokens relationship is decoded correcly", func(t *testing.T) {
|
||||||
|
for _, oc := range ocl.Items {
|
||||||
|
assert.Equal(t, 1, len(oc.OAuthTokens))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// We need to strip some fields before the next test.
|
||||||
|
for _, oc := range append(ocl.Items, ocTest1, ocTest2) {
|
||||||
|
oc.OAuthTokens = nil
|
||||||
|
oc.Organization = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, ocl.Items, ocTest1)
|
||||||
|
assert.Contains(t, ocl.Items, ocTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, ocl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, ocl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
options := OAuthClientListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ocl, err := client.OAuthClients.List(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, ocl.Items)
|
||||||
|
assert.Equal(t, 999, ocl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, ocl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
options := OAuthClientListOptions{}
|
||||||
|
|
||||||
|
ocl, err := client.OAuthClients.List(ctx, badIdentifier, options)
|
||||||
|
assert.Nil(t, ocl)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuthClientsCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
githubToken := os.Getenv("GITHUB_TOKEN")
|
||||||
|
if githubToken == "" {
|
||||||
|
t.Fatal("Export a valid GITHUB_TOKEN before running this test!")
|
||||||
|
}
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := OAuthClientCreateOptions{
|
||||||
|
APIURL: String("https://api.github.com"),
|
||||||
|
HTTPURL: String("https://github.com"),
|
||||||
|
OAuthToken: String(githubToken),
|
||||||
|
ServiceProvider: ServiceProvider(ServiceProviderGithub),
|
||||||
|
}
|
||||||
|
|
||||||
|
oc, err := client.OAuthClients.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, oc.ID)
|
||||||
|
assert.Equal(t, "https://api.github.com", oc.APIURL)
|
||||||
|
assert.Equal(t, "https://github.com", oc.HTTPURL)
|
||||||
|
assert.Equal(t, 1, len(oc.OAuthTokens))
|
||||||
|
assert.Equal(t, ServiceProviderGithub, oc.ServiceProvider)
|
||||||
|
|
||||||
|
t.Run("the organization relationship is decoded correcly", func(t *testing.T) {
|
||||||
|
assert.NotEmpty(t, oc.Organization)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without an valid organization", func(t *testing.T) {
|
||||||
|
options := OAuthClientCreateOptions{
|
||||||
|
APIURL: String("https://api.github.com"),
|
||||||
|
HTTPURL: String("https://github.com"),
|
||||||
|
OAuthToken: String(githubToken),
|
||||||
|
ServiceProvider: ServiceProvider(ServiceProviderGithub),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.OAuthClients.Create(ctx, badIdentifier, options)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without an API URL", func(t *testing.T) {
|
||||||
|
options := OAuthClientCreateOptions{
|
||||||
|
HTTPURL: String("https://github.com"),
|
||||||
|
OAuthToken: String(githubToken),
|
||||||
|
ServiceProvider: ServiceProvider(ServiceProviderGithub),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.EqualError(t, err, "API URL is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a HTTP URL", func(t *testing.T) {
|
||||||
|
options := OAuthClientCreateOptions{
|
||||||
|
APIURL: String("https://api.github.com"),
|
||||||
|
OAuthToken: String(githubToken),
|
||||||
|
ServiceProvider: ServiceProvider(ServiceProviderGithub),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.EqualError(t, err, "HTTP URL is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without an OAuth token", func(t *testing.T) {
|
||||||
|
options := OAuthClientCreateOptions{
|
||||||
|
APIURL: String("https://api.github.com"),
|
||||||
|
HTTPURL: String("https://github.com"),
|
||||||
|
ServiceProvider: ServiceProvider(ServiceProviderGithub),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.EqualError(t, err, "OAuth token is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a service provider", func(t *testing.T) {
|
||||||
|
options := OAuthClientCreateOptions{
|
||||||
|
APIURL: String("https://api.github.com"),
|
||||||
|
HTTPURL: String("https://github.com"),
|
||||||
|
OAuthToken: String(githubToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.OAuthClients.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.EqualError(t, err, "service provider is required")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuthClientsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ocTest, ocTestCleanup := createOAuthClient(t, client, nil)
|
||||||
|
defer ocTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the OAuth client exists", func(t *testing.T) {
|
||||||
|
oc, err := client.OAuthClients.Read(ctx, ocTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ocTest.ID, oc.ID)
|
||||||
|
assert.Equal(t, ocTest.APIURL, oc.APIURL)
|
||||||
|
assert.Equal(t, ocTest.CallbackURL, oc.CallbackURL)
|
||||||
|
assert.Equal(t, ocTest.ConnectPath, oc.ConnectPath)
|
||||||
|
assert.Equal(t, ocTest.HTTPURL, oc.HTTPURL)
|
||||||
|
assert.Equal(t, ocTest.ServiceProvider, oc.ServiceProvider)
|
||||||
|
assert.Equal(t, ocTest.ServiceProviderName, oc.ServiceProviderName)
|
||||||
|
assert.Equal(t, ocTest.OAuthTokens, oc.OAuthTokens)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the OAuth client does not exist", func(t *testing.T) {
|
||||||
|
oc, err := client.OAuthClients.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, oc)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid OAuth client ID", func(t *testing.T) {
|
||||||
|
oc, err := client.OAuthClients.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, oc)
|
||||||
|
assert.EqualError(t, err, "invalid value for OAuth client ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuthClientsDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
ocTest, _ := createOAuthClient(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.OAuthClients.Delete(ctx, ocTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the OAuth client - it should fail.
|
||||||
|
_, err = client.OAuthClients.Read(ctx, ocTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the OAuth client does not exist", func(t *testing.T) {
|
||||||
|
err := client.OAuthClients.Delete(ctx, ocTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the OAuth client ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.OAuthClients.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for OAuth client ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOAuthTokensList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
otTest1, _ := createOAuthToken(t, client, orgTest)
|
||||||
|
otTest2, _ := createOAuthToken(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
options := OAuthTokenListOptions{}
|
||||||
|
|
||||||
|
otl, err := client.OAuthTokens.List(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("the OAuth client relationship is decoded correcly", func(t *testing.T) {
|
||||||
|
for _, ot := range otl.Items {
|
||||||
|
assert.NotEmpty(t, ot.OAuthClient)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// We need to strip some fields before the next test.
|
||||||
|
for _, ot := range otl.Items {
|
||||||
|
ot.CreatedAt = time.Time{}
|
||||||
|
ot.ServiceProviderUser = ""
|
||||||
|
ot.OAuthClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, otl.Items, otTest1)
|
||||||
|
assert.Contains(t, otl.Items, otTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, otl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, otl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
options := OAuthTokenListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
otl, err := client.OAuthTokens.List(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, otl.Items)
|
||||||
|
assert.Equal(t, 999, otl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, otl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
options := OAuthTokenListOptions{}
|
||||||
|
|
||||||
|
otl, err := client.OAuthTokens.List(ctx, badIdentifier, options)
|
||||||
|
assert.Nil(t, otl)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuthTokensRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
otTest, otTestCleanup := createOAuthToken(t, client, nil)
|
||||||
|
defer otTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the OAuth token exists", func(t *testing.T) {
|
||||||
|
ot, err := client.OAuthTokens.Read(ctx, otTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, otTest.ID, ot.ID)
|
||||||
|
assert.NotEmpty(t, ot.OAuthClient)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the OAuth token does not exist", func(t *testing.T) {
|
||||||
|
ot, err := client.OAuthTokens.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, ot)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid OAuth token ID", func(t *testing.T) {
|
||||||
|
ot, err := client.OAuthTokens.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, ot)
|
||||||
|
assert.EqualError(t, err, "invalid value for OAuth token ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuthTokensUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
otTest, otTestCleanup := createOAuthToken(t, client, nil)
|
||||||
|
defer otTestCleanup()
|
||||||
|
|
||||||
|
t.Run("before updating with an SSH key", func(t *testing.T) {
|
||||||
|
assert.False(t, otTest.HasSSHKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without options", func(t *testing.T) {
|
||||||
|
ot, err := client.OAuthTokens.Update(ctx, otTest.ID, OAuthTokenUpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, ot.HasSSHKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when updating with a valid SSH key", func(t *testing.T) {
|
||||||
|
dummyPrivateSSHKey := `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXgIBAAKBgQDIF0s2yX7dSQQL1grdTbai1Mb7sEco6RIOz8iqrHTGqmESpu5n
|
||||||
|
d8imMkV5KadgVBJ/UvHsWpg446O3DAMYn0Y6f8dDlK7pmCEtiGVKTR1PaVRMpF8R
|
||||||
|
5Guvrmlru8Kex5ozh0pPMB15aGsIzSezCKgSs74Od9YL4smdgKyYwqsu3wIDAQAB
|
||||||
|
AoGBAKCs6+4j4icqYgBrMjBCHp4lRWCJTqtQdfrE6jv73o5F9Uu4FwupScwD5HwG
|
||||||
|
cezNtkjeP3zvxvsv+aCdGcNk60vSz4n9Nt6gEJveWFSpePYXKZ9cz/IjFLI7nSzc
|
||||||
|
1msLyE3DfUqB91s/A/aT5p0LiVDc8i4mCGDOga2OINIwqDGZAkEA/Vz8dkcqsAVW
|
||||||
|
CL1F000hWTrM6tu0V+x8Nm8CRx7wM/Gy/19PbV0t26wCVG0GXyLWsV2//huY7w5b
|
||||||
|
3AcSl5pfJQJBAMosYQXk5L4S+qivz2zmZdtyz+Ik6IZ3PwZoED32PxGSdW5rG8iP
|
||||||
|
V+iSJek5ESkS1zeXwDMnF4LeoBY9H07DiLMCQQCrHm1o2SIMpl34IxWQ4+wdHuid
|
||||||
|
yuuf4pn2Db2lGVE0VA8ICXBUtfUuA5vDN6tw/8+vFVmBn1QISVNjZOd6uwl9AkA+
|
||||||
|
jIRoAm0SsWSDlAEkvBN/VYIjgS+/il0haki8ItdYZGuYgeLSpiaYeb7o7RL2FjIn
|
||||||
|
rPd12/5WKvJ0buykvbIpAkEA5Uy3T8xQJkDGbp0+xA0yThoOYiB09lAok8I7Sv/5
|
||||||
|
dpIe8YOINN27XaojJvVpT5uBVCcZLF+G7kaMjSwCTlDx3Q==
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
|
||||||
|
ot, err := client.OAuthTokens.Update(ctx, otTest.ID, OAuthTokenUpdateOptions{
|
||||||
|
PrivateSSHKey: String(dummyPrivateSSHKey),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, otTest.ID, ot.ID)
|
||||||
|
assert.True(t, ot.HasSSHKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when updating with an invalid SSH key", func(t *testing.T) {
|
||||||
|
ot, err := client.OAuthTokens.Update(ctx, otTest.ID, OAuthTokenUpdateOptions{
|
||||||
|
PrivateSSHKey: String(randomString(t)),
|
||||||
|
})
|
||||||
|
assert.Nil(t, ot)
|
||||||
|
assert.Contains(t, err.Error(), "Ssh key is invalid")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid policy ID", func(t *testing.T) {
|
||||||
|
ot, err := client.OAuthTokens.Update(ctx, badIdentifier, OAuthTokenUpdateOptions{})
|
||||||
|
assert.Nil(t, ot)
|
||||||
|
assert.EqualError(t, err, "invalid value for OAuth token ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuthTokensDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
otTest, _ := createOAuthToken(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.OAuthTokens.Delete(ctx, otTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the OAuth token - it should fail.
|
||||||
|
_, err = client.OAuthTokens.Read(ctx, otTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the OAuth token does not exist", func(t *testing.T) {
|
||||||
|
err := client.OAuthTokens.Delete(ctx, otTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the OAuth token ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.OAuthTokens.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for OAuth token ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -77,6 +77,7 @@ type OrganizationList struct {
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
Name string `jsonapi:"primary,organizations"`
|
Name string `jsonapi:"primary,organizations"`
|
||||||
CollaboratorAuthPolicy AuthPolicyType `jsonapi:"attr,collaborator-auth-policy"`
|
CollaboratorAuthPolicy AuthPolicyType `jsonapi:"attr,collaborator-auth-policy"`
|
||||||
|
CostEstimationEnabled bool `jsonapi:"attr,cost-estimation-enabled"`
|
||||||
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
|
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
|
||||||
Email string `jsonapi:"attr,email"`
|
Email string `jsonapi:"attr,email"`
|
||||||
EnterprisePlan EnterprisePlanType `jsonapi:"attr,enterprise-plan"`
|
EnterprisePlan EnterprisePlanType `jsonapi:"attr,enterprise-plan"`
|
||||||
|
@ -167,6 +168,9 @@ type OrganizationCreateOptions struct {
|
||||||
// Authentication policy.
|
// Authentication policy.
|
||||||
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"`
|
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"`
|
||||||
|
|
||||||
|
// Enable Cost Estimation
|
||||||
|
CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"`
|
||||||
|
|
||||||
// The name of the "owners" team
|
// The name of the "owners" team
|
||||||
OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"`
|
OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -248,6 +252,9 @@ type OrganizationUpdateOptions struct {
|
||||||
// Authentication policy.
|
// Authentication policy.
|
||||||
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"`
|
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"`
|
||||||
|
|
||||||
|
// Enable Cost Estimation
|
||||||
|
CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"`
|
||||||
|
|
||||||
// The name of the "owners" team
|
// The name of the "owners" team
|
||||||
OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"`
|
OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,367 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrganizationsList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest1, orgTest1Cleanup := createOrganization(t, client)
|
||||||
|
defer orgTest1Cleanup()
|
||||||
|
orgTest2, orgTest2Cleanup := createOrganization(t, client)
|
||||||
|
defer orgTest2Cleanup()
|
||||||
|
|
||||||
|
t.Run("with no list options", func(t *testing.T) {
|
||||||
|
orgl, err := client.Organizations.List(ctx, OrganizationListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, orgl.Items, orgTest1)
|
||||||
|
assert.Contains(t, orgl.Items, orgTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, orgl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, orgl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
orgl, err := client.Organizations.List(ctx, OrganizationListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, orgl)
|
||||||
|
assert.Equal(t, 999, orgl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, orgl.TotalCount)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationsCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := OrganizationCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Email: String(randomString(t) + "@tfe.local"),
|
||||||
|
}
|
||||||
|
|
||||||
|
org, err := client.Organizations.Create(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make sure we clean up the created org.
|
||||||
|
defer client.Organizations.Delete(ctx, org.Name)
|
||||||
|
|
||||||
|
assert.Equal(t, *options.Name, org.Name)
|
||||||
|
assert.Equal(t, *options.Email, org.Email)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when no email is provided", func(t *testing.T) {
|
||||||
|
org, err := client.Organizations.Create(ctx, OrganizationCreateOptions{
|
||||||
|
Name: String("foo"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, org)
|
||||||
|
assert.EqualError(t, err, "email is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when no name is provided", func(t *testing.T) {
|
||||||
|
_, err := client.Organizations.Create(ctx, OrganizationCreateOptions{
|
||||||
|
Email: String("foo@bar.com"),
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid name", func(t *testing.T) {
|
||||||
|
org, err := client.Organizations.Create(ctx, OrganizationCreateOptions{
|
||||||
|
Name: String(badIdentifier),
|
||||||
|
Email: String("foo@bar.com"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, org)
|
||||||
|
assert.EqualError(t, err, "invalid value for name")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the org exists", func(t *testing.T) {
|
||||||
|
org, err := client.Organizations.Read(ctx, orgTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, orgTest, org)
|
||||||
|
|
||||||
|
t.Run("permissions are properly decoded", func(t *testing.T) {
|
||||||
|
assert.True(t, org.Permissions.CanDestroy)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("timestamps are populated", func(t *testing.T) {
|
||||||
|
assert.NotEmpty(t, org.CreatedAt)
|
||||||
|
assert.NotEmpty(t, org.TrialExpiresAt)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid name", func(t *testing.T) {
|
||||||
|
org, err := client.Organizations.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, org)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the org does not exist", func(t *testing.T) {
|
||||||
|
_, err := client.Organizations.Read(ctx, randomString(t))
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationsUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
|
||||||
|
options := OrganizationUpdateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Email: String(randomString(t) + "@tfe.local"),
|
||||||
|
SessionTimeout: Int(3600),
|
||||||
|
SessionRemember: Int(3600),
|
||||||
|
}
|
||||||
|
|
||||||
|
org, err := client.Organizations.Update(ctx, orgTest.Name, options)
|
||||||
|
if err != nil {
|
||||||
|
orgTestCleanup()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make sure we clean up the renamed org.
|
||||||
|
defer client.Organizations.Delete(ctx, org.Name)
|
||||||
|
|
||||||
|
// Also get a fresh result from the API to ensure we get the
|
||||||
|
// expected values back.
|
||||||
|
refreshed, err := client.Organizations.Read(ctx, *options.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*Organization{
|
||||||
|
org,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.Equal(t, *options.Name, item.Name)
|
||||||
|
assert.Equal(t, *options.Email, item.Email)
|
||||||
|
assert.Equal(t, *options.SessionTimeout, item.SessionTimeout)
|
||||||
|
assert.Equal(t, *options.SessionRemember, item.SessionRemember)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid name", func(t *testing.T) {
|
||||||
|
org, err := client.Organizations.Update(ctx, badIdentifier, OrganizationUpdateOptions{})
|
||||||
|
assert.Nil(t, org)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when only updating a subset of fields", func(t *testing.T) {
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
org, err := client.Organizations.Update(ctx, orgTest.Name, OrganizationUpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, orgTest.Name, org.Name)
|
||||||
|
assert.Equal(t, orgTest.Email, org.Email)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationsDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
orgTest, _ := createOrganization(t, client)
|
||||||
|
|
||||||
|
err := client.Organizations.Delete(ctx, orgTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try fetching the org again - it should error.
|
||||||
|
_, err = client.Organizations.Read(ctx, orgTest.Name)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid name", func(t *testing.T) {
|
||||||
|
err := client.Organizations.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationsCapacity(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest1, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest2, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest3, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest4, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("without queued runs", func(t *testing.T) {
|
||||||
|
c, err := client.Organizations.Capacity(ctx, orgTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, c.Pending)
|
||||||
|
assert.Equal(t, 0, c.Running)
|
||||||
|
})
|
||||||
|
|
||||||
|
// For this test FRQ should be enabled and have a
|
||||||
|
// limit of 2 concurrent runs per organization.
|
||||||
|
t.Run("with queued runs", func(t *testing.T) {
|
||||||
|
_, _ = createRun(t, client, wTest1)
|
||||||
|
_, _ = createRun(t, client, wTest2)
|
||||||
|
_, _ = createRun(t, client, wTest3)
|
||||||
|
_, _ = createRun(t, client, wTest4)
|
||||||
|
|
||||||
|
c, err := client.Organizations.Capacity(ctx, orgTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, c.Pending)
|
||||||
|
assert.Equal(t, 2, c.Running)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid name", func(t *testing.T) {
|
||||||
|
org, err := client.Organizations.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, org)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the org does not exist", func(t *testing.T) {
|
||||||
|
_, err := client.Organizations.Read(ctx, randomString(t))
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationsEntitlements(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the org exists", func(t *testing.T) {
|
||||||
|
entitlements, err := client.Organizations.Entitlements(ctx, orgTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, entitlements.ID)
|
||||||
|
assert.True(t, entitlements.Operations)
|
||||||
|
assert.True(t, entitlements.PrivateModuleRegistry)
|
||||||
|
assert.True(t, entitlements.Sentinel)
|
||||||
|
assert.True(t, entitlements.StateStorage)
|
||||||
|
assert.True(t, entitlements.Teams)
|
||||||
|
assert.True(t, entitlements.VCSIntegrations)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid name", func(t *testing.T) {
|
||||||
|
entitlements, err := client.Organizations.Entitlements(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, entitlements)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the org does not exist", func(t *testing.T) {
|
||||||
|
_, err := client.Organizations.Entitlements(ctx, randomString(t))
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationsRunQueue(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest1, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest2, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest3, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest4, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("without queued runs", func(t *testing.T) {
|
||||||
|
rq, err := client.Organizations.RunQueue(ctx, orgTest.Name, RunQueueOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(rq.Items))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a couple or runs to fill the queue.
|
||||||
|
rTest1, _ := createRun(t, client, wTest1)
|
||||||
|
rTest2, _ := createRun(t, client, wTest2)
|
||||||
|
rTest3, _ := createRun(t, client, wTest3)
|
||||||
|
rTest4, _ := createRun(t, client, wTest4)
|
||||||
|
|
||||||
|
// For this test FRQ should be enabled and have a
|
||||||
|
// limit of 2 concurrent runs per organization.
|
||||||
|
t.Run("with queued runs", func(t *testing.T) {
|
||||||
|
rq, err := client.Organizations.RunQueue(ctx, orgTest.Name, RunQueueOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
found := []string{}
|
||||||
|
for _, r := range rq.Items {
|
||||||
|
found = append(found, r.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, found, rTest1.ID)
|
||||||
|
assert.Contains(t, found, rTest2.ID)
|
||||||
|
assert.Contains(t, found, rTest3.ID)
|
||||||
|
assert.Contains(t, found, rTest4.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without queue options", func(t *testing.T) {
|
||||||
|
rq, err := client.Organizations.RunQueue(ctx, orgTest.Name, RunQueueOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
found := []string{}
|
||||||
|
for _, r := range rq.Items {
|
||||||
|
found = append(found, r.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, found, rTest1.ID)
|
||||||
|
assert.Contains(t, found, rTest2.ID)
|
||||||
|
assert.Contains(t, found, rTest3.ID)
|
||||||
|
assert.Contains(t, found, rTest4.ID)
|
||||||
|
assert.Equal(t, 1, rq.CurrentPage)
|
||||||
|
assert.Equal(t, 4, rq.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with queue options", func(t *testing.T) {
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
rq, err := client.Organizations.RunQueue(ctx, orgTest.Name, RunQueueOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, rq.Items)
|
||||||
|
assert.Equal(t, 999, rq.CurrentPage)
|
||||||
|
assert.Equal(t, 4, rq.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid name", func(t *testing.T) {
|
||||||
|
org, err := client.Organizations.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, org)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the org does not exist", func(t *testing.T) {
|
||||||
|
_, err := client.Organizations.Read(ctx, randomString(t))
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrganizationTokensGenerate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
var tkToken string
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
ot, err := client.OrganizationTokens.Generate(ctx, orgTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, ot.Token)
|
||||||
|
tkToken = ot.Token
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a token already exists", func(t *testing.T) {
|
||||||
|
ot, err := client.OrganizationTokens.Generate(ctx, orgTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, ot.Token)
|
||||||
|
assert.NotEqual(t, tkToken, ot.Token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without valid organization", func(t *testing.T) {
|
||||||
|
ot, err := client.OrganizationTokens.Generate(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, ot)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationTokensRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
_, otTestCleanup := createOrganizationToken(t, client, orgTest)
|
||||||
|
|
||||||
|
ot, err := client.OrganizationTokens.Read(ctx, orgTest.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, ot)
|
||||||
|
|
||||||
|
otTestCleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a token doesn't exists", func(t *testing.T) {
|
||||||
|
ot, err := client.OrganizationTokens.Read(ctx, orgTest.Name)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
assert.Nil(t, ot)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without valid organization", func(t *testing.T) {
|
||||||
|
ot, err := client.OrganizationTokens.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, ot)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrganizationTokensDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
createOrganizationToken(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.OrganizationTokens.Delete(ctx, orgTest.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a token does not exist", func(t *testing.T) {
|
||||||
|
err := client.OrganizationTokens.Delete(ctx, orgTest.Name)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without valid organization", func(t *testing.T) {
|
||||||
|
err := client.OrganizationTokens.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlansRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rTest, rTestCleanup := createPlannedRun(t, client, nil)
|
||||||
|
defer rTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the plan exists", func(t *testing.T) {
|
||||||
|
p, err := client.Plans.Read(ctx, rTest.Plan.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, p.HasChanges)
|
||||||
|
assert.NotEmpty(t, p.LogReadURL)
|
||||||
|
assert.Equal(t, p.Status, PlanFinished)
|
||||||
|
assert.NotEmpty(t, p.StatusTimestamps)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the plan does not exist", func(t *testing.T) {
|
||||||
|
p, err := client.Plans.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid plan ID", func(t *testing.T) {
|
||||||
|
p, err := client.Plans.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "invalid value for plan ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlansLogs(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rTest, rTestCleanup := createPlannedRun(t, client, nil)
|
||||||
|
defer rTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the log exists", func(t *testing.T) {
|
||||||
|
p, err := client.Plans.Read(ctx, rTest.Plan.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logReader, err := client.Plans.Logs(ctx, p.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(logReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, string(logs), "1 to add, 0 to change, 0 to destroy")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the log does not exist", func(t *testing.T) {
|
||||||
|
logs, err := client.Plans.Logs(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, logs)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPolicyChecksList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest1, _ := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
pTest2, _ := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
createPolicySet(t, client, orgTest, []*Policy{pTest1, pTest2}, []*Workspace{wTest})
|
||||||
|
|
||||||
|
rTest, _ := createPlannedRun(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
pcl, err := client.PolicyChecks.List(ctx, rTest.ID, PolicyCheckListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(pcl.Items))
|
||||||
|
|
||||||
|
t.Run("pagination is properly decoded", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, pcl.CurrentPage)
|
||||||
|
assert.Equal(t, 1, pcl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("permissions are properly decoded", func(t *testing.T) {
|
||||||
|
assert.NotEmpty(t, pcl.Items[0].Permissions)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("result is properly decoded", func(t *testing.T) {
|
||||||
|
require.NotEmpty(t, pcl.Items[0].Result)
|
||||||
|
assert.Equal(t, 2, pcl.Items[0].Result.Passed)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("timestamps are properly decoded", func(t *testing.T) {
|
||||||
|
assert.NotEmpty(t, pcl.Items[0].StatusTimestamps)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
pcl, err := client.PolicyChecks.List(ctx, rTest.ID, PolicyCheckListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, pcl.Items)
|
||||||
|
assert.Equal(t, 999, pcl.CurrentPage)
|
||||||
|
assert.Equal(t, 1, pcl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid run ID", func(t *testing.T) {
|
||||||
|
pcl, err := client.PolicyChecks.List(ctx, badIdentifier, PolicyCheckListOptions{})
|
||||||
|
assert.Nil(t, pcl)
|
||||||
|
assert.EqualError(t, err, "invalid value for run ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyChecksRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest, _ := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest})
|
||||||
|
|
||||||
|
rTest, _ := createPlannedRun(t, client, wTest)
|
||||||
|
require.Equal(t, 1, len(rTest.PolicyChecks))
|
||||||
|
|
||||||
|
t.Run("when the policy check exists", func(t *testing.T) {
|
||||||
|
pc, err := client.PolicyChecks.Read(ctx, rTest.PolicyChecks[0].ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, pc.Permissions)
|
||||||
|
assert.Equal(t, PolicyScopeOrganization, pc.Scope)
|
||||||
|
assert.Equal(t, PolicyPasses, pc.Status)
|
||||||
|
assert.NotEmpty(t, pc.StatusTimestamps)
|
||||||
|
|
||||||
|
t.Run("result is properly decoded", func(t *testing.T) {
|
||||||
|
require.NotEmpty(t, pc.Result)
|
||||||
|
assert.Equal(t, 1, pc.Result.Passed)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the policy check does not exist", func(t *testing.T) {
|
||||||
|
pc, err := client.PolicyChecks.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, pc)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid policy check ID", func(t *testing.T) {
|
||||||
|
pc, err := client.PolicyChecks.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, pc)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy check ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyChecksOverride(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the policy failed", func(t *testing.T) {
|
||||||
|
pTest, pTestCleanup := createUploadedPolicy(t, client, false, orgTest)
|
||||||
|
defer pTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest})
|
||||||
|
rTest, _ := createPlannedRun(t, client, wTest)
|
||||||
|
|
||||||
|
pcl, err := client.PolicyChecks.List(ctx, rTest.ID, PolicyCheckListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(pcl.Items))
|
||||||
|
require.Equal(t, PolicySoftFailed, pcl.Items[0].Status)
|
||||||
|
|
||||||
|
pc, err := client.PolicyChecks.Override(ctx, pcl.Items[0].ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, pc.Result)
|
||||||
|
assert.Equal(t, PolicyOverridden, pc.Status)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the policy passed", func(t *testing.T) {
|
||||||
|
pTest, pTestCleanup := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
defer pTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest})
|
||||||
|
rTest, _ := createPlannedRun(t, client, wTest)
|
||||||
|
|
||||||
|
pcl, err := client.PolicyChecks.List(ctx, rTest.ID, PolicyCheckListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(pcl.Items))
|
||||||
|
require.Equal(t, PolicyPasses, pcl.Items[0].Status)
|
||||||
|
|
||||||
|
_, err = client.PolicyChecks.Override(ctx, pcl.Items[0].ID)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid policy check ID", func(t *testing.T) {
|
||||||
|
p, err := client.PolicyChecks.Override(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy check ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyChecksLogs(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest, _ := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
createPolicySet(t, client, orgTest, []*Policy{pTest}, []*Workspace{wTest})
|
||||||
|
|
||||||
|
rTest, _ := createPlannedRun(t, client, wTest)
|
||||||
|
require.Equal(t, 1, len(rTest.PolicyChecks))
|
||||||
|
|
||||||
|
t.Run("when the log exists", func(t *testing.T) {
|
||||||
|
pc, err := client.PolicyChecks.Read(ctx, rTest.PolicyChecks[0].ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logReader, err := client.PolicyChecks.Logs(ctx, pc.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadAll(logReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, string(logs), "1 policies evaluated")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the log does not exist", func(t *testing.T) {
|
||||||
|
logs, err := client.PolicyChecks.Logs(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, logs)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,454 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPolicySetsList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
psTest1, _ := createPolicySet(t, client, orgTest, nil, nil)
|
||||||
|
psTest2, _ := createPolicySet(t, client, orgTest, nil, nil)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
psl, err := client.PolicySets.List(ctx, orgTest.Name, PolicySetListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, psl.Items, psTest1)
|
||||||
|
assert.Contains(t, psl.Items, psTest2)
|
||||||
|
assert.Equal(t, 1, psl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, psl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with pagination", func(t *testing.T) {
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
psl, err := client.PolicySets.List(ctx, orgTest.Name, PolicySetListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, psl.Items)
|
||||||
|
assert.Equal(t, 999, psl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, psl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with search", func(t *testing.T) {
|
||||||
|
// Search by one of the policy set's names; we should get only that policy
|
||||||
|
// set and pagination data should reflect the search as well
|
||||||
|
psl, err := client.PolicySets.List(ctx, orgTest.Name, PolicySetListOptions{
|
||||||
|
Search: String(psTest1.Name),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, psl.Items, psTest1)
|
||||||
|
assert.NotContains(t, psl.Items, psTest2)
|
||||||
|
assert.Equal(t, 1, psl.CurrentPage)
|
||||||
|
assert.Equal(t, 1, psl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.List(ctx, badIdentifier, PolicySetListOptions{})
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid attributes", func(t *testing.T) {
|
||||||
|
options := PolicySetCreateOptions{
|
||||||
|
Name: String("policy-set"),
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Create(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, ps.Name, *options.Name)
|
||||||
|
assert.Equal(t, ps.Description, "")
|
||||||
|
assert.False(t, ps.Global)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with all attributes provided", func(t *testing.T) {
|
||||||
|
options := PolicySetCreateOptions{
|
||||||
|
Name: String("global"),
|
||||||
|
Description: String("Policies in this set will be checked in ALL workspaces!"),
|
||||||
|
Global: Bool(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Create(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, ps.Name, *options.Name)
|
||||||
|
assert.Equal(t, ps.Description, *options.Description)
|
||||||
|
assert.True(t, ps.Global)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with policies and workspaces provided", func(t *testing.T) {
|
||||||
|
pTest, _ := createPolicy(t, client, orgTest)
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
options := PolicySetCreateOptions{
|
||||||
|
Name: String("populated-policy-set"),
|
||||||
|
Policies: []*Policy{pTest},
|
||||||
|
Workspaces: []*Workspace{wTest},
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Create(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, ps.Name, *options.Name)
|
||||||
|
assert.Equal(t, ps.PolicyCount, 1)
|
||||||
|
assert.Equal(t, ps.Policies[0].ID, pTest.ID)
|
||||||
|
assert.Equal(t, ps.WorkspaceCount, 1)
|
||||||
|
assert.Equal(t, ps.Workspaces[0].ID, wTest.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a name provided", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.Create(ctx, orgTest.Name, PolicySetCreateOptions{})
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with an invalid name provided", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.Create(ctx, orgTest.Name, PolicySetCreateOptions{
|
||||||
|
Name: String("nope!"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "invalid value for name")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.Create(ctx, badIdentifier, PolicySetCreateOptions{
|
||||||
|
Name: String("policy-set"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
psTest, _ := createPolicySet(t, client, orgTest, nil, nil)
|
||||||
|
|
||||||
|
t.Run("with a valid ID", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.Read(ctx, psTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, ps.ID, psTest.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid ID", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy set ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
psTest, _ := createPolicySet(t, client, orgTest, nil, nil)
|
||||||
|
|
||||||
|
t.Run("with valid attributes", func(t *testing.T) {
|
||||||
|
options := PolicySetUpdateOptions{
|
||||||
|
Name: String("global"),
|
||||||
|
Description: String("Policies in this set will be checked in ALL workspaces!"),
|
||||||
|
Global: Bool(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Update(ctx, psTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, ps.Name, *options.Name)
|
||||||
|
assert.Equal(t, ps.Description, *options.Description)
|
||||||
|
assert.True(t, ps.Global)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid attributes", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.Update(ctx, psTest.ID, PolicySetUpdateOptions{
|
||||||
|
Name: String("nope!"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "invalid value for name")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid ID", func(t *testing.T) {
|
||||||
|
ps, err := client.PolicySets.Update(ctx, badIdentifier, PolicySetUpdateOptions{
|
||||||
|
Name: String("policy-set"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy set ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsAddPolicies(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest1, _ := createPolicy(t, client, orgTest)
|
||||||
|
pTest2, _ := createPolicy(t, client, orgTest)
|
||||||
|
psTest, _ := createPolicySet(t, client, orgTest, nil, nil)
|
||||||
|
|
||||||
|
t.Run("with policies provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddPolicies(ctx, psTest.ID, PolicySetAddPoliciesOptions{
|
||||||
|
Policies: []*Policy{pTest1, pTest2},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Read(ctx, psTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ps.PolicyCount, 2)
|
||||||
|
|
||||||
|
ids := []string{}
|
||||||
|
for _, policy := range ps.Policies {
|
||||||
|
ids = append(ids, policy.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, ids, pTest1.ID)
|
||||||
|
assert.Contains(t, ids, pTest2.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without policies provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddPolicies(ctx, psTest.ID, PolicySetAddPoliciesOptions{})
|
||||||
|
assert.EqualError(t, err, "policies is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with empty policies slice", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddPolicies(ctx, psTest.ID, PolicySetAddPoliciesOptions{
|
||||||
|
Policies: []*Policy{},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "must provide at least one policy")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid ID", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddPolicies(ctx, badIdentifier, PolicySetAddPoliciesOptions{
|
||||||
|
Policies: []*Policy{pTest1, pTest2},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "invalid value for policy set ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsRemovePolicies(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest1, _ := createPolicy(t, client, orgTest)
|
||||||
|
pTest2, _ := createPolicy(t, client, orgTest)
|
||||||
|
psTest, _ := createPolicySet(t, client, orgTest, []*Policy{pTest1, pTest2}, nil)
|
||||||
|
|
||||||
|
t.Run("with policies provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemovePolicies(ctx, psTest.ID, PolicySetRemovePoliciesOptions{
|
||||||
|
Policies: []*Policy{pTest1, pTest2},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Read(ctx, psTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, ps.PolicyCount)
|
||||||
|
assert.Empty(t, ps.Policies)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without policies provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemovePolicies(ctx, psTest.ID, PolicySetRemovePoliciesOptions{})
|
||||||
|
assert.EqualError(t, err, "policies is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with empty policies slice", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemovePolicies(ctx, psTest.ID, PolicySetRemovePoliciesOptions{
|
||||||
|
Policies: []*Policy{},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "must provide at least one policy")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid ID", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemovePolicies(ctx, badIdentifier, PolicySetRemovePoliciesOptions{
|
||||||
|
Policies: []*Policy{pTest1, pTest2},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "invalid value for policy set ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsAddWorkspaces(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest1, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest2, _ := createWorkspace(t, client, orgTest)
|
||||||
|
psTest, _ := createPolicySet(t, client, orgTest, nil, nil)
|
||||||
|
|
||||||
|
t.Run("with workspaces provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddWorkspaces(
|
||||||
|
ctx,
|
||||||
|
psTest.ID,
|
||||||
|
PolicySetAddWorkspacesOptions{
|
||||||
|
Workspaces: []*Workspace{wTest1, wTest2},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Read(ctx, psTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, ps.WorkspaceCount)
|
||||||
|
|
||||||
|
ids := []string{}
|
||||||
|
for _, ws := range ps.Workspaces {
|
||||||
|
ids = append(ids, ws.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, ids, wTest1.ID)
|
||||||
|
assert.Contains(t, ids, wTest2.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without workspaces provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddWorkspaces(
|
||||||
|
ctx,
|
||||||
|
psTest.ID,
|
||||||
|
PolicySetAddWorkspacesOptions{},
|
||||||
|
)
|
||||||
|
assert.EqualError(t, err, "workspaces is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with empty workspaces slice", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddWorkspaces(
|
||||||
|
ctx,
|
||||||
|
psTest.ID,
|
||||||
|
PolicySetAddWorkspacesOptions{Workspaces: []*Workspace{}},
|
||||||
|
)
|
||||||
|
assert.EqualError(t, err, "must provide at least one workspace")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid ID", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.AddWorkspaces(
|
||||||
|
ctx,
|
||||||
|
badIdentifier,
|
||||||
|
PolicySetAddWorkspacesOptions{
|
||||||
|
Workspaces: []*Workspace{wTest1, wTest2},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy set ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsRemoveWorkspaces(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest1, _ := createWorkspace(t, client, orgTest)
|
||||||
|
wTest2, _ := createWorkspace(t, client, orgTest)
|
||||||
|
psTest, _ := createPolicySet(t, client, orgTest, nil, []*Workspace{wTest1, wTest2})
|
||||||
|
|
||||||
|
t.Run("with workspaces provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemoveWorkspaces(
|
||||||
|
ctx,
|
||||||
|
psTest.ID,
|
||||||
|
PolicySetRemoveWorkspacesOptions{
|
||||||
|
Workspaces: []*Workspace{wTest1, wTest2},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ps, err := client.PolicySets.Read(ctx, psTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, ps.WorkspaceCount)
|
||||||
|
assert.Empty(t, ps.Workspaces)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without workspaces provided", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemoveWorkspaces(
|
||||||
|
ctx,
|
||||||
|
psTest.ID,
|
||||||
|
PolicySetRemoveWorkspacesOptions{},
|
||||||
|
)
|
||||||
|
assert.EqualError(t, err, "workspaces is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with empty workspaces slice", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemoveWorkspaces(
|
||||||
|
ctx,
|
||||||
|
psTest.ID,
|
||||||
|
PolicySetRemoveWorkspacesOptions{Workspaces: []*Workspace{}},
|
||||||
|
)
|
||||||
|
assert.EqualError(t, err, "must provide at least one workspace")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid ID", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.RemoveWorkspaces(
|
||||||
|
ctx,
|
||||||
|
badIdentifier,
|
||||||
|
PolicySetRemoveWorkspacesOptions{
|
||||||
|
Workspaces: []*Workspace{wTest1, wTest2},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy set ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicySetsDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
psTest, _ := createPolicySet(t, client, orgTest, nil, nil)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.Delete(ctx, psTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the policy - it should fail.
|
||||||
|
_, err = client.PolicySets.Read(ctx, psTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the policy does not exist", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.Delete(ctx, psTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the policy ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.PolicySets.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy set ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,393 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPoliciesList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest1, _ := createPolicy(t, client, orgTest)
|
||||||
|
pTest2, _ := createPolicy(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
pl, err := client.Policies.List(ctx, orgTest.Name, PolicyListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, pl.Items, pTest1)
|
||||||
|
assert.Contains(t, pl.Items, pTest2)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, pl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, pl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with pagination", func(t *testing.T) {
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
pl, err := client.Policies.List(ctx, orgTest.Name, PolicyListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, pl.Items)
|
||||||
|
assert.Equal(t, 999, pl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, pl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with search", func(t *testing.T) {
|
||||||
|
// Search by one of the policy's names; we should get only that policy
|
||||||
|
// and pagination data should reflect the search as well
|
||||||
|
pl, err := client.Policies.List(ctx, orgTest.Name, PolicyListOptions{
|
||||||
|
Search: &pTest1.Name,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, pl.Items, pTest1)
|
||||||
|
assert.NotContains(t, pl.Items, pTest2)
|
||||||
|
assert.Equal(t, 1, pl.CurrentPage)
|
||||||
|
assert.Equal(t, 1, pl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
ps, err := client.Policies.List(ctx, badIdentifier, PolicyListOptions{})
|
||||||
|
assert.Nil(t, ps)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoliciesCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
name := randomString(t)
|
||||||
|
options := PolicyCreateOptions{
|
||||||
|
Name: String(name),
|
||||||
|
Description: String("A sample policy"),
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Path: String(name + ".sentinel"),
|
||||||
|
Mode: EnforcementMode(EnforcementSoft),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := client.Policies.Create(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view from the API.
|
||||||
|
refreshed, err := client.Policies.Read(ctx, p.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*Policy{
|
||||||
|
p,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, *options.Name, item.Name)
|
||||||
|
assert.Equal(t, *options.Description, item.Description)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid name", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{
|
||||||
|
Name: String(badIdentifier),
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Path: String(badIdentifier + ".sentinel"),
|
||||||
|
Mode: EnforcementMode(EnforcementSoft),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "invalid value for name")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing name", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Create(ctx, orgTest.Name, PolicyCreateOptions{
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Path: String(randomString(t) + ".sentinel"),
|
||||||
|
Mode: EnforcementMode(EnforcementSoft),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing an enforcement", func(t *testing.T) {
|
||||||
|
options := PolicyCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := client.Policies.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "enforce is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing enforcement path", func(t *testing.T) {
|
||||||
|
options := PolicyCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Mode: EnforcementMode(EnforcementSoft),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := client.Policies.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "enforcement path is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing enforcement path", func(t *testing.T) {
|
||||||
|
name := randomString(t)
|
||||||
|
options := PolicyCreateOptions{
|
||||||
|
Name: String(name),
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Path: String(name + ".sentinel"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := client.Policies.Create(ctx, orgTest.Name, options)
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "enforcement mode is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid organization", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Create(ctx, badIdentifier, PolicyCreateOptions{
|
||||||
|
Name: String("foo"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoliciesRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest, pTestCleanup := createPolicy(t, client, orgTest)
|
||||||
|
defer pTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the policy exists without content", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Read(ctx, pTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, pTest.ID, p.ID)
|
||||||
|
assert.Equal(t, pTest.Name, p.Name)
|
||||||
|
assert.Equal(t, pTest.PolicySetCount, p.PolicySetCount)
|
||||||
|
assert.Empty(t, p.Enforce)
|
||||||
|
assert.Equal(t, pTest.Organization.Name, p.Organization.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
err := client.Policies.Upload(ctx, pTest.ID, []byte(`main = rule { true }`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("when the policy exists with content", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Read(ctx, pTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, pTest.ID, p.ID)
|
||||||
|
assert.Equal(t, pTest.Name, p.Name)
|
||||||
|
assert.Equal(t, pTest.Description, p.Description)
|
||||||
|
assert.Equal(t, pTest.PolicySetCount, p.PolicySetCount)
|
||||||
|
assert.NotEmpty(t, p.Enforce)
|
||||||
|
assert.Equal(t, pTest.Organization.Name, p.Organization.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the policy does not exist", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid policy ID", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoliciesUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when updating with an existing path", func(t *testing.T) {
|
||||||
|
pBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
defer pBeforeCleanup()
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(pBefore.Enforce))
|
||||||
|
|
||||||
|
pAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Path: String(pBefore.Enforce[0].Path),
|
||||||
|
Mode: EnforcementMode(EnforcementAdvisory),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(pAfter.Enforce))
|
||||||
|
|
||||||
|
assert.Equal(t, pBefore.ID, pAfter.ID)
|
||||||
|
assert.Equal(t, pBefore.Name, pAfter.Name)
|
||||||
|
assert.Equal(t, pBefore.Description, pAfter.Description)
|
||||||
|
assert.Equal(t, pBefore.Enforce[0].Path, pAfter.Enforce[0].Path)
|
||||||
|
assert.Equal(t, EnforcementAdvisory, pAfter.Enforce[0].Mode)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when updating with a nonexisting path", func(t *testing.T) {
|
||||||
|
pBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
defer pBeforeCleanup()
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(pBefore.Enforce))
|
||||||
|
|
||||||
|
pAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{
|
||||||
|
Enforce: []*EnforcementOptions{
|
||||||
|
{
|
||||||
|
Path: String("nonexisting"),
|
||||||
|
Mode: EnforcementMode(EnforcementAdvisory),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Weirdly enough this is not equal as updating a nonexisting path
|
||||||
|
// causes the enforce mode to reset to the default hard-mandatory
|
||||||
|
t.Skip("see comment...")
|
||||||
|
assert.Equal(t, pBefore, pAfter)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with a new description", func(t *testing.T) {
|
||||||
|
pBefore, pBeforeCleanup := createUploadedPolicy(t, client, true, orgTest)
|
||||||
|
defer pBeforeCleanup()
|
||||||
|
|
||||||
|
pAfter, err := client.Policies.Update(ctx, pBefore.ID, PolicyUpdateOptions{
|
||||||
|
Description: String("A brand new description"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, pBefore.Name, pAfter.Name)
|
||||||
|
assert.Equal(t, pBefore.Enforce, pAfter.Enforce)
|
||||||
|
assert.NotEqual(t, pBefore.Description, pAfter.Description)
|
||||||
|
assert.Equal(t, "A brand new description", pAfter.Description)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid policy ID", func(t *testing.T) {
|
||||||
|
p, err := client.Policies.Update(ctx, badIdentifier, PolicyUpdateOptions{})
|
||||||
|
assert.Nil(t, p)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoliciesDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
pTest, _ := createPolicy(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.Policies.Delete(ctx, pTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the policy - it should fail.
|
||||||
|
_, err = client.Policies.Read(ctx, pTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the policy does not exist", func(t *testing.T) {
|
||||||
|
err := client.Policies.Delete(ctx, pTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the policy ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.Policies.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoliciesUpload(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
pTest, pTestCleanup := createPolicy(t, client, nil)
|
||||||
|
defer pTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.Policies.Upload(ctx, pTest.ID, []byte(`main = rule { true }`))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with empty content", func(t *testing.T) {
|
||||||
|
err := client.Policies.Upload(ctx, pTest.ID, []byte{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without any content", func(t *testing.T) {
|
||||||
|
err := client.Policies.Upload(ctx, pTest.ID, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid policy ID", func(t *testing.T) {
|
||||||
|
err := client.Policies.Upload(ctx, badIdentifier, []byte(`main = rule { true }`))
|
||||||
|
assert.EqualError(t, err, "invalid value for policy ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoliciesDownload(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
pTest, pTestCleanup := createPolicy(t, client, nil)
|
||||||
|
defer pTestCleanup()
|
||||||
|
|
||||||
|
testContent := []byte(`main = rule { true }`)
|
||||||
|
|
||||||
|
t.Run("without existing content", func(t *testing.T) {
|
||||||
|
content, err := client.Policies.Download(ctx, pTest.ID)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
assert.Nil(t, content)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.Policies.Upload(ctx, pTest.ID, testContent)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
content, err := client.Policies.Download(ctx, pTest.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, testContent, content)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid policy ID", func(t *testing.T) {
|
||||||
|
content, err := client.Policies.Download(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for policy ID")
|
||||||
|
assert.Nil(t, content)
|
||||||
|
})
|
||||||
|
}
|
|
@ -49,12 +49,16 @@ type RunStatus string
|
||||||
//List all available run statuses.
|
//List all available run statuses.
|
||||||
const (
|
const (
|
||||||
RunApplied RunStatus = "applied"
|
RunApplied RunStatus = "applied"
|
||||||
|
RunApplyQueued RunStatus = "apply_queued"
|
||||||
RunApplying RunStatus = "applying"
|
RunApplying RunStatus = "applying"
|
||||||
RunCanceled RunStatus = "canceled"
|
RunCanceled RunStatus = "canceled"
|
||||||
RunConfirmed RunStatus = "confirmed"
|
RunConfirmed RunStatus = "confirmed"
|
||||||
|
RunCostEstimated RunStatus = "cost_estimated"
|
||||||
|
RunCostEstimating RunStatus = "cost_estimating"
|
||||||
RunDiscarded RunStatus = "discarded"
|
RunDiscarded RunStatus = "discarded"
|
||||||
RunErrored RunStatus = "errored"
|
RunErrored RunStatus = "errored"
|
||||||
RunPending RunStatus = "pending"
|
RunPending RunStatus = "pending"
|
||||||
|
RunPlanQueued RunStatus = "plan_queued"
|
||||||
RunPlanned RunStatus = "planned"
|
RunPlanned RunStatus = "planned"
|
||||||
RunPlannedAndFinished RunStatus = "planned_and_finished"
|
RunPlannedAndFinished RunStatus = "planned_and_finished"
|
||||||
RunPlanning RunStatus = "planning"
|
RunPlanning RunStatus = "planning"
|
||||||
|
@ -98,6 +102,7 @@ type Run struct {
|
||||||
// Relations
|
// Relations
|
||||||
Apply *Apply `jsonapi:"relation,apply"`
|
Apply *Apply `jsonapi:"relation,apply"`
|
||||||
ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"`
|
ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"`
|
||||||
|
CostEstimation *CostEstimation `jsonapi:"relation,cost-estimation"`
|
||||||
Plan *Plan `jsonapi:"relation,plan"`
|
Plan *Plan `jsonapi:"relation,plan"`
|
||||||
PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"`
|
PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"`
|
||||||
Workspace *Workspace `jsonapi:"relation,workspace"`
|
Workspace *Workspace `jsonapi:"relation,workspace"`
|
||||||
|
@ -274,7 +279,7 @@ func (s *runs) Cancel(ctx context.Context, runID string, options RunCancelOption
|
||||||
return s.client.do(ctx, req, nil)
|
return s.client.do(ctx, req, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunCancelOptions represents the options for force-canceling a run.
|
// RunForceCancelOptions represents the options for force-canceling a run.
|
||||||
type RunForceCancelOptions struct {
|
type RunForceCancelOptions struct {
|
||||||
// An optional comment explaining the reason for the force-cancel.
|
// An optional comment explaining the reason for the force-cancel.
|
||||||
Comment *string `json:"comment,omitempty"`
|
Comment *string `json:"comment,omitempty"`
|
||||||
|
|
|
@ -0,0 +1,277 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunsList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
rTest1, _ := createRun(t, client, wTest)
|
||||||
|
rTest2, _ := createRun(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
rl, err := client.Runs.List(ctx, wTest.ID, RunListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
found := []string{}
|
||||||
|
for _, r := range rl.Items {
|
||||||
|
found = append(found, r.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, found, rTest1.ID)
|
||||||
|
assert.Contains(t, found, rTest2.ID)
|
||||||
|
assert.Equal(t, 1, rl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, rl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
rl, err := client.Runs.List(ctx, wTest.ID, RunListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, rl.Items)
|
||||||
|
assert.Equal(t, 999, rl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, rl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace ID", func(t *testing.T) {
|
||||||
|
rl, err := client.Runs.List(ctx, badIdentifier, RunListOptions{})
|
||||||
|
assert.Nil(t, rl)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunsCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
cvTest, _ := createUploadedConfigurationVersion(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("without a configuration version", func(t *testing.T) {
|
||||||
|
options := RunCreateOptions{
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Runs.Create(ctx, options)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with a configuration version", func(t *testing.T) {
|
||||||
|
options := RunCreateOptions{
|
||||||
|
ConfigurationVersion: cvTest,
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := client.Runs.Create(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, cvTest.ID, r.ConfigurationVersion.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a workspace", func(t *testing.T) {
|
||||||
|
r, err := client.Runs.Create(ctx, RunCreateOptions{})
|
||||||
|
assert.Nil(t, r)
|
||||||
|
assert.EqualError(t, err, "workspace is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with additional attributes", func(t *testing.T) {
|
||||||
|
options := RunCreateOptions{
|
||||||
|
Message: String("yo"),
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := client.Runs.Create(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, *options.Message, r.Message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rTest, rTestCleanup := createPlannedRun(t, client, nil)
|
||||||
|
defer rTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the run exists", func(t *testing.T) {
|
||||||
|
r, err := client.Runs.Read(ctx, rTest.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rTest, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the run does not exist", func(t *testing.T) {
|
||||||
|
r, err := client.Runs.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, r)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid run ID", func(t *testing.T) {
|
||||||
|
r, err := client.Runs.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, r)
|
||||||
|
assert.EqualError(t, err, "invalid value for run ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunsApply(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rTest, rTestCleanup := createPlannedRun(t, client, nil)
|
||||||
|
defer rTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the run exists", func(t *testing.T) {
|
||||||
|
err := client.Runs.Apply(ctx, rTest.ID, RunApplyOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the run does not exist", func(t *testing.T) {
|
||||||
|
err := client.Runs.Apply(ctx, "nonexisting", RunApplyOptions{})
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid run ID", func(t *testing.T) {
|
||||||
|
err := client.Runs.Apply(ctx, badIdentifier, RunApplyOptions{})
|
||||||
|
assert.EqualError(t, err, "invalid value for run ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunsCancel(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
// We need to create 2 runs here. The first run will automatically
|
||||||
|
// be planned so that one cannot be cancelled. The second one will
|
||||||
|
// be pending until the first one is confirmed or discarded, so we
|
||||||
|
// can cancel that one.
|
||||||
|
_, _ = createRun(t, client, wTest)
|
||||||
|
rTest2, _ := createRun(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("when the run exists", func(t *testing.T) {
|
||||||
|
err := client.Runs.Cancel(ctx, rTest2.ID, RunCancelOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the run does not exist", func(t *testing.T) {
|
||||||
|
err := client.Runs.Cancel(ctx, "nonexisting", RunCancelOptions{})
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid run ID", func(t *testing.T) {
|
||||||
|
err := client.Runs.Cancel(ctx, badIdentifier, RunCancelOptions{})
|
||||||
|
assert.EqualError(t, err, "invalid value for run ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunsForceCancel(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
// We need to create 2 runs here. The first run will automatically
|
||||||
|
// be planned so that one cannot be cancelled. The second one will
|
||||||
|
// be pending until the first one is confirmed or discarded, so we
|
||||||
|
// can cancel that one.
|
||||||
|
_, _ = createRun(t, client, wTest)
|
||||||
|
rTest, _ := createRun(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("run is not force-cancelable", func(t *testing.T) {
|
||||||
|
assert.False(t, rTest.Actions.IsForceCancelable)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("user is allowed to force-cancel", func(t *testing.T) {
|
||||||
|
assert.True(t, rTest.Permissions.CanForceCancel)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("after a normal cancel", func(t *testing.T) {
|
||||||
|
// Request the normal cancel
|
||||||
|
err := client.Runs.Cancel(ctx, rTest.ID, RunCancelOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
// Refresh the view of the run
|
||||||
|
rTest, err = client.Runs.Read(ctx, rTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check if the timestamp is present.
|
||||||
|
if !rTest.ForceCancelAvailableAt.IsZero() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 30 {
|
||||||
|
t.Fatal("Timeout waiting for run to be canceled")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("force-cancel-available-at timestamp is present", func(t *testing.T) {
|
||||||
|
assert.True(t, rTest.ForceCancelAvailableAt.After(time.Now()))
|
||||||
|
})
|
||||||
|
|
||||||
|
// This test case is minimal because a force-cancel is not needed in
|
||||||
|
// any normal circumstance. Only if Terraform encounters unexpected
|
||||||
|
// errors or behaves abnormally should this functionality be required.
|
||||||
|
// Force-cancel only becomes available if a normal cancel is performed
|
||||||
|
// first, and the desired canceled state is not reached within a pre-
|
||||||
|
// determined amount of time (see
|
||||||
|
// https://www.terraform.io/docs/enterprise/api/run.html#forcefully-cancel-a-run).
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the run does not exist", func(t *testing.T) {
|
||||||
|
err := client.Runs.ForceCancel(ctx, "nonexisting", RunForceCancelOptions{})
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid run ID", func(t *testing.T) {
|
||||||
|
err := client.Runs.ForceCancel(ctx, badIdentifier, RunForceCancelOptions{})
|
||||||
|
assert.EqualError(t, err, "invalid value for run ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunsDiscard(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rTest, rTestCleanup := createPlannedRun(t, client, nil)
|
||||||
|
defer rTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the run exists", func(t *testing.T) {
|
||||||
|
err := client.Runs.Discard(ctx, rTest.ID, RunDiscardOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the run does not exist", func(t *testing.T) {
|
||||||
|
err := client.Runs.Discard(ctx, "nonexisting", RunDiscardOptions{})
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid run ID", func(t *testing.T) {
|
||||||
|
err := client.Runs.Discard(ctx, badIdentifier, RunDiscardOptions{})
|
||||||
|
assert.EqualError(t, err, "invalid value for run ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSSHKeysList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
kTest1, _ := createSSHKey(t, client, orgTest)
|
||||||
|
kTest2, _ := createSSHKey(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
kl, err := client.SSHKeys.List(ctx, orgTest.Name, SSHKeyListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, kl.Items, kTest1)
|
||||||
|
assert.Contains(t, kl.Items, kTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, kl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, kl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
kl, err := client.SSHKeys.List(ctx, orgTest.Name, SSHKeyListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, kl.Items)
|
||||||
|
assert.Equal(t, 999, kl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, kl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
kl, err := client.SSHKeys.List(ctx, badIdentifier, SSHKeyListOptions{})
|
||||||
|
assert.Nil(t, kl)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHKeysCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := SSHKeyCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := client.SSHKeys.Create(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view from the API.
|
||||||
|
refreshed, err := client.SSHKeys.Read(ctx, k.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*SSHKey{
|
||||||
|
k,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, *options.Name, item.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing name", func(t *testing.T) {
|
||||||
|
k, err := client.SSHKeys.Create(ctx, "foo", SSHKeyCreateOptions{
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
})
|
||||||
|
assert.Nil(t, k)
|
||||||
|
assert.EqualError(t, err, "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing value", func(t *testing.T) {
|
||||||
|
k, err := client.SSHKeys.Create(ctx, "foo", SSHKeyCreateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
})
|
||||||
|
assert.Nil(t, k)
|
||||||
|
assert.EqualError(t, err, "value is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid organization", func(t *testing.T) {
|
||||||
|
k, err := client.SSHKeys.Create(ctx, badIdentifier, SSHKeyCreateOptions{
|
||||||
|
Name: String("foo"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, k)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHKeysRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
kTest, _ := createSSHKey(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("when the SSH key exists", func(t *testing.T) {
|
||||||
|
k, err := client.SSHKeys.Read(ctx, kTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, kTest, k)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the SSH key does not exist", func(t *testing.T) {
|
||||||
|
k, err := client.SSHKeys.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, k)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid SSH key ID", func(t *testing.T) {
|
||||||
|
k, err := client.SSHKeys.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, k)
|
||||||
|
assert.EqualError(t, err, "invalid value for SSH key ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHKeysUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
kBefore, kTestCleanup := createSSHKey(t, client, orgTest)
|
||||||
|
defer kTestCleanup()
|
||||||
|
|
||||||
|
kAfter, err := client.SSHKeys.Update(ctx, kBefore.ID, SSHKeyUpdateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, kBefore.ID, kAfter.ID)
|
||||||
|
assert.NotEqual(t, kBefore.Name, kAfter.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when updating the name", func(t *testing.T) {
|
||||||
|
kBefore, kTestCleanup := createSSHKey(t, client, orgTest)
|
||||||
|
defer kTestCleanup()
|
||||||
|
|
||||||
|
kAfter, err := client.SSHKeys.Update(ctx, kBefore.ID, SSHKeyUpdateOptions{
|
||||||
|
Name: String("updated-key-name"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, kBefore.ID, kAfter.ID)
|
||||||
|
assert.Equal(t, "updated-key-name", kAfter.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when updating the value", func(t *testing.T) {
|
||||||
|
kBefore, kTestCleanup := createSSHKey(t, client, orgTest)
|
||||||
|
defer kTestCleanup()
|
||||||
|
|
||||||
|
kAfter, err := client.SSHKeys.Update(ctx, kBefore.ID, SSHKeyUpdateOptions{
|
||||||
|
Value: String("updated-key-value"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, kBefore.ID, kAfter.ID)
|
||||||
|
assert.Equal(t, kBefore.Name, kAfter.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid SSH key ID", func(t *testing.T) {
|
||||||
|
w, err := client.SSHKeys.Update(ctx, badIdentifier, SSHKeyUpdateOptions{})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for SSH key ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHKeysDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
kTest, _ := createSSHKey(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.SSHKeys.Delete(ctx, kTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the SSH key - it should fail.
|
||||||
|
_, err = client.SSHKeys.Read(ctx, kTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the SSH key does not exist", func(t *testing.T) {
|
||||||
|
err := client.SSHKeys.Delete(ctx, kTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the SSH key ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.SSHKeys.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for SSH key ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,350 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateVersionsList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, orgTest)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
svTest1, _ := createStateVersion(t, client, 0, wTest)
|
||||||
|
svTest2, _ := createStateVersion(t, client, 1, wTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
options := StateVersionListOptions{
|
||||||
|
Organization: String(orgTest.Name),
|
||||||
|
Workspace: String(wTest.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
svl, err := client.StateVersions.List(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// We need to strip the upload URL as that is a dynamic link.
|
||||||
|
svTest1.DownloadURL = ""
|
||||||
|
svTest2.DownloadURL = ""
|
||||||
|
|
||||||
|
// And for the retrieved configuration versions as well.
|
||||||
|
for _, sv := range svl.Items {
|
||||||
|
sv.DownloadURL = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, svl.Items, svTest1)
|
||||||
|
assert.Contains(t, svl.Items, svTest2)
|
||||||
|
assert.Equal(t, 1, svl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, svl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
options := StateVersionListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
Organization: String(orgTest.Name),
|
||||||
|
Workspace: String(wTest.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
svl, err := client.StateVersions.List(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, svl.Items)
|
||||||
|
assert.Equal(t, 999, svl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, svl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without an organization", func(t *testing.T) {
|
||||||
|
options := StateVersionListOptions{
|
||||||
|
Workspace: String(wTest.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
svl, err := client.StateVersions.List(ctx, options)
|
||||||
|
assert.Nil(t, svl)
|
||||||
|
assert.EqualError(t, err, "organization is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a workspace", func(t *testing.T) {
|
||||||
|
options := StateVersionListOptions{
|
||||||
|
Organization: String(orgTest.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
svl, err := client.StateVersions.List(ctx, options)
|
||||||
|
assert.Nil(t, svl)
|
||||||
|
assert.EqualError(t, err, "workspace is required")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateVersionsCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
state, err := ioutil.ReadFile("test-fixtures/state-version/terraform.tfstate")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{
|
||||||
|
Lineage: String("741c4949-60b9-5bb1-5bf8-b14f4bb14af3"),
|
||||||
|
MD5: String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||||
|
Serial: Int64(1),
|
||||||
|
State: String(base64.StdEncoding.EncodeToString(state)),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view of the configuration version.
|
||||||
|
refreshed, err := client.StateVersions.Read(ctx, sv.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = client.Workspaces.Unlock(ctx, wTest.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range []*StateVersion{
|
||||||
|
sv,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, int64(1), item.Serial)
|
||||||
|
assert.NotEmpty(t, item.CreatedAt)
|
||||||
|
assert.NotEmpty(t, item.DownloadURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with the force flag set", func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{
|
||||||
|
Lineage: String("741c4949-60b9-5bb1-5bf8-b14f4bb14af3"),
|
||||||
|
MD5: String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||||
|
Serial: Int64(1),
|
||||||
|
State: String(base64.StdEncoding.EncodeToString(state)),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sv, err = client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{
|
||||||
|
Lineage: String("821c4747-a0b9-3bd1-8bf3-c14f4bb14be7"),
|
||||||
|
MD5: String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||||
|
Serial: Int64(2),
|
||||||
|
State: String(base64.StdEncoding.EncodeToString(state)),
|
||||||
|
Force: Bool(true),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view of the configuration version.
|
||||||
|
refreshed, err := client.StateVersions.Read(ctx, sv.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = client.Workspaces.Unlock(ctx, wTest.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range []*StateVersion{
|
||||||
|
sv,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, int64(2), item.Serial)
|
||||||
|
assert.NotEmpty(t, item.CreatedAt)
|
||||||
|
assert.NotEmpty(t, item.DownloadURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with a run to associate with", func(t *testing.T) {
|
||||||
|
t.Skip("This can only be tested with the run specific token")
|
||||||
|
|
||||||
|
rTest, _ := createRun(t, client, wTest)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
sv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{
|
||||||
|
MD5: String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||||
|
Serial: Int64(0),
|
||||||
|
State: String(base64.StdEncoding.EncodeToString(state)),
|
||||||
|
Run: rTest,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, sv.Run)
|
||||||
|
|
||||||
|
// Get a refreshed view of the configuration version.
|
||||||
|
refreshed, err := client.StateVersions.Read(ctx, sv.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, refreshed.Run)
|
||||||
|
|
||||||
|
for _, item := range []*StateVersion{
|
||||||
|
sv,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, int64(0), item.Serial)
|
||||||
|
assert.NotEmpty(t, item.CreatedAt)
|
||||||
|
assert.NotEmpty(t, item.DownloadURL)
|
||||||
|
assert.Equal(t, rTest.ID, item.Run.ID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without md5 hash", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{
|
||||||
|
Serial: Int64(0),
|
||||||
|
State: String(base64.StdEncoding.EncodeToString(state)),
|
||||||
|
})
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.EqualError(t, err, "MD5 is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("withous serial", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{
|
||||||
|
MD5: String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||||
|
State: String(base64.StdEncoding.EncodeToString(state)),
|
||||||
|
})
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.EqualError(t, err, "serial is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without state", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{
|
||||||
|
MD5: String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||||
|
Serial: Int64(0),
|
||||||
|
})
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.EqualError(t, err, "state is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid workspace id", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Create(ctx, badIdentifier, StateVersionCreateOptions{})
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateVersionsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
svTest, svTestCleanup := createStateVersion(t, client, 0, nil)
|
||||||
|
defer svTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the state version exists", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Read(ctx, svTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Don't compare the DownloadURL because it will be generated twice
|
||||||
|
// in this test - once at creation of the configuration version, and
|
||||||
|
// again during the GET.
|
||||||
|
svTest.DownloadURL, sv.DownloadURL = "", ""
|
||||||
|
|
||||||
|
assert.Equal(t, svTest, sv)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the state version does not exist", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid state version id", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.EqualError(t, err, "invalid value for state version ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateVersionsCurrent(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest1, wTest1Cleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTest1Cleanup()
|
||||||
|
|
||||||
|
wTest2, wTest2Cleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTest2Cleanup()
|
||||||
|
|
||||||
|
svTest, svTestCleanup := createStateVersion(t, client, 0, wTest1)
|
||||||
|
defer svTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when a state version exists", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Current(ctx, wTest1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Don't compare the DownloadURL because it will be generated twice
|
||||||
|
// in this test - once at creation of the configuration version, and
|
||||||
|
// again during the GET.
|
||||||
|
svTest.DownloadURL, sv.DownloadURL = "", ""
|
||||||
|
|
||||||
|
assert.Equal(t, svTest, sv)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a state version does not exist", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Current(ctx, wTest2.ID)
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid workspace id", func(t *testing.T) {
|
||||||
|
sv, err := client.StateVersions.Current(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, sv)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateVersionsDownload(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
svTest, svTestCleanup := createStateVersion(t, client, 0, nil)
|
||||||
|
defer svTestCleanup()
|
||||||
|
|
||||||
|
stateTest, err := ioutil.ReadFile("test-fixtures/state-version/terraform.tfstate")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("when the state version exists", func(t *testing.T) {
|
||||||
|
state, err := client.StateVersions.Download(ctx, svTest.DownloadURL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, stateTest, state)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the state version does not exist", func(t *testing.T) {
|
||||||
|
state, err := client.StateVersions.Download(
|
||||||
|
ctx,
|
||||||
|
svTest.DownloadURL[:len(svTest.DownloadURL)-10]+"nonexisting",
|
||||||
|
)
|
||||||
|
assert.Nil(t, state)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with an invalid url", func(t *testing.T) {
|
||||||
|
state, err := client.StateVersions.Download(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, state)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTeamAccessesList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
tmTest1, tmTest1Cleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTest1Cleanup()
|
||||||
|
tmTest2, tmTest2Cleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTest2Cleanup()
|
||||||
|
|
||||||
|
taTest1, taTest1Cleanup := createTeamAccess(t, client, tmTest1, wTest, orgTest)
|
||||||
|
defer taTest1Cleanup()
|
||||||
|
taTest2, taTest2Cleanup := createTeamAccess(t, client, tmTest2, wTest, orgTest)
|
||||||
|
defer taTest2Cleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
tal, err := client.TeamAccess.List(ctx, TeamAccessListOptions{
|
||||||
|
WorkspaceID: String(wTest.ID),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, tal.Items, taTest1)
|
||||||
|
assert.Contains(t, tal.Items, taTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, tal.CurrentPage)
|
||||||
|
assert.Equal(t, 2, tal.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
tal, err := client.TeamAccess.List(ctx, TeamAccessListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, tal.Items)
|
||||||
|
assert.Equal(t, 999, tal.CurrentPage)
|
||||||
|
assert.Equal(t, 2, tal.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
tal, err := client.TeamAccess.List(ctx, TeamAccessListOptions{})
|
||||||
|
assert.Nil(t, tal)
|
||||||
|
assert.EqualError(t, err, "workspace ID is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace ID", func(t *testing.T) {
|
||||||
|
tal, err := client.TeamAccess.List(ctx, TeamAccessListOptions{
|
||||||
|
WorkspaceID: String(badIdentifier),
|
||||||
|
})
|
||||||
|
assert.Nil(t, tal)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamAccessesAdd(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := TeamAccessAddOptions{
|
||||||
|
Access: Access(AccessAdmin),
|
||||||
|
Team: tmTest,
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
ta, err := client.TeamAccess.Add(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view from the API.
|
||||||
|
refreshed, err := client.TeamAccess.Read(ctx, ta.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*TeamAccess{
|
||||||
|
ta,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, *options.Access, item.Access)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team already has access", func(t *testing.T) {
|
||||||
|
options := TeamAccessAddOptions{
|
||||||
|
Access: Access(AccessAdmin),
|
||||||
|
Team: tmTest,
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.TeamAccess.Add(ctx, options)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing access", func(t *testing.T) {
|
||||||
|
ta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{
|
||||||
|
Team: tmTest,
|
||||||
|
Workspace: wTest,
|
||||||
|
})
|
||||||
|
assert.Nil(t, ta)
|
||||||
|
assert.EqualError(t, err, "access is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing team", func(t *testing.T) {
|
||||||
|
ta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{
|
||||||
|
Access: Access(AccessAdmin),
|
||||||
|
Workspace: wTest,
|
||||||
|
})
|
||||||
|
assert.Nil(t, ta)
|
||||||
|
assert.EqualError(t, err, "team is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing workspace", func(t *testing.T) {
|
||||||
|
ta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{
|
||||||
|
Access: Access(AccessAdmin),
|
||||||
|
Team: tmTest,
|
||||||
|
})
|
||||||
|
assert.Nil(t, ta)
|
||||||
|
assert.EqualError(t, err, "workspace is required")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamAccessesRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
taTest, taTestCleanup := createTeamAccess(t, client, nil, nil, nil)
|
||||||
|
defer taTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the team access exists", func(t *testing.T) {
|
||||||
|
ta, err := client.TeamAccess.Read(ctx, taTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, AccessAdmin, ta.Access)
|
||||||
|
|
||||||
|
t.Run("team relationship is decoded", func(t *testing.T) {
|
||||||
|
assert.NotEmpty(t, ta.Team)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("workspace relationship is decoded", func(t *testing.T) {
|
||||||
|
assert.NotEmpty(t, ta.Workspace)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team access does not exist", func(t *testing.T) {
|
||||||
|
ta, err := client.TeamAccess.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, ta)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid team access ID", func(t *testing.T) {
|
||||||
|
ta, err := client.TeamAccess.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, ta)
|
||||||
|
assert.EqualError(t, err, "invalid value for team access ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamAccessesRemove(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
taTest, _ := createTeamAccess(t, client, tmTest, nil, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.TeamAccess.Remove(ctx, taTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the workspace - it should fail.
|
||||||
|
_, err = client.TeamAccess.Read(ctx, taTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team access does not exist", func(t *testing.T) {
|
||||||
|
err := client.TeamAccess.Remove(ctx, taTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team access ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.TeamAccess.Remove(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for team access ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTeamMembersList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, nil)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
options := TeamMemberAddOptions{
|
||||||
|
Usernames: []string{"admin"},
|
||||||
|
}
|
||||||
|
err := client.TeamMembers.Add(ctx, tmTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
users, err := client.TeamMembers.List(ctx, tmTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(users))
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Username == "admin" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, found)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team ID is invalid", func(t *testing.T) {
|
||||||
|
users, err := client.TeamMembers.List(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
assert.Nil(t, users)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamMembersAdd(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, nil)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := TeamMemberAddOptions{
|
||||||
|
Usernames: []string{"admin"},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := client.TeamMembers.Add(ctx, tmTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
users, err := client.TeamMembers.List(ctx, tmTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Username == "admin" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, found)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing usernames", func(t *testing.T) {
|
||||||
|
err := client.TeamMembers.Add(ctx, tmTest.ID, TeamMemberAddOptions{})
|
||||||
|
assert.EqualError(t, err, "usernames is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when usernames is empty", func(t *testing.T) {
|
||||||
|
err := client.TeamMembers.Add(ctx, tmTest.ID, TeamMemberAddOptions{
|
||||||
|
Usernames: []string{},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "invalid value for usernames")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.TeamMembers.Add(ctx, badIdentifier, TeamMemberAddOptions{
|
||||||
|
Usernames: []string{"user1"},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamMembersRemove(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, nil)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
options := TeamMemberAddOptions{
|
||||||
|
Usernames: []string{"admin"},
|
||||||
|
}
|
||||||
|
err := client.TeamMembers.Add(ctx, tmTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := TeamMemberRemoveOptions{
|
||||||
|
Usernames: []string{"admin"},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := client.TeamMembers.Remove(ctx, tmTest.ID, options)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing usernames", func(t *testing.T) {
|
||||||
|
err := client.TeamMembers.Remove(ctx, tmTest.ID, TeamMemberRemoveOptions{})
|
||||||
|
assert.EqualError(t, err, "usernames is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when usernames is empty", func(t *testing.T) {
|
||||||
|
err := client.TeamMembers.Remove(ctx, tmTest.ID, TeamMemberRemoveOptions{
|
||||||
|
Usernames: []string{},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "invalid value for usernames")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team ID is invalid", func(t *testing.T) {
|
||||||
|
err := client.TeamMembers.Remove(ctx, badIdentifier, TeamMemberRemoveOptions{
|
||||||
|
Usernames: []string{"user1"},
|
||||||
|
})
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTeamsList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
tmTest1, tmTest1Cleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTest1Cleanup()
|
||||||
|
tmTest2, tmTest2Cleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTest2Cleanup()
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
tl, err := client.Teams.List(ctx, orgTest.Name, TeamListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, tl.Items, tmTest1)
|
||||||
|
assert.Contains(t, tl.Items, tmTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, tl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, tl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
tl, err := client.Teams.List(ctx, orgTest.Name, TeamListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, tl.Items)
|
||||||
|
assert.Equal(t, 999, tl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, tl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
tl, err := client.Teams.List(ctx, badIdentifier, TeamListOptions{})
|
||||||
|
assert.Nil(t, tl)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamsCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := TeamCreateOptions{
|
||||||
|
Name: String("foo"),
|
||||||
|
}
|
||||||
|
|
||||||
|
tm, err := client.Teams.Create(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view from the API.
|
||||||
|
refreshed, err := client.Teams.Read(ctx, tm.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*Team{
|
||||||
|
tm,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, *options.Name, item.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing name", func(t *testing.T) {
|
||||||
|
tm, err := client.Teams.Create(ctx, "foo", TeamCreateOptions{})
|
||||||
|
assert.Nil(t, tm)
|
||||||
|
assert.EqualError(t, err, "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid organization", func(t *testing.T) {
|
||||||
|
tm, err := client.Teams.Create(ctx, badIdentifier, TeamCreateOptions{
|
||||||
|
Name: String("foo"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, tm)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamsRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the team exists", func(t *testing.T) {
|
||||||
|
tm, err := client.Teams.Read(ctx, tmTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tmTest, tm)
|
||||||
|
|
||||||
|
t.Run("permissions are properly decoded", func(t *testing.T) {
|
||||||
|
assert.True(t, tm.Permissions.CanDestroy)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("organization access is properly decoded", func(t *testing.T) {
|
||||||
|
assert.True(t, tm.OrganizationAccess.ManagePolicies)
|
||||||
|
assert.False(t, tm.OrganizationAccess.ManageWorkspaces)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team does not exist", func(t *testing.T) {
|
||||||
|
tm, err := client.Teams.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, tm)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid team ID", func(t *testing.T) {
|
||||||
|
tm, err := client.Teams.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, tm)
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamsUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, orgTest)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := TeamUpdateOptions{
|
||||||
|
Name: String("foo bar"),
|
||||||
|
OrganizationAccess: &OrganizationAccessOptions{
|
||||||
|
ManagePolicies: Bool(false),
|
||||||
|
ManageVCSSettings: Bool(true)},
|
||||||
|
}
|
||||||
|
|
||||||
|
tm, err := client.Teams.Update(ctx, tmTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
refreshed, err := client.Teams.Read(ctx, tmTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*Team{
|
||||||
|
tm,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.Equal(t, *options.Name, item.Name)
|
||||||
|
assert.Equal(t,
|
||||||
|
*options.OrganizationAccess.ManagePolicies,
|
||||||
|
item.OrganizationAccess.ManagePolicies,
|
||||||
|
)
|
||||||
|
assert.Equal(t,
|
||||||
|
*options.OrganizationAccess.ManageVCSSettings,
|
||||||
|
item.OrganizationAccess.ManageVCSSettings,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the team does not exist", func(t *testing.T) {
|
||||||
|
tm, err := client.Teams.Update(ctx, "nonexisting", TeamUpdateOptions{
|
||||||
|
Name: String("foo bar"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, tm)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid team ID", func(t *testing.T) {
|
||||||
|
tm, err := client.Teams.Update(ctx, badIdentifier, TeamUpdateOptions{})
|
||||||
|
assert.Nil(t, tm)
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamsDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
tmTest, _ := createTeam(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.Teams.Delete(ctx, tmTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the workspace - it should fail.
|
||||||
|
_, err = client.Teams.Read(ctx, tmTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without valid team ID", func(t *testing.T) {
|
||||||
|
err := client.Teams.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTeamTokensGenerate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, nil)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
var tmToken string
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
tt, err := client.TeamTokens.Generate(ctx, tmTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, tt.Token)
|
||||||
|
tmToken = tt.Token
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a token already exists", func(t *testing.T) {
|
||||||
|
tt, err := client.TeamTokens.Generate(ctx, tmTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, tt.Token)
|
||||||
|
assert.NotEqual(t, tmToken, tt.Token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without valid team ID", func(t *testing.T) {
|
||||||
|
tt, err := client.TeamTokens.Generate(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, tt)
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func TestTeamTokensRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, nil)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
_, ttTestCleanup := createTeamToken(t, client, tmTest)
|
||||||
|
|
||||||
|
tt, err := client.TeamTokens.Read(ctx, tmTest.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, tt)
|
||||||
|
|
||||||
|
ttTestCleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a token doesn't exists", func(t *testing.T) {
|
||||||
|
tt, err := client.TeamTokens.Read(ctx, tmTest.ID)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
assert.Nil(t, tt)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without valid organization", func(t *testing.T) {
|
||||||
|
tt, err := client.OrganizationTokens.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, tt)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamTokensDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tmTest, tmTestCleanup := createTeam(t, client, nil)
|
||||||
|
defer tmTestCleanup()
|
||||||
|
|
||||||
|
createTeamToken(t, client, tmTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.TeamTokens.Delete(ctx, tmTest.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when a token does not exist", func(t *testing.T) {
|
||||||
|
err := client.TeamTokens.Delete(ctx, tmTest.ID)
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without valid team ID", func(t *testing.T) {
|
||||||
|
err := client.TeamTokens.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for team ID")
|
||||||
|
})
|
||||||
|
}
|
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/bar.txt
generated
vendored
Normal file
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/bar.txt
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
bar
|
0
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/exe
generated
vendored
Executable file
0
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/exe
generated
vendored
Executable file
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/foo.txt
generated
vendored
Normal file
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/foo.txt
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
foo
|
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/sub/foo.txt
generated
vendored
Symbolic link
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/sub/foo.txt
generated
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../foo.txt
|
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/sub/zip.txt
generated
vendored
Normal file
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/archive-dir/sub/zip.txt
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
zip
|
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/config-version/main.tf
generated
vendored
Normal file
1
vendor/github.com/hashicorp/go-tfe/test-fixtures/config-version/main.tf
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
resource "null_resource" "foo" {}
|
|
@ -106,6 +106,7 @@ type Client struct {
|
||||||
|
|
||||||
Applies Applies
|
Applies Applies
|
||||||
ConfigurationVersions ConfigurationVersions
|
ConfigurationVersions ConfigurationVersions
|
||||||
|
CostEstimations CostEstimations
|
||||||
NotificationConfigurations NotificationConfigurations
|
NotificationConfigurations NotificationConfigurations
|
||||||
OAuthClients OAuthClients
|
OAuthClients OAuthClients
|
||||||
OAuthTokens OAuthTokens
|
OAuthTokens OAuthTokens
|
||||||
|
@ -195,6 +196,7 @@ func NewClient(cfg *Config) (*Client, error) {
|
||||||
// Create the services.
|
// Create the services.
|
||||||
client.Applies = &applies{client: client}
|
client.Applies = &applies{client: client}
|
||||||
client.ConfigurationVersions = &configurationVersions{client: client}
|
client.ConfigurationVersions = &configurationVersions{client: client}
|
||||||
|
client.CostEstimations = &costEstimations{client: client}
|
||||||
client.NotificationConfigurations = ¬ificationConfigurations{client: client}
|
client.NotificationConfigurations = ¬ificationConfigurations{client: client}
|
||||||
client.OAuthClients = &oAuthClients{client: client}
|
client.OAuthClients = &oAuthClients{client: client}
|
||||||
client.OAuthTokens = &oAuthTokens{client: client}
|
client.OAuthTokens = &oAuthTokens{client: client}
|
||||||
|
@ -247,7 +249,7 @@ func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp *
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the rate limit backoff function when we are rate limited.
|
// Use the rate limit backoff function when we are rate limited.
|
||||||
if resp.StatusCode == 429 {
|
if resp != nil && resp.StatusCode == 429 {
|
||||||
return rateLimitBackoff(min, max, attemptNum, resp)
|
return rateLimitBackoff(min, max, attemptNum, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,398 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_newClient(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||||
|
w.Header().Set("X-RateLimit-Limit", "30")
|
||||||
|
w.WriteHeader(404) // We query the configured base URL which should return a 404.
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("uses env vars if values are missing", func(t *testing.T) {
|
||||||
|
defer setupEnvVars("abcd1234", ts.URL)()
|
||||||
|
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if client.token != "abcd1234" {
|
||||||
|
t.Fatalf("unexpected token: %q", client.token)
|
||||||
|
}
|
||||||
|
if client.baseURL.String() != ts.URL+DefaultBasePath {
|
||||||
|
t.Fatalf("unexpected address: %q", client.baseURL.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fails if token is empty", func(t *testing.T) {
|
||||||
|
defer setupEnvVars("", "")()
|
||||||
|
|
||||||
|
_, err := NewClient(cfg)
|
||||||
|
if err == nil || err.Error() != "missing API token" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("makes a new client with good settings", func(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
Address: ts.URL,
|
||||||
|
Token: "abcd1234",
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Address+DefaultBasePath != client.baseURL.String() {
|
||||||
|
t.Fatalf("unexpected client address %q", client.baseURL.String())
|
||||||
|
}
|
||||||
|
if config.Token != client.token {
|
||||||
|
t.Fatalf("unexpected client token %q", client.token)
|
||||||
|
}
|
||||||
|
if ts.Client() != client.http.HTTPClient {
|
||||||
|
t.Fatal("unexpected HTTP client value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_defaultConfig(t *testing.T) {
|
||||||
|
t.Run("with no environment variables", func(t *testing.T) {
|
||||||
|
defer setupEnvVars("", "")()
|
||||||
|
|
||||||
|
config := DefaultConfig()
|
||||||
|
|
||||||
|
if config.Address != DefaultAddress {
|
||||||
|
t.Fatalf("expected %q, got %q", DefaultAddress, config.Address)
|
||||||
|
}
|
||||||
|
if config.Token != "" {
|
||||||
|
t.Fatalf("expected empty token, got %q", config.Token)
|
||||||
|
}
|
||||||
|
if config.HTTPClient == nil {
|
||||||
|
t.Fatalf("expected default http client, got %v", config.HTTPClient)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with environment variables", func(t *testing.T) {
|
||||||
|
defer setupEnvVars("abcd1234", "https://mytfe.local")()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_headers(t *testing.T) {
|
||||||
|
testedCalls := 0
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testedCalls++
|
||||||
|
|
||||||
|
if testedCalls == 1 {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||||
|
w.Header().Set("X-RateLimit-Limit", "30")
|
||||||
|
w.WriteHeader(404) // We query the configured base URL which should return a 404.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Header.Get("Accept") != "application/vnd.api+json" {
|
||||||
|
t.Fatalf("unexpected accept header: %q", r.Header.Get("Accept"))
|
||||||
|
}
|
||||||
|
if r.Header.Get("Authorization") != "Bearer dummy-token" {
|
||||||
|
t.Fatalf("unexpected authorization header: %q", r.Header.Get("Authorization"))
|
||||||
|
}
|
||||||
|
if r.Header.Get("My-Custom-Header") != "foobar" {
|
||||||
|
t.Fatalf("unexpected custom header: %q", r.Header.Get("My-Custom-Header"))
|
||||||
|
}
|
||||||
|
if r.Header.Get("Terraform-Version") != "0.11.9" {
|
||||||
|
t.Fatalf("unexpected Terraform version header: %q", r.Header.Get("Terraform-Version"))
|
||||||
|
}
|
||||||
|
if r.Header.Get("User-Agent") != "go-tfe" {
|
||||||
|
t.Fatalf("unexpected user agent header: %q", r.Header.Get("User-Agent"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Address: ts.URL,
|
||||||
|
Token: "dummy-token",
|
||||||
|
Headers: make(http.Header),
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set some custom header.
|
||||||
|
cfg.Headers.Set("My-Custom-Header", "foobar")
|
||||||
|
cfg.Headers.Set("Terraform-Version", "0.11.9")
|
||||||
|
|
||||||
|
// This one should be overridden!
|
||||||
|
cfg.Headers.Set("Authorization", "bad-token")
|
||||||
|
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Make a few calls so we can check they all send the expected headers.
|
||||||
|
_, _ = client.Organizations.List(ctx, OrganizationListOptions{})
|
||||||
|
_, _ = client.Plans.Logs(ctx, "plan-123456789")
|
||||||
|
_ = client.Runs.Apply(ctx, "run-123456789", RunApplyOptions{})
|
||||||
|
_, _ = client.Workspaces.Lock(ctx, "ws-123456789", WorkspaceLockOptions{})
|
||||||
|
_, _ = client.Workspaces.Read(ctx, "organization", "workspace")
|
||||||
|
|
||||||
|
if testedCalls != 6 {
|
||||||
|
t.Fatalf("expected 6 tested calls, got: %d", testedCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_userAgent(t *testing.T) {
|
||||||
|
testedCalls := 0
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testedCalls++
|
||||||
|
|
||||||
|
if testedCalls == 1 {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||||
|
w.Header().Set("X-RateLimit-Limit", "30")
|
||||||
|
w.WriteHeader(404) // We query the configured base URL which should return a 404.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Header.Get("User-Agent") != "hashicorp" {
|
||||||
|
t.Fatalf("unexpected user agent header: %q", r.Header.Get("User-Agent"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Address: ts.URL,
|
||||||
|
Token: "dummy-token",
|
||||||
|
Headers: make(http.Header),
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a custom user agent.
|
||||||
|
cfg.Headers.Set("User-Agent", "hashicorp")
|
||||||
|
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Make a few calls so we can check they all send the expected headers.
|
||||||
|
_, _ = client.Organizations.List(ctx, OrganizationListOptions{})
|
||||||
|
_, _ = client.Plans.Logs(ctx, "plan-123456789")
|
||||||
|
_ = client.Runs.Apply(ctx, "run-123456789", RunApplyOptions{})
|
||||||
|
_, _ = client.Workspaces.Lock(ctx, "ws-123456789", WorkspaceLockOptions{})
|
||||||
|
_, _ = client.Workspaces.Read(ctx, "organization", "workspace")
|
||||||
|
|
||||||
|
if testedCalls != 6 {
|
||||||
|
t.Fatalf("expected 6 tested calls, got: %d", testedCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_configureLimiter(t *testing.T) {
|
||||||
|
rateLimit := ""
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||||
|
w.Header().Set("X-RateLimit-Limit", rateLimit)
|
||||||
|
w.WriteHeader(404) // We query the configured base URL which should return a 404.
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Address: ts.URL,
|
||||||
|
Token: "dummy-token",
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
rate string
|
||||||
|
limit rate.Limit
|
||||||
|
burst int
|
||||||
|
}{
|
||||||
|
"no-value": {
|
||||||
|
rate: "",
|
||||||
|
limit: rate.Inf,
|
||||||
|
burst: 0,
|
||||||
|
},
|
||||||
|
"limit-0": {
|
||||||
|
rate: "0",
|
||||||
|
limit: rate.Inf,
|
||||||
|
burst: 0,
|
||||||
|
},
|
||||||
|
"limit-30": {
|
||||||
|
rate: "30",
|
||||||
|
limit: rate.Limit(19.8),
|
||||||
|
burst: 9,
|
||||||
|
},
|
||||||
|
"limit-100": {
|
||||||
|
rate: "100",
|
||||||
|
limit: rate.Limit(66),
|
||||||
|
burst: 33,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
// First set the test rate limit.
|
||||||
|
rateLimit = tc.rate
|
||||||
|
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.limiter.Limit() != tc.limit {
|
||||||
|
t.Fatalf("test %s expected limit %f, got: %f", name, tc.limit, client.limiter.Limit())
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.limiter.Burst() != tc.burst {
|
||||||
|
t.Fatalf("test %s expected burst %d, got: %d", name, tc.burst, client.limiter.Burst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_retryHTTPCheck(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||||
|
w.Header().Set("X-RateLimit-Limit", "30")
|
||||||
|
w.WriteHeader(404) // We query the configured base URL which should return a 404.
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Address: ts.URL,
|
||||||
|
Token: "dummy-token",
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
}
|
||||||
|
|
||||||
|
connErr := errors.New("connection error")
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
resp *http.Response
|
||||||
|
err error
|
||||||
|
retryServerErrors bool
|
||||||
|
checkOK bool
|
||||||
|
checkErr error
|
||||||
|
}{
|
||||||
|
"429-no-server-errors": {
|
||||||
|
resp: &http.Response{StatusCode: 429},
|
||||||
|
err: nil,
|
||||||
|
checkOK: true,
|
||||||
|
checkErr: nil,
|
||||||
|
},
|
||||||
|
"429-with-server-errors": {
|
||||||
|
resp: &http.Response{StatusCode: 429},
|
||||||
|
err: nil,
|
||||||
|
retryServerErrors: true,
|
||||||
|
checkOK: true,
|
||||||
|
checkErr: nil,
|
||||||
|
},
|
||||||
|
"500-no-server-errors": {
|
||||||
|
resp: &http.Response{StatusCode: 500},
|
||||||
|
err: nil,
|
||||||
|
checkOK: false,
|
||||||
|
checkErr: nil,
|
||||||
|
},
|
||||||
|
"500-with-server-errors": {
|
||||||
|
resp: &http.Response{StatusCode: 500},
|
||||||
|
err: nil,
|
||||||
|
retryServerErrors: true,
|
||||||
|
checkOK: true,
|
||||||
|
checkErr: nil,
|
||||||
|
},
|
||||||
|
"err-no-server-errors": {
|
||||||
|
err: connErr,
|
||||||
|
checkOK: false,
|
||||||
|
checkErr: connErr,
|
||||||
|
},
|
||||||
|
"err-with-server-errors": {
|
||||||
|
err: connErr,
|
||||||
|
retryServerErrors: true,
|
||||||
|
checkOK: true,
|
||||||
|
checkErr: connErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.RetryServerErrors(tc.retryServerErrors)
|
||||||
|
|
||||||
|
checkOK, checkErr := client.retryHTTPCheck(ctx, tc.resp, tc.err)
|
||||||
|
if checkOK != tc.checkOK {
|
||||||
|
t.Fatalf("test %s expected checkOK %t, got: %t", name, tc.checkOK, checkOK)
|
||||||
|
}
|
||||||
|
if checkErr != tc.checkErr {
|
||||||
|
t.Fatalf("test %s expected checkErr %v, got: %v", name, tc.checkErr, checkErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_retryHTTPBackoff(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||||
|
w.Header().Set("X-RateLimit-Limit", "30")
|
||||||
|
w.WriteHeader(404) // We query the configured base URL which should return a 404.
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
var attempts int
|
||||||
|
retryLogHook := func(attemptNum int, resp *http.Response) {
|
||||||
|
attempts++
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Address: ts.URL,
|
||||||
|
Token: "dummy-token",
|
||||||
|
HTTPClient: ts.Client(),
|
||||||
|
RetryLogHook: retryLogHook,
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retries := 3
|
||||||
|
resp := &http.Response{StatusCode: 500}
|
||||||
|
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
client.retryHTTPBackoff(time.Second, time.Second, i, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempts != retries {
|
||||||
|
t.Fatalf("expected %d log hook callbacks, got: %d callbacks", retries, attempts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupEnvVars(token, address string) func() {
|
||||||
|
origToken := os.Getenv("TFE_TOKEN")
|
||||||
|
origAddress := os.Getenv("TFE_ADDRESS")
|
||||||
|
|
||||||
|
os.Setenv("TFE_TOKEN", token)
|
||||||
|
os.Setenv("TFE_ADDRESS", address)
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
os.Setenv("TFE_TOKEN", origToken)
|
||||||
|
os.Setenv("TFE_ADDRESS", origAddress)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUsersReadCurrent(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
u, err := client.Users.ReadCurrent(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, u.ID)
|
||||||
|
assert.NotEmpty(t, u.AvatarURL)
|
||||||
|
assert.NotEmpty(t, u.Username)
|
||||||
|
|
||||||
|
t.Run("two factor options are decoded", func(t *testing.T) {
|
||||||
|
assert.NotNil(t, u.TwoFactor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
uTest, err := client.Users.ReadCurrent(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make sure we reset the current user when were done.
|
||||||
|
defer func() {
|
||||||
|
client.Users.Update(ctx, UserUpdateOptions{
|
||||||
|
Email: String(uTest.Email),
|
||||||
|
Username: String(uTest.Username),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Run("without any options", func(t *testing.T) {
|
||||||
|
_, err := client.Users.Update(ctx, UserUpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u, err := client.Users.ReadCurrent(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, u, uTest)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with a new username", func(t *testing.T) {
|
||||||
|
_, err := client.Users.Update(ctx, UserUpdateOptions{
|
||||||
|
Username: String("NewTestUsername"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u, err := client.Users.ReadCurrent(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "NewTestUsername", u.Username)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with a new email address", func(t *testing.T) {
|
||||||
|
_, err := client.Users.Update(ctx, UserUpdateOptions{
|
||||||
|
Email: String("newtestemail@hashicorp.com"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u, err := client.Users.ReadCurrent(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "newtestemail@hashicorp.com", u.UnconfirmedEmail)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid email address", func(t *testing.T) {
|
||||||
|
u, err := client.Users.Update(ctx, UserUpdateOptions{
|
||||||
|
Email: String("notamailaddress"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, u)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,295 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVariablesList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
vTest1, _ := createVariable(t, client, wTest)
|
||||||
|
vTest2, _ := createVariable(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
vl, err := client.Variables.List(ctx, VariableListOptions{
|
||||||
|
Organization: String(orgTest.Name),
|
||||||
|
Workspace: String(wTest.Name),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, vl.Items, vTest1)
|
||||||
|
assert.Contains(t, vl.Items, vTest2)
|
||||||
|
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
assert.Equal(t, 1, vl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, vl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
t.Skip("paging not supported yet in API")
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
vl, err := client.Variables.List(ctx, VariableListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
Organization: String(orgTest.Name),
|
||||||
|
Workspace: String(wTest.Name),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, vl.Items)
|
||||||
|
assert.Equal(t, 999, vl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, vl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing an organization", func(t *testing.T) {
|
||||||
|
vl, err := client.Variables.List(ctx, VariableListOptions{
|
||||||
|
Workspace: String(wTest.Name),
|
||||||
|
})
|
||||||
|
assert.Nil(t, vl)
|
||||||
|
assert.EqualError(t, err, "organization is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing an workspace", func(t *testing.T) {
|
||||||
|
vl, err := client.Variables.List(ctx, VariableListOptions{
|
||||||
|
Organization: String(orgTest.Name),
|
||||||
|
})
|
||||||
|
assert.Nil(t, vl)
|
||||||
|
assert.EqualError(t, err, "workspace is required")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariablesCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := VariableCreateOptions{
|
||||||
|
Key: String(randomString(t)),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
Category: Category(CategoryTerraform),
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Variables.Create(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, v.ID)
|
||||||
|
assert.Equal(t, *options.Key, v.Key)
|
||||||
|
assert.Equal(t, *options.Value, v.Value)
|
||||||
|
assert.Equal(t, *options.Category, v.Category)
|
||||||
|
// The workspace isn't returned correcly by the API.
|
||||||
|
// assert.Equal(t, *options.Workspace, v.Workspace)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an empty string value", func(t *testing.T) {
|
||||||
|
options := VariableCreateOptions{
|
||||||
|
Key: String(randomString(t)),
|
||||||
|
Value: String(""),
|
||||||
|
Category: Category(CategoryTerraform),
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Variables.Create(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, v.ID)
|
||||||
|
assert.Equal(t, *options.Key, v.Key)
|
||||||
|
assert.Equal(t, *options.Value, v.Value)
|
||||||
|
assert.Equal(t, *options.Category, v.Category)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing value", func(t *testing.T) {
|
||||||
|
options := VariableCreateOptions{
|
||||||
|
Key: String(randomString(t)),
|
||||||
|
Category: Category(CategoryTerraform),
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Variables.Create(ctx, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, v.ID)
|
||||||
|
assert.Equal(t, *options.Key, v.Key)
|
||||||
|
assert.Equal(t, "", v.Value)
|
||||||
|
assert.Equal(t, *options.Category, v.Category)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing key", func(t *testing.T) {
|
||||||
|
options := VariableCreateOptions{
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
Category: Category(CategoryTerraform),
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Variables.Create(ctx, options)
|
||||||
|
assert.EqualError(t, err, "key is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an empty key", func(t *testing.T) {
|
||||||
|
options := VariableCreateOptions{
|
||||||
|
Key: String(""),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
Category: Category(CategoryTerraform),
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Variables.Create(ctx, options)
|
||||||
|
assert.EqualError(t, err, "key is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing category", func(t *testing.T) {
|
||||||
|
options := VariableCreateOptions{
|
||||||
|
Key: String(randomString(t)),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
Workspace: wTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Variables.Create(ctx, options)
|
||||||
|
assert.EqualError(t, err, "category is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing workspace", func(t *testing.T) {
|
||||||
|
options := VariableCreateOptions{
|
||||||
|
Key: String(randomString(t)),
|
||||||
|
Value: String(randomString(t)),
|
||||||
|
Category: Category(CategoryTerraform),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.Variables.Create(ctx, options)
|
||||||
|
assert.EqualError(t, err, "workspace is required")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariablesRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
vTest, vTestCleanup := createVariable(t, client, nil)
|
||||||
|
defer vTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the variable exists", func(t *testing.T) {
|
||||||
|
v, err := client.Variables.Read(ctx, vTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, vTest.ID, v.ID)
|
||||||
|
assert.Equal(t, vTest.Category, v.Category)
|
||||||
|
assert.Equal(t, vTest.HCL, v.HCL)
|
||||||
|
assert.Equal(t, vTest.Key, v.Key)
|
||||||
|
assert.Equal(t, vTest.Sensitive, v.Sensitive)
|
||||||
|
assert.Equal(t, vTest.Value, v.Value)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the variable does not exist", func(t *testing.T) {
|
||||||
|
v, err := client.Variables.Read(ctx, "nonexisting")
|
||||||
|
assert.Nil(t, v)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid variable ID", func(t *testing.T) {
|
||||||
|
v, err := client.Variables.Read(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, v)
|
||||||
|
assert.EqualError(t, err, "invalid value for variable ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariablesUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
vTest, vTestCleanup := createVariable(t, client, nil)
|
||||||
|
defer vTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := VariableUpdateOptions{
|
||||||
|
Key: String("newname"),
|
||||||
|
Value: String("newvalue"),
|
||||||
|
HCL: Bool(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Variables.Update(ctx, vTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, *options.Key, v.Key)
|
||||||
|
assert.Equal(t, *options.HCL, v.HCL)
|
||||||
|
assert.Equal(t, *options.Value, v.Value)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when updating a subset of values", func(t *testing.T) {
|
||||||
|
options := VariableUpdateOptions{
|
||||||
|
Key: String("someothername"),
|
||||||
|
HCL: Bool(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Variables.Update(ctx, vTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, *options.Key, v.Key)
|
||||||
|
assert.Equal(t, *options.HCL, v.HCL)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with sensitive set", func(t *testing.T) {
|
||||||
|
options := VariableUpdateOptions{
|
||||||
|
Sensitive: Bool(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Variables.Update(ctx, vTest.ID, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, *options.Sensitive, v.Sensitive)
|
||||||
|
assert.Empty(t, v.Value) // Because its now sensitive
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without any changes", func(t *testing.T) {
|
||||||
|
vTest, vTestCleanup := createVariable(t, client, nil)
|
||||||
|
defer vTestCleanup()
|
||||||
|
|
||||||
|
v, err := client.Variables.Update(ctx, vTest.ID, VariableUpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, vTest, v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid variable ID", func(t *testing.T) {
|
||||||
|
_, err := client.Variables.Update(ctx, badIdentifier, VariableUpdateOptions{})
|
||||||
|
assert.EqualError(t, err, "invalid value for variable ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariablesDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, nil)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
vTest, _ := createVariable(t, client, wTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.Variables.Delete(ctx, vTest.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with non existing variable ID", func(t *testing.T) {
|
||||||
|
err := client.Variables.Delete(ctx, "nonexisting")
|
||||||
|
assert.Equal(t, err, ErrResourceNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with invalid variable ID", func(t *testing.T) {
|
||||||
|
err := client.Variables.Delete(ctx, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for variable ID")
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,496 @@
|
||||||
|
package tfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWorkspacesList(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest1, wTest1Cleanup := createWorkspace(t, client, orgTest)
|
||||||
|
defer wTest1Cleanup()
|
||||||
|
wTest2, wTest2Cleanup := createWorkspace(t, client, orgTest)
|
||||||
|
defer wTest2Cleanup()
|
||||||
|
|
||||||
|
t.Run("without list options", func(t *testing.T) {
|
||||||
|
wl, err := client.Workspaces.List(ctx, orgTest.Name, WorkspaceListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, wl.Items, wTest1)
|
||||||
|
assert.Contains(t, wl.Items, wTest2)
|
||||||
|
assert.Equal(t, 1, wl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, wl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with list options", func(t *testing.T) {
|
||||||
|
// Request a page number which is out of range. The result should
|
||||||
|
// be successful, but return no results if the paging options are
|
||||||
|
// properly passed along.
|
||||||
|
wl, err := client.Workspaces.List(ctx, orgTest.Name, WorkspaceListOptions{
|
||||||
|
ListOptions: ListOptions{
|
||||||
|
PageNumber: 999,
|
||||||
|
PageSize: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, wl.Items)
|
||||||
|
assert.Equal(t, 999, wl.CurrentPage)
|
||||||
|
assert.Equal(t, 2, wl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when searching a known workspace", func(t *testing.T) {
|
||||||
|
// Use a known workspace prefix as search attribute. The result
|
||||||
|
// should be successful and only contain the matching workspace.
|
||||||
|
wl, err := client.Workspaces.List(ctx, orgTest.Name, WorkspaceListOptions{
|
||||||
|
Search: String(wTest1.Name[:len(wTest1.Name)-5]),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, wl.Items, wTest1)
|
||||||
|
assert.NotContains(t, wl.Items, wTest2)
|
||||||
|
assert.Equal(t, 1, wl.CurrentPage)
|
||||||
|
assert.Equal(t, 1, wl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when searching an unknown workspace", func(t *testing.T) {
|
||||||
|
// Use a nonexisting workspace name as search attribute. The result
|
||||||
|
// should be successful, but return no results.
|
||||||
|
wl, err := client.Workspaces.List(ctx, orgTest.Name, WorkspaceListOptions{
|
||||||
|
Search: String("nonexisting"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, wl.Items)
|
||||||
|
assert.Equal(t, 1, wl.CurrentPage)
|
||||||
|
assert.Equal(t, 0, wl.TotalCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
wl, err := client.Workspaces.List(ctx, badIdentifier, WorkspaceListOptions{})
|
||||||
|
assert.Nil(t, wl)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesCreate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := WorkspaceCreateOptions{
|
||||||
|
Name: String("foo"),
|
||||||
|
AutoApply: Bool(true),
|
||||||
|
QueueAllRuns: Bool(true),
|
||||||
|
TerraformVersion: String("0.11.0"),
|
||||||
|
WorkingDirectory: String("bar/"),
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := client.Workspaces.Create(ctx, orgTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view from the API.
|
||||||
|
refreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*Workspace{
|
||||||
|
w,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.NotEmpty(t, item.ID)
|
||||||
|
assert.Equal(t, *options.Name, item.Name)
|
||||||
|
assert.Equal(t, *options.AutoApply, item.AutoApply)
|
||||||
|
assert.Equal(t, *options.QueueAllRuns, item.QueueAllRuns)
|
||||||
|
assert.Equal(t, *options.TerraformVersion, item.TerraformVersion)
|
||||||
|
assert.Equal(t, *options.WorkingDirectory, item.WorkingDirectory)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options is missing name", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Create(ctx, "foo", WorkspaceCreateOptions{})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid name", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Create(ctx, "foo", WorkspaceCreateOptions{
|
||||||
|
Name: String(badIdentifier),
|
||||||
|
})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for name")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid organization", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Create(ctx, badIdentifier, WorkspaceCreateOptions{
|
||||||
|
Name: String("foo"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when an error is returned from the api", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Create(ctx, "bar", WorkspaceCreateOptions{
|
||||||
|
Name: String("bar"),
|
||||||
|
TerraformVersion: String("nonexisting"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesRead(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, orgTest)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
t.Run("when the workspace exists", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, wTest, w)
|
||||||
|
|
||||||
|
t.Run("permissions are properly decoded", func(t *testing.T) {
|
||||||
|
assert.True(t, w.Permissions.CanDestroy)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("relationships are properly decoded", func(t *testing.T) {
|
||||||
|
assert.Equal(t, orgTest.Name, w.Organization.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("timestamps are properly decoded", func(t *testing.T) {
|
||||||
|
assert.NotEmpty(t, w.CreatedAt)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the workspace does not exist", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Read(ctx, orgTest.Name, "nonexisting")
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when the organization does not exist", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Read(ctx, "nonexisting", "nonexisting")
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid organization", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Read(ctx, badIdentifier, wTest.Name)
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Read(ctx, orgTest.Name, badIdentifier)
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesUpdate(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("when updating a subset of values", func(t *testing.T) {
|
||||||
|
options := WorkspaceUpdateOptions{
|
||||||
|
Name: String(wTest.Name),
|
||||||
|
AutoApply: Bool(true),
|
||||||
|
QueueAllRuns: Bool(true),
|
||||||
|
TerraformVersion: String("0.10.0"),
|
||||||
|
}
|
||||||
|
|
||||||
|
wAfter, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, wTest.Name, wAfter.Name)
|
||||||
|
assert.NotEqual(t, wTest.AutoApply, wAfter.AutoApply)
|
||||||
|
assert.NotEqual(t, wTest.QueueAllRuns, wAfter.QueueAllRuns)
|
||||||
|
assert.NotEqual(t, wTest.TerraformVersion, wAfter.TerraformVersion)
|
||||||
|
assert.Equal(t, wTest.WorkingDirectory, wAfter.WorkingDirectory)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
options := WorkspaceUpdateOptions{
|
||||||
|
Name: String(randomString(t)),
|
||||||
|
AutoApply: Bool(false),
|
||||||
|
QueueAllRuns: Bool(false),
|
||||||
|
TerraformVersion: String("0.11.1"),
|
||||||
|
WorkingDirectory: String("baz/"),
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get a refreshed view of the workspace from the API
|
||||||
|
refreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, item := range []*Workspace{
|
||||||
|
w,
|
||||||
|
refreshed,
|
||||||
|
} {
|
||||||
|
assert.Equal(t, *options.Name, item.Name)
|
||||||
|
assert.Equal(t, *options.AutoApply, item.AutoApply)
|
||||||
|
assert.Equal(t, *options.QueueAllRuns, item.QueueAllRuns)
|
||||||
|
assert.Equal(t, *options.TerraformVersion, item.TerraformVersion)
|
||||||
|
assert.Equal(t, *options.WorkingDirectory, item.WorkingDirectory)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when an error is returned from the api", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{
|
||||||
|
TerraformVersion: String("nonexisting"),
|
||||||
|
})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid name", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Update(ctx, orgTest.Name, badIdentifier, WorkspaceUpdateOptions{})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when options has an invalid organization", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Update(ctx, badIdentifier, wTest.Name, WorkspaceUpdateOptions{})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesDelete(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
err := client.Workspaces.Delete(ctx, orgTest.Name, wTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Try loading the workspace - it should fail.
|
||||||
|
_, err = client.Workspaces.Read(ctx, orgTest.Name, wTest.Name)
|
||||||
|
assert.Equal(t, ErrResourceNotFound, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when organization is invalid", func(t *testing.T) {
|
||||||
|
err := client.Workspaces.Delete(ctx, badIdentifier, wTest.Name)
|
||||||
|
assert.EqualError(t, err, "invalid value for organization")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when workspace is invalid", func(t *testing.T) {
|
||||||
|
err := client.Workspaces.Delete(ctx, orgTest.Name, badIdentifier)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesRemoveVCSConnection(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspaceWithVCS(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("remove vcs integration", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.RemoveVCSConnection(ctx, orgTest.Name, wTest.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, (*VCSRepo)(nil), w.VCSRepo)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesLock(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, w.Locked)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when workspace is already locked", func(t *testing.T) {
|
||||||
|
_, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})
|
||||||
|
assert.Equal(t, ErrWorkspaceLocked, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace ID", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Lock(ctx, badIdentifier, WorkspaceLockOptions{})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesUnlock(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
w, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})
|
||||||
|
if err != nil {
|
||||||
|
orgTestCleanup()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, w.Locked)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Unlock(ctx, wTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, w.Locked)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when workspace is already unlocked", func(t *testing.T) {
|
||||||
|
_, err := client.Workspaces.Unlock(ctx, wTest.ID)
|
||||||
|
assert.Equal(t, ErrWorkspaceNotLocked, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace ID", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.Unlock(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesForceUnlock(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, _ := createWorkspace(t, client, orgTest)
|
||||||
|
|
||||||
|
w, err := client.Workspaces.Lock(ctx, wTest.ID, WorkspaceLockOptions{})
|
||||||
|
if err != nil {
|
||||||
|
orgTestCleanup()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, w.Locked)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.ForceUnlock(ctx, wTest.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, w.Locked)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when workspace is already unlocked", func(t *testing.T) {
|
||||||
|
_, err := client.Workspaces.ForceUnlock(ctx, wTest.ID)
|
||||||
|
assert.Equal(t, ErrWorkspaceNotLocked, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace ID", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.ForceUnlock(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesAssignSSHKey(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, orgTest)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
sshKeyTest, sshKeyTestCleanup := createSSHKey(t, client, orgTest)
|
||||||
|
defer sshKeyTestCleanup()
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{
|
||||||
|
SSHKeyID: String(sshKeyTest.ID),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, w.SSHKey)
|
||||||
|
assert.Equal(t, w.SSHKey.ID, sshKeyTest.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without an SSH key ID", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "SSH key ID is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid SSH key ID", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{
|
||||||
|
SSHKeyID: String(badIdentifier),
|
||||||
|
})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for SSH key ID")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace ID", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.AssignSSHKey(ctx, badIdentifier, WorkspaceAssignSSHKeyOptions{
|
||||||
|
SSHKeyID: String(sshKeyTest.ID),
|
||||||
|
})
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorkspacesUnassignSSHKey(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
orgTest, orgTestCleanup := createOrganization(t, client)
|
||||||
|
defer orgTestCleanup()
|
||||||
|
|
||||||
|
wTest, wTestCleanup := createWorkspace(t, client, orgTest)
|
||||||
|
defer wTestCleanup()
|
||||||
|
|
||||||
|
sshKeyTest, sshKeyTestCleanup := createSSHKey(t, client, orgTest)
|
||||||
|
defer sshKeyTestCleanup()
|
||||||
|
|
||||||
|
w, err := client.Workspaces.AssignSSHKey(ctx, wTest.ID, WorkspaceAssignSSHKeyOptions{
|
||||||
|
SSHKeyID: String(sshKeyTest.ID),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
orgTestCleanup()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, w.SSHKey)
|
||||||
|
require.Equal(t, w.SSHKey.ID, sshKeyTest.ID)
|
||||||
|
|
||||||
|
t.Run("with valid options", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.UnassignSSHKey(ctx, wTest.ID)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Nil(t, w.SSHKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without a valid workspace ID", func(t *testing.T) {
|
||||||
|
w, err := client.Workspaces.UnassignSSHKey(ctx, badIdentifier)
|
||||||
|
assert.Nil(t, w)
|
||||||
|
assert.EqualError(t, err, "invalid value for workspace ID")
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue