pulumi/cmd/new_test.go
Pat Gavlin 14da941b0c
Standardize on Stack for backend methods. (#3336)
The current pattern of using backend.Stack values in the CLI but
accepting backend.StackReference values in backend methods leads to
frequent transitions between these types. In the case of the HTTP
backend, the transition from a StackReference to a Stack requires an API
call. These changes refactor the backend.Backend API such that most of
its methods accept backend.Stack values in place of
backend.StackReference values, which avoids these transitions.

This removes two calls to the getStack API on the startup path of a
`pulumi preview`.
2019-10-14 14:30:42 -07:00

507 lines
13 KiB
Go

// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/pulumi/pulumi/pkg/backend"
"github.com/pulumi/pulumi/pkg/backend/display"
"github.com/pulumi/pulumi/pkg/workspace"
"github.com/stretchr/testify/assert"
)
func TestCreatingStackWithArgsSpecifiedName(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
var args = newArgs{
interactive: false,
prompt: promptForValue,
secretsProvider: "default",
stack: stackName,
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
assert.Equal(t, stackName, loadStackName(t))
removeStack(t, stackName)
}
func TestCreatingStackWithPromptedName(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
uniqueProjectName := filepath.Base(tempdir)
var args = newArgs{
interactive: true,
prompt: promptMock(uniqueProjectName, stackName),
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
assert.Equal(t, stackName, loadStackName(t))
removeStack(t, stackName)
}
func TestCreatingStackWithArgsSpecifiedOrgName(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
orgStackName := fmt.Sprintf("%s/%s", currentUser(t), stackName)
var args = newArgs{
interactive: false,
prompt: promptForValue,
secretsProvider: "default",
stack: orgStackName,
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
assert.Equal(t, stackName, loadStackName(t))
removeStack(t, stackName)
}
func TestCreatingStackWithPromptedOrgName(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
uniqueProjectName := filepath.Base(tempdir)
orgStackName := fmt.Sprintf("%s/%s", currentUser(t), stackName)
var args = newArgs{
interactive: true,
prompt: promptMock(uniqueProjectName, orgStackName),
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
assert.Equal(t, stackName, loadStackName(t))
removeStack(t, stackName)
}
func TestCreatingStackWithArgsSpecifiedFullNameSucceeds(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
fullStackName := fmt.Sprintf("%s/%s/%s", currentUser(t), projectName, stackName)
var args = newArgs{
interactive: false,
prompt: promptForValue,
secretsProvider: "default",
stack: fullStackName,
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
assert.Equal(t, fullStackName, loadStackName(t))
}
func TestCreatingProjectWithDefaultName(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
defaultProjectName := filepath.Base(tempdir)
var args = newArgs{
interactive: true,
prompt: promptForValue,
secretsProvider: "default",
stack: stackName,
templateNameOrURL: "typescript",
yes: true,
}
err := runNew(args)
assert.NoError(t, err)
removeStack(t, stackName)
proj := loadProject(t, tempdir)
assert.Equal(t, defaultProjectName, proj.Name.String())
}
func TestCreatingProjectWithArgsSpecifiedName(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
uniqueProjectName := filepath.Base(tempdir) + "test"
var args = newArgs{
interactive: false,
name: uniqueProjectName,
prompt: promptForValue,
secretsProvider: "default",
stack: stackName,
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
removeStack(t, stackName)
proj := loadProject(t, tempdir)
assert.Equal(t, uniqueProjectName, proj.Name.String())
}
func TestCreatingProjectWithPromptedName(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
uniqueProjectName := filepath.Base(tempdir) + "test"
var args = newArgs{
interactive: true,
prompt: promptMock(uniqueProjectName, stackName),
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
removeStack(t, stackName)
proj := loadProject(t, tempdir)
assert.Equal(t, uniqueProjectName, proj.Name.String())
}
func TestCreatingProjectWithExistingArgsSpecifiedNameFails(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
backendInstance = &backend.MockBackend{
DoesProjectExistF: func(ctx context.Context, name string) (bool, error) {
return name == projectName, nil
},
}
var args = newArgs{
interactive: false,
name: projectName,
prompt: promptForValue,
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.Error(t, err)
assert.Contains(t, err.Error(), "project with this name already exists")
}
func TestCreatingProjectWithExistingPromptedNameFails(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
backendInstance = &backend.MockBackend{
DoesProjectExistF: func(ctx context.Context, name string) (bool, error) {
return name == projectName, nil
},
}
var args = newArgs{
interactive: true,
prompt: promptMock(projectName, ""),
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.Error(t, err)
assert.Contains(t, err.Error(), "project with this name already exists")
}
func TestGeneratingProjectWithExistingArgsSpecifiedNameSucceeds(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
backendInstance = &backend.MockBackend{
DoesProjectExistF: func(ctx context.Context, name string) (bool, error) {
return true, nil
},
}
// Generate-only command is not creating any stacks, so don't bother with with the name uniqueness check.
var args = newArgs{
generateOnly: true,
interactive: false,
name: projectName,
prompt: promptForValue,
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
proj := loadProject(t, tempdir)
assert.Equal(t, projectName, proj.Name.String())
}
func TestGeneratingProjectWithExistingPromptedNameSucceeds(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
backendInstance = &backend.MockBackend{
DoesProjectExistF: func(ctx context.Context, name string) (bool, error) {
return true, nil
},
}
// Generate-only command is not creating any stacks, so don't bother with with the name uniqueness check.
var args = newArgs{
generateOnly: true,
interactive: true,
prompt: promptMock(projectName, ""),
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.NoError(t, err)
proj := loadProject(t, tempdir)
assert.Equal(t, projectName, proj.Name.String())
}
func TestGeneratingProjectWithInvalidArgsSpecifiedNameFails(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
backendInstance = &backend.MockBackend{
DoesProjectExistF: func(ctx context.Context, name string) (bool, error) {
return true, nil
},
}
// Generate-only command is not creating any stacks, so don't bother with with the name uniqueness check.
var args = newArgs{
generateOnly: true,
interactive: false,
name: "not#valid",
prompt: promptForValue,
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.Error(t, err)
assert.Contains(t, err.Error(), "project name may only contain")
}
func TestGeneratingProjectWithInvalidPromptedNameFails(t *testing.T) {
skipIfShort(t)
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
backendInstance = &backend.MockBackend{
DoesProjectExistF: func(ctx context.Context, name string) (bool, error) {
return true, nil
},
}
// Generate-only command is not creating any stacks, so don't bother with with the name uniqueness check.
var args = newArgs{
generateOnly: true,
interactive: true,
prompt: promptMock("not#valid", ""),
secretsProvider: "default",
templateNameOrURL: "typescript",
}
err := runNew(args)
assert.Error(t, err)
assert.Contains(t, err.Error(), "project name may only contain")
}
func TestInvalidTemplateName(t *testing.T) {
skipIfShort(t)
t.Run("NoTemplateSpecified", func(t *testing.T) {
t.Parallel()
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
var args = newArgs{
secretsProvider: "default",
templateNameOrURL: "",
}
err := runNew(args)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no template selected")
})
t.Run("RemoteTemplateNotFound", func(t *testing.T) {
t.Parallel()
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
// A template that will never exist.
template := "this-is-not-the-template-youre-looking-for"
var args = newArgs{
secretsProvider: "default",
templateNameOrURL: template,
}
err := runNew(args)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
})
t.Run("LocalTemplateNotFound", func(t *testing.T) {
t.Parallel()
tempdir, _ := ioutil.TempDir("", "test-env")
defer os.RemoveAll(tempdir)
assert.NoError(t, os.Chdir(tempdir))
// A template that will never exist remotely.
template := "this-is-not-the-template-youre-looking-for"
var args = newArgs{
generateOnly: true,
offline: true,
secretsProvider: "default",
templateNameOrURL: template,
yes: true,
}
err := runNew(args)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
})
}
const projectName = "test_project"
const stackName = "test_stack"
func promptMock(name string, stackName string) promptForValueFunc {
return func(yes bool, valueType string, defaultValue string, secret bool,
isValidFn func(value string) error, opts display.Options) (string, error) {
if valueType == "project name" {
err := isValidFn(name)
return name, err
}
if valueType == "stack name" {
err := isValidFn(stackName)
return stackName, err
}
return defaultValue, nil
}
}
func loadProject(t *testing.T, dir string) *workspace.Project {
path, err := workspace.DetectProjectPathFrom(dir)
assert.NoError(t, err)
proj, err := workspace.LoadProject(path)
assert.NoError(t, err)
return proj
}
func currentUser(t *testing.T) string {
b, err := currentBackend(display.Options{})
assert.NoError(t, err)
currentUser, err := b.CurrentUser()
assert.NoError(t, err)
return currentUser
}
func loadStackName(t *testing.T) string {
w, err := workspace.New()
assert.NoError(t, err)
return w.Settings().Stack
}
func removeStack(t *testing.T, name string) {
b, err := currentBackend(display.Options{})
assert.NoError(t, err)
ref, err := b.ParseStackReference(name)
assert.NoError(t, err)
stack, err := b.GetStack(context.Background(), ref)
assert.NoError(t, err)
_, err = b.RemoveStack(context.Background(), stack, false)
assert.NoError(t, err)
}
func skipIfShort(t *testing.T) {
if testing.Short() {
t.Skip("Skipped in short test run")
}
}