2018-06-25 05:47:54 +00:00
|
|
|
package workspace
|
|
|
|
|
|
|
|
import (
|
2022-10-19 08:12:25 +00:00
|
|
|
"context"
|
2018-06-25 05:47:54 +00:00
|
|
|
"encoding/json"
|
2022-10-19 08:12:25 +00:00
|
|
|
"fmt"
|
2022-09-22 08:27:27 +00:00
|
|
|
"os"
|
2018-06-25 05:47:54 +00:00
|
|
|
"testing"
|
|
|
|
|
2023-10-10 01:35:39 +00:00
|
|
|
"github.com/pulumi/esc"
|
2023-11-22 05:04:14 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/encoding"
|
2022-09-22 15:32:18 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
|
2022-09-02 17:25:45 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
2018-06-25 05:47:54 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2023-02-10 17:05:21 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2018-06-25 05:47:54 +00:00
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestProjectRuntimeInfoRoundtripYAML(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2018-06-25 05:47:54 +00:00
|
|
|
doTest := func(marshal func(interface{}) ([]byte, error), unmarshal func([]byte, interface{}) error) {
|
|
|
|
ri := NewProjectRuntimeInfo("nodejs", nil)
|
|
|
|
byts, err := marshal(ri)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
var riRountrip ProjectRuntimeInfo
|
|
|
|
err = unmarshal(byts, &riRountrip)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "nodejs", riRountrip.Name())
|
|
|
|
assert.Nil(t, riRountrip.Options())
|
|
|
|
|
2018-08-06 18:05:44 +00:00
|
|
|
ri = NewProjectRuntimeInfo("nodejs", map[string]interface{}{
|
|
|
|
"typescript": true,
|
|
|
|
"stringOption": "hello",
|
2018-06-25 05:47:54 +00:00
|
|
|
})
|
|
|
|
byts, err = marshal(ri)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
err = unmarshal(byts, &riRountrip)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "nodejs", riRountrip.Name())
|
|
|
|
assert.Equal(t, true, riRountrip.Options()["typescript"])
|
2018-08-06 18:05:44 +00:00
|
|
|
assert.Equal(t, "hello", riRountrip.Options()["stringOption"])
|
2018-06-25 05:47:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
doTest(yaml.Marshal, yaml.Unmarshal)
|
|
|
|
doTest(json.Marshal, json.Unmarshal)
|
|
|
|
}
|
2022-08-25 12:52:17 +00:00
|
|
|
|
2022-09-20 18:13:45 +00:00
|
|
|
func TestProjectValidationForNameAndRuntime(t *testing.T) {
|
2022-08-25 12:52:17 +00:00
|
|
|
t.Parallel()
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Test lack of name
|
|
|
|
proj := Project{}
|
|
|
|
err = proj.Validate()
|
2023-12-08 06:40:14 +00:00
|
|
|
assert.EqualError(t, err, "project is missing a 'name' attribute")
|
2022-08-25 12:52:17 +00:00
|
|
|
// Test lack of runtime
|
|
|
|
proj.Name = "a project"
|
|
|
|
err = proj.Validate()
|
2023-12-08 06:40:14 +00:00
|
|
|
assert.EqualError(t, err, "project is missing a 'runtime' attribute")
|
2022-08-25 12:52:17 +00:00
|
|
|
|
2022-09-02 09:35:14 +00:00
|
|
|
// Test success
|
2022-08-25 12:52:17 +00:00
|
|
|
proj.Runtime = NewProjectRuntimeInfo("test", nil)
|
|
|
|
err = proj.Validate()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
2022-09-20 18:13:45 +00:00
|
|
|
func TestProjectValidationFailsForIncorrectDefaultValueType(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
project := Project{Name: "test", Runtime: NewProjectRuntimeInfo("dotnet", nil)}
|
|
|
|
invalidConfig := make(map[string]ProjectConfigType)
|
2022-10-30 22:42:39 +00:00
|
|
|
integerType := "integer"
|
2022-09-20 18:13:45 +00:00
|
|
|
invalidConfig["instanceSize"] = ProjectConfigType{
|
2022-10-30 22:42:39 +00:00
|
|
|
Type: &integerType,
|
2022-09-20 18:13:45 +00:00
|
|
|
Items: nil,
|
|
|
|
Default: "hello",
|
|
|
|
}
|
|
|
|
|
|
|
|
project.Config = invalidConfig
|
|
|
|
err := project.Validate()
|
2023-12-20 15:54:06 +00:00
|
|
|
assert.ErrorContains(t, err,
|
2022-09-20 18:13:45 +00:00
|
|
|
"The default value specified for configuration key 'instanceSize' is not of the expected type 'integer'")
|
|
|
|
|
|
|
|
invalidValues := make([]interface{}, 0)
|
|
|
|
invalidValues = append(invalidValues, "hello")
|
|
|
|
// default value here has type array<string>
|
|
|
|
// config type specified is array<array<string>>
|
|
|
|
// should fail!
|
2022-10-30 22:42:39 +00:00
|
|
|
arrayType := "array"
|
2022-09-20 18:13:45 +00:00
|
|
|
invalidConfigWithArray := make(map[string]ProjectConfigType)
|
|
|
|
invalidConfigWithArray["values"] = ProjectConfigType{
|
2022-10-30 22:42:39 +00:00
|
|
|
Type: &arrayType,
|
2022-09-20 18:13:45 +00:00
|
|
|
Items: &ProjectConfigItemsType{
|
|
|
|
Type: "array",
|
|
|
|
Items: &ProjectConfigItemsType{
|
|
|
|
Type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Default: invalidValues,
|
|
|
|
}
|
|
|
|
project.Config = invalidConfigWithArray
|
|
|
|
err = project.Validate()
|
2023-12-20 15:54:06 +00:00
|
|
|
assert.ErrorContains(t, err,
|
2022-09-20 18:13:45 +00:00
|
|
|
"The default value specified for configuration key 'values' is not of the expected type 'array<array<string>>'")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProjectValidationSucceedsForCorrectDefaultValueType(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
project := Project{Name: "test", Runtime: NewProjectRuntimeInfo("dotnet", nil)}
|
2022-10-30 22:42:39 +00:00
|
|
|
integerType := "integer"
|
2022-09-20 18:13:45 +00:00
|
|
|
validConfig := make(map[string]ProjectConfigType)
|
|
|
|
validConfig["instanceSize"] = ProjectConfigType{
|
2022-10-30 22:42:39 +00:00
|
|
|
Type: &integerType,
|
2022-09-20 18:13:45 +00:00
|
|
|
Items: nil,
|
|
|
|
Default: 1,
|
|
|
|
}
|
|
|
|
|
|
|
|
project.Config = validConfig
|
|
|
|
err := project.Validate()
|
|
|
|
assert.NoError(t, err, "There should be no validation error")
|
|
|
|
|
|
|
|
// validValues = ["hello"]
|
|
|
|
validValues := make([]interface{}, 0)
|
|
|
|
validValues = append(validValues, "hello")
|
|
|
|
// validValuesArray = [["hello"]]
|
|
|
|
validValuesArray := make([]interface{}, 0)
|
|
|
|
validValuesArray = append(validValuesArray, validValues)
|
|
|
|
|
|
|
|
// default value here has type array<array<string>>
|
|
|
|
// config type specified is also array<array<string>>
|
|
|
|
// should succeed
|
2022-10-30 22:42:39 +00:00
|
|
|
arrayType := "array"
|
2022-09-20 18:13:45 +00:00
|
|
|
validConfigWithArray := make(map[string]ProjectConfigType)
|
|
|
|
validConfigWithArray["values"] = ProjectConfigType{
|
2022-10-30 22:42:39 +00:00
|
|
|
Type: &arrayType,
|
2022-09-20 18:13:45 +00:00
|
|
|
Items: &ProjectConfigItemsType{
|
|
|
|
Type: "array",
|
|
|
|
Items: &ProjectConfigItemsType{
|
|
|
|
Type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Default: validValuesArray,
|
|
|
|
}
|
|
|
|
project.Config = validConfigWithArray
|
|
|
|
err = project.Validate()
|
|
|
|
assert.NoError(t, err, "There should be no validation error")
|
|
|
|
}
|
|
|
|
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
func writeAndLoad(t *testing.T, str string) (*Project, error) {
|
|
|
|
tmp, err := os.CreateTemp("", "*.json")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
path := tmp.Name()
|
|
|
|
err = os.WriteFile(path, []byte(str), 0o600)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return LoadProject(path)
|
|
|
|
}
|
|
|
|
|
2022-09-02 17:25:45 +00:00
|
|
|
func TestProjectLoadJSON(t *testing.T) {
|
2022-09-02 09:35:14 +00:00
|
|
|
t.Parallel()
|
2022-09-02 17:25:45 +00:00
|
|
|
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
// Test wrong type
|
|
|
|
t.Run("wrong type", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, "\"hello \"")
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "expected project to be an object, was 'string'")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("missing name attribute", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, "{}")
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "project is missing a 'name' attribute")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("bad name", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, "{\"name\": \"\"}")
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "project is missing a non-empty string 'name' attribute")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("missing runtime", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, "{\"name\": \"project\"}")
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "project is missing a 'runtime' attribute")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("multiple errors 1", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, "{\"name\": \"project\", \"runtime\": 4}")
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
// The order can vary here, so we use Contains and not Equals.
|
|
|
|
expected := []string{
|
|
|
|
"3 errors occurred:",
|
|
|
|
"* #/runtime: oneOf failed",
|
|
|
|
"* #/runtime: expected string, but got number",
|
|
|
|
"* #/runtime: expected object, but got number",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, e := range expected {
|
|
|
|
assert.ErrorContains(t, err, e)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("multiple errors, 2", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, "{\"name\": \"project\", \"runtime\": \"test\", \"backend\": 4, \"main\": {}}")
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
// The order can vary here, so we use Contains and not Equals.
|
|
|
|
expected := []string{
|
|
|
|
"2 errors occurred:",
|
|
|
|
"* #/main: expected string or null, but got object",
|
|
|
|
"* #/backend: expected object or null, but got number",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, e := range expected {
|
|
|
|
assert.ErrorContains(t, err, e)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("success", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
proj, err := writeAndLoad(t, "{\"name\": \"project\", \"runtime\": \"test\"}")
|
|
|
|
|
|
|
|
// Assert.
|
2022-09-02 17:25:45 +00:00
|
|
|
assert.NoError(t, err)
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
assert.Equal(t, tokens.PackageName("project"), proj.Name)
|
|
|
|
assert.Equal(t, "test", proj.Runtime.Name())
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("null optionals should work", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
proj, err := writeAndLoad(t, "{\"name\": \"project\", \"runtime\": \"test\", "+
|
|
|
|
"\"description\": null, \"main\": null, \"backend\": null}")
|
|
|
|
|
|
|
|
// Assert.
|
2022-09-02 17:25:45 +00:00
|
|
|
assert.NoError(t, err)
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
assert.Nil(t, proj.Description)
|
|
|
|
assert.Equal(t, "", proj.Main)
|
|
|
|
})
|
|
|
|
}
|
2022-08-25 12:52:17 +00:00
|
|
|
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
func TestProjectLoadJSONInformativeErrors(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-08-25 12:52:17 +00:00
|
|
|
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
t.Run("a missing name attribute", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-08-25 12:52:17 +00:00
|
|
|
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, `{"Name": "project", "runtime": "test"}`)
|
2022-08-25 12:52:17 +00:00
|
|
|
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "project is missing a 'name' attribute")
|
|
|
|
assert.ErrorContains(t, err, "found 'Name' instead")
|
|
|
|
})
|
2022-09-16 16:05:40 +00:00
|
|
|
|
Better error messages for schema validation (#16097)
This PR improves the error messages produced during project schema
validation so that, where possible, we suggest valid attribute names
that the user may have meant to type. For instance, if they provide a
"Name" attribute where we wanted "name" (lowercase "n"), we'll now say
so. Where there is not a close match, we'll enumerate the full list of
valid names to try and guide the user.
Matching is implemented using Levenshtein distances and ignores case.
Some examples of the new functionality:
* `{"Name": ...}` yields `project is missing a 'name' attribute; found
'Name' instead`
* `{..., "rutnime": ...}` yields `project is missing a 'runtime'
attribute; found 'rutnime' instead`
* `{..., "template": {"displayNameDisplayName": ...}, ...}` yields
`'displayNameDisplayName' not allowed; the allowed attributes are
'config', 'description', 'displayName', 'important', 'metadata' and
'quickstart'`
Co-authored-by: Will Jones <will@sacharissa.co.uk>
2024-06-14 09:03:22 +00:00
|
|
|
t.Run("a missing runtime attribute", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, `{"name": "project", "rutnime": "test"}`)
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "project is missing a 'runtime' attribute")
|
|
|
|
assert.ErrorContains(t, err, "found 'rutnime' instead")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a minor spelling mistake in a schema field", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, `{
|
|
|
|
"name": "project",
|
|
|
|
"runtime": "test",
|
|
|
|
"template": {
|
|
|
|
"displatName": "foo"
|
|
|
|
}
|
|
|
|
}`)
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "'displatName' not allowed; did you mean 'displayName'?")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a major spelling mistake in a schema field", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, `{
|
|
|
|
"name": "project",
|
|
|
|
"runtime": "test",
|
|
|
|
"template": {
|
|
|
|
"displayNameDisplayName": "foo"
|
|
|
|
}
|
|
|
|
}`)
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "'displayNameDisplayName' not allowed")
|
|
|
|
assert.ErrorContains(t, err, "'displayNameDisplayName' not allowed; the allowed attributes are "+
|
|
|
|
"'config', 'description', 'displayName', 'important', 'metadata' and 'quickstart'")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("specific errors when only a single attribute is expected", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, `{
|
|
|
|
"name": "project",
|
|
|
|
"runtime": "test",
|
|
|
|
"backend": {
|
|
|
|
"url": "https://pulumi.com",
|
|
|
|
"name": "test"
|
|
|
|
}
|
|
|
|
}`)
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "'name' not allowed")
|
|
|
|
assert.ErrorContains(t, err, "'name' not allowed; the only allowed attribute is 'url'")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a minor spelling mistake even deeper in the schema", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, `{
|
|
|
|
"name": "project",
|
|
|
|
"runtime": "test",
|
|
|
|
"plugins": {
|
|
|
|
"providers": [
|
|
|
|
{
|
|
|
|
"nome": "test"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}`)
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "'nome' not allowed; did you mean 'name'")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("a major spelling mistake even deeper in the schema", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Act.
|
|
|
|
_, err := writeAndLoad(t, `{
|
|
|
|
"name": "project",
|
|
|
|
"runtime": "test",
|
|
|
|
"plugins": {
|
|
|
|
"providers": [
|
|
|
|
{
|
|
|
|
"displayName": "test"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}`)
|
|
|
|
|
|
|
|
// Assert.
|
|
|
|
assert.ErrorContains(t, err, "'displayName' not allowed")
|
|
|
|
assert.ErrorContains(t, err, "'displayName' not allowed; the allowed attributes are "+
|
|
|
|
"'name', 'path' and 'version'")
|
|
|
|
})
|
2022-08-25 12:52:17 +00:00
|
|
|
}
|
2022-09-02 12:13:41 +00:00
|
|
|
|
2022-09-22 08:27:27 +00:00
|
|
|
func deleteFile(t *testing.T, file *os.File) {
|
|
|
|
if file != nil {
|
|
|
|
err := os.Remove(file.Name())
|
|
|
|
assert.NoError(t, err, "Error while deleting file")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadProjectFromText(t *testing.T, content string) (*Project, error) {
|
2023-01-06 22:39:16 +00:00
|
|
|
tmp, err := os.CreateTemp("", "*.yaml")
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
path := tmp.Name()
|
2023-03-03 16:36:39 +00:00
|
|
|
err = os.WriteFile(path, []byte(content), 0o600)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
defer deleteFile(t, tmp)
|
|
|
|
return LoadProject(path)
|
|
|
|
}
|
|
|
|
|
2022-09-22 15:32:18 +00:00
|
|
|
func loadProjectStackFromText(t *testing.T, project *Project, content string) (*ProjectStack, error) {
|
2023-01-06 22:39:16 +00:00
|
|
|
tmp, err := os.CreateTemp("", "*.yaml")
|
2022-09-22 15:32:18 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
path := tmp.Name()
|
2023-03-03 16:36:39 +00:00
|
|
|
err = os.WriteFile(path, []byte(content), 0o600)
|
2022-09-22 15:32:18 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
defer deleteFile(t, tmp)
|
|
|
|
return LoadProjectStack(project, path)
|
|
|
|
}
|
|
|
|
|
2023-11-22 05:04:14 +00:00
|
|
|
func loadProjectStackFromJSONText(t *testing.T, project *Project, content string) (*ProjectStack, error) {
|
|
|
|
tmp, err := os.CreateTemp("", "*.json")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
path := tmp.Name()
|
|
|
|
err = os.WriteFile(path, []byte(content), 0o600)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
defer deleteFile(t, tmp)
|
|
|
|
return LoadProjectStack(project, path)
|
|
|
|
}
|
|
|
|
|
2022-09-22 08:27:27 +00:00
|
|
|
func TestProjectLoadsConfigSchemas(t *testing.T) {
|
2022-09-02 12:13:41 +00:00
|
|
|
t.Parallel()
|
2022-09-22 08:27:27 +00:00
|
|
|
projectContent := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
integerSchemaFull:
|
|
|
|
type: integer
|
|
|
|
description: a very important value
|
|
|
|
default: 1
|
|
|
|
integerSchemaSimple: 20
|
|
|
|
textSchemaFull:
|
|
|
|
type: string
|
|
|
|
default: t3.micro
|
|
|
|
textSchemaSimple: t4.large
|
|
|
|
booleanSchemaFull:
|
|
|
|
type: boolean
|
|
|
|
default: true
|
|
|
|
booleanSchemaSimple: false
|
|
|
|
simpleArrayOfStrings:
|
|
|
|
type: array
|
|
|
|
items:
|
|
|
|
type: string
|
|
|
|
default: [hello]
|
|
|
|
arrayOfArrays:
|
|
|
|
type: array
|
|
|
|
items:
|
|
|
|
type: array
|
|
|
|
items:
|
|
|
|
type: string
|
2022-10-19 08:12:25 +00:00
|
|
|
secretString:
|
|
|
|
type: string
|
|
|
|
secret: true
|
2022-09-22 08:27:27 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectContent)
|
|
|
|
assert.NoError(t, err, "Should be able to load the project")
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.Equal(t, 9, len(project.Config), "There are 9 config type definition")
|
2022-09-22 08:27:27 +00:00
|
|
|
// full integer config schema
|
|
|
|
integerSchemFull, ok := project.Config["integerSchemaFull"]
|
|
|
|
assert.True(t, ok, "should be able to read integerSchemaFull")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "integer", integerSchemFull.TypeName())
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.Equal(t, "a very important value", integerSchemFull.Description)
|
|
|
|
assert.Equal(t, 1, integerSchemFull.Default)
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, integerSchemFull.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.Nil(t, integerSchemFull.Items, "Primtive config type doesn't have an items type")
|
|
|
|
|
|
|
|
integerSchemaSimple, ok := project.Config["integerSchemaSimple"]
|
|
|
|
assert.True(t, ok, "should be able to read integerSchemaSimple")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "", integerSchemaSimple.TypeName(), "not explicitly typed")
|
|
|
|
assert.False(t, integerSchemaSimple.IsExplicitlyTyped())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, integerSchemaSimple.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.Equal(t, 20, integerSchemaSimple.Default, "Default integer value is parsed correctly")
|
|
|
|
|
|
|
|
textSchemaFull, ok := project.Config["textSchemaFull"]
|
|
|
|
assert.True(t, ok, "should be able to read textSchemaFull")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "string", textSchemaFull.TypeName())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, textSchemaFull.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.Equal(t, "t3.micro", textSchemaFull.Default)
|
|
|
|
assert.Equal(t, "", textSchemaFull.Description)
|
|
|
|
|
|
|
|
textSchemaSimple, ok := project.Config["textSchemaSimple"]
|
|
|
|
assert.True(t, ok, "should be able to read textSchemaSimple")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "", textSchemaSimple.TypeName(), "not explicitly typed")
|
|
|
|
assert.False(t, textSchemaSimple.IsExplicitlyTyped())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, textSchemaSimple.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.Equal(t, "t4.large", textSchemaSimple.Default)
|
|
|
|
|
|
|
|
booleanSchemaFull, ok := project.Config["booleanSchemaFull"]
|
|
|
|
assert.True(t, ok, "should be able to read booleanSchemaFull")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "boolean", booleanSchemaFull.TypeName())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, booleanSchemaFull.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.Equal(t, true, booleanSchemaFull.Default)
|
|
|
|
|
|
|
|
booleanSchemaSimple, ok := project.Config["booleanSchemaSimple"]
|
|
|
|
assert.True(t, ok, "should be able to read booleanSchemaSimple")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "", booleanSchemaSimple.TypeName(), "not explicitly typed")
|
|
|
|
assert.False(t, booleanSchemaSimple.IsExplicitlyTyped())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, booleanSchemaSimple.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.Equal(t, false, booleanSchemaSimple.Default)
|
|
|
|
|
|
|
|
simpleArrayOfStrings, ok := project.Config["simpleArrayOfStrings"]
|
|
|
|
assert.True(t, ok, "should be able to read simpleArrayOfStrings")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "array", simpleArrayOfStrings.TypeName())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, simpleArrayOfStrings.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.NotNil(t, simpleArrayOfStrings.Items)
|
|
|
|
assert.Equal(t, "string", simpleArrayOfStrings.Items.Type)
|
|
|
|
arrayValues := simpleArrayOfStrings.Default.([]interface{})
|
|
|
|
assert.Equal(t, "hello", arrayValues[0])
|
|
|
|
|
|
|
|
arrayOfArrays, ok := project.Config["arrayOfArrays"]
|
|
|
|
assert.True(t, ok, "should be able to read arrayOfArrays")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "array", arrayOfArrays.TypeName())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.False(t, arrayOfArrays.Secret)
|
2022-09-22 08:27:27 +00:00
|
|
|
assert.NotNil(t, arrayOfArrays.Items)
|
|
|
|
assert.Equal(t, "array", arrayOfArrays.Items.Type)
|
|
|
|
assert.NotNil(t, arrayOfArrays.Items.Items)
|
|
|
|
assert.Equal(t, "string", arrayOfArrays.Items.Items.Type)
|
2022-10-19 08:12:25 +00:00
|
|
|
|
|
|
|
secretString, ok := project.Config["secretString"]
|
|
|
|
assert.True(t, ok, "should be able to read secretString")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "string", secretString.TypeName())
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.Equal(t, "", secretString.Description)
|
|
|
|
assert.Equal(t, nil, secretString.Default)
|
|
|
|
assert.True(t, secretString.Secret)
|
|
|
|
assert.Nil(t, secretString.Items)
|
2022-09-22 08:27:27 +00:00
|
|
|
}
|
2022-09-02 17:25:45 +00:00
|
|
|
|
2022-09-22 15:32:18 +00:00
|
|
|
func getConfigValue(t *testing.T, stackConfig config.Map, key string) string {
|
|
|
|
parsedKey, err := config.ParseKey(key)
|
|
|
|
assert.NoErrorf(t, err, "There should be no error parsing the config key '%v'", key)
|
|
|
|
configValue, foundValue := stackConfig[parsedKey]
|
|
|
|
assert.Truef(t, foundValue, "Couldn't find a value for config key %v", key)
|
|
|
|
value, valueError := configValue.Value(config.NopDecrypter)
|
|
|
|
assert.NoErrorf(t, valueError, "Error while getting the value for key %v", key)
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2022-10-30 22:42:39 +00:00
|
|
|
func getConfigValueUnmarshalled(t *testing.T, stackConfig config.Map, key string) interface{} {
|
|
|
|
parsedKey, err := config.ParseKey(key)
|
|
|
|
assert.NoErrorf(t, err, "There should be no error parsing the config key '%v'", key)
|
|
|
|
configValue, foundValue := stackConfig[parsedKey]
|
|
|
|
assert.Truef(t, foundValue, "Couldn't find a value for config key %v", key)
|
|
|
|
valueJSON, valueError := configValue.Value(config.NopDecrypter)
|
|
|
|
assert.NoErrorf(t, valueError, "Error while getting the value for key %v", key)
|
|
|
|
var value interface{}
|
|
|
|
err = json.Unmarshal([]byte(valueJSON), &value)
|
|
|
|
assert.NoErrorf(t, err, "Error while unmarshalling value for key %v", key)
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2022-09-22 15:32:18 +00:00
|
|
|
func TestStackConfigIsInheritedFromProjectConfig(t *testing.T) {
|
2022-09-22 16:27:08 +00:00
|
|
|
t.Parallel()
|
2022-09-22 15:32:18 +00:00
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
instanceSize: t3.micro
|
|
|
|
instanceCount: 20
|
|
|
|
protect: true`
|
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
test:instanceSize: t4.large`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-09-22 15:32:18 +00:00
|
|
|
assert.NoError(t, configError, "Config override should be valid")
|
|
|
|
|
|
|
|
assert.Equal(t, 3, len(stack.Config), "Stack config now has three values")
|
|
|
|
// value of instanceSize is overwritten from the stack
|
|
|
|
assert.Equal(t, "t4.large", getConfigValue(t, stack.Config, "test:instanceSize"))
|
|
|
|
// instanceCount and protect are inherited from the project
|
|
|
|
assert.Equal(t, "20", getConfigValue(t, stack.Config, "test:instanceCount"))
|
|
|
|
assert.Equal(t, "true", getConfigValue(t, stack.Config, "test:protect"))
|
|
|
|
}
|
|
|
|
|
2022-09-24 12:51:08 +00:00
|
|
|
func TestNamespacedConfigValuesAreInheritedCorrectly(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
2022-10-04 19:29:42 +00:00
|
|
|
aws:region: us-west-1
|
2022-10-30 22:42:39 +00:00
|
|
|
pulumi:disable-default-providers: ["*"]
|
2022-10-04 19:29:42 +00:00
|
|
|
instanceSize: t3.micro`
|
2022-09-24 12:51:08 +00:00
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
test:instanceSize: t4.large`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-09-24 12:51:08 +00:00
|
|
|
assert.NoError(t, configError, "Config override should be valid")
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, 3, len(stack.Config), "Stack config now has three values")
|
2022-09-24 12:51:08 +00:00
|
|
|
// value of instanceSize is overwritten from the stack
|
|
|
|
assert.Equal(t, "t4.large", getConfigValue(t, stack.Config, "test:instanceSize"))
|
|
|
|
// aws:region is namespaced and is inherited from the project
|
|
|
|
assert.Equal(t, "us-west-1", getConfigValue(t, stack.Config, "aws:region"))
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.Equal(t, "[\"*\"]", getConfigValue(t, stack.Config, "pulumi:disable-default-providers"))
|
|
|
|
assert.Equal(t, []interface{}{"*"}, getConfigValueUnmarshalled(t, stack.Config, "pulumi:disable-default-providers"))
|
2022-09-24 12:51:08 +00:00
|
|
|
}
|
|
|
|
|
2022-09-26 10:30:55 +00:00
|
|
|
func TestLoadingStackConfigWithoutNamespacingTheProject(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
2022-10-04 19:29:42 +00:00
|
|
|
aws:region: us-west-1
|
|
|
|
instanceSize: t3.micro`
|
2022-09-26 10:30:55 +00:00
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
instanceSize: t4.large`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-09-26 10:30:55 +00:00
|
|
|
assert.NoError(t, configError, "Config override should be valid")
|
|
|
|
|
|
|
|
assert.Equal(t, 2, len(stack.Config), "Stack config now has three values")
|
|
|
|
// value of instanceSize is overwritten from the stack
|
|
|
|
assert.Equal(t, "t4.large", getConfigValue(t, stack.Config, "test:instanceSize"))
|
|
|
|
// aws:region is namespaced and is inherited from the project
|
|
|
|
assert.Equal(t, "us-west-1", getConfigValue(t, stack.Config, "aws:region"))
|
|
|
|
}
|
|
|
|
|
2022-10-30 22:42:39 +00:00
|
|
|
func TestUntypedProjectConfigValuesAreNotValidated(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
instanceSize: t3.micro
|
|
|
|
aws:region: us-west-1`
|
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
instanceSize: 9999
|
|
|
|
aws:region: 42`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.NoError(t, configError, "Config override should be valid")
|
|
|
|
assert.Equal(t, 2, len(stack.Config), "Stack config now has three values")
|
|
|
|
// value of instanceSize is overwritten from the stack
|
|
|
|
assert.Equal(t, "9999", getConfigValue(t, stack.Config, "test:instanceSize"))
|
|
|
|
assert.Equal(t, "42", getConfigValue(t, stack.Config, "aws:region"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUntypedProjectConfigValuesWithOnlyDefaultOrOnlyValue(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
instanceSize:
|
|
|
|
default: t3.micro
|
2023-02-10 17:05:21 +00:00
|
|
|
region:
|
2022-10-30 22:42:39 +00:00
|
|
|
value: us-west-1`
|
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
aws:answer: 42`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.NoError(t, configError, "Config override should be valid")
|
|
|
|
assert.Equal(t, 3, len(stack.Config), "Stack config now has three values")
|
|
|
|
// value of instanceSize is overwritten from the stack
|
|
|
|
assert.Equal(t, "t3.micro", getConfigValue(t, stack.Config, "test:instanceSize"))
|
|
|
|
assert.Equal(t, "us-west-1", getConfigValue(t, stack.Config, "test:region"))
|
|
|
|
assert.Equal(t, "42", getConfigValue(t, stack.Config, "aws:answer"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUntypedStackConfigValuesDoNeedProjectDeclaration(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
createVpc: true`
|
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
instanceSize: 42`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.NoError(t, configError, "Config override should be valid")
|
|
|
|
assert.Equal(t, 2, len(stack.Config), "Stack config now has three values")
|
|
|
|
// value of instanceSize is overwritten from the stack
|
|
|
|
assert.Equal(t, "42", getConfigValue(t, stack.Config, "test:instanceSize"))
|
|
|
|
assert.Equal(t, "true", getConfigValue(t, stack.Config, "test:createVpc"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNamespacedProjectConfigShouldNotBeExplicitlyTyped(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
aws:region:
|
|
|
|
type: string
|
|
|
|
value:
|
|
|
|
region: us-west-1`
|
|
|
|
|
|
|
|
_, projectError := loadProjectFromText(t, projectYaml)
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, projectError,
|
2022-10-30 22:42:39 +00:00
|
|
|
"Configuration key 'aws:region' is not namespaced by the project and should not define a type")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProjectConfigCannotHaveBothValueAndDefault(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
instanceSize:
|
|
|
|
type: string
|
|
|
|
default: t3.micro
|
|
|
|
value: t4.large`
|
|
|
|
|
|
|
|
_, projectError := loadProjectFromText(t, projectYaml)
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, projectError,
|
2022-10-30 22:42:39 +00:00
|
|
|
"project config 'instanceSize' cannot have both a 'default' and 'value' attribute")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProjectConfigCannotBeTypedArrayWithoutItems(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
instanceSize:
|
|
|
|
type: array
|
|
|
|
default: [t3.micro, t4.large]`
|
|
|
|
|
|
|
|
_, projectError := loadProjectFromText(t, projectYaml)
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, projectError,
|
2022-10-30 22:42:39 +00:00
|
|
|
"The configuration key 'instanceSize' declares an array "+
|
|
|
|
"but does not specify the underlying type via the 'items' attribute")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNamespacedProjectConfigShouldNotBeProvideDefault(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
aws:region:
|
|
|
|
default: us-west-1`
|
|
|
|
|
|
|
|
_, projectError := loadProjectFromText(t, projectYaml)
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, projectError,
|
2022-10-30 22:42:39 +00:00
|
|
|
"Configuration key 'aws:region' is not namespaced by the project and should not define a default value")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, projectError,
|
2022-10-30 22:42:39 +00:00
|
|
|
"Did you mean to use the 'value' attribute instead of 'default'?")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUntypedProjectConfigObjectValuesPassedDownToStack(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
instanceSize:
|
|
|
|
value:
|
|
|
|
hello: world
|
|
|
|
aws:config:
|
|
|
|
value:
|
|
|
|
region: us-west-1`
|
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
aws:whatever: 42`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-10-30 22:42:39 +00:00
|
|
|
assert.NoError(t, configError, "Config override should be valid")
|
|
|
|
assert.Equal(t, 3, len(stack.Config), "Stack config now has three values")
|
|
|
|
// value of instanceSize is overwritten from the stack
|
|
|
|
assert.Equal(t, "{\"hello\":\"world\"}", getConfigValue(t, stack.Config, "test:instanceSize"))
|
|
|
|
assert.Equal(t, "{\"region\":\"us-west-1\"}", getConfigValue(t, stack.Config, "aws:config"))
|
|
|
|
assert.Equal(t, "42", getConfigValue(t, stack.Config, "aws:whatever"))
|
|
|
|
}
|
|
|
|
|
2022-09-22 15:32:18 +00:00
|
|
|
func TestStackConfigErrorsWhenStackValueIsNotCorrectlyTyped(t *testing.T) {
|
2022-09-22 16:27:08 +00:00
|
|
|
t.Parallel()
|
2022-09-22 15:32:18 +00:00
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
values:
|
|
|
|
type: array
|
2022-10-19 08:12:25 +00:00
|
|
|
items:
|
2022-09-22 15:32:18 +00:00
|
|
|
type: string
|
|
|
|
default: [value]`
|
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
test:values: someValue
|
|
|
|
`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, configError, "Stack 'dev' with configuration key 'values' must be of type 'array<string>'")
|
2022-09-22 15:32:18 +00:00
|
|
|
}
|
|
|
|
|
2022-09-24 12:32:11 +00:00
|
|
|
func TestLoadingConfigIsRewrittenToStackConfigDir(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config: ./some/path`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
assert.Equal(t, "./some/path", project.StackConfigDir, "Stack config dir is read from the config property")
|
|
|
|
assert.Equal(t, 0, len(project.Config), "Config should be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDefningBothConfigAndStackConfigDirErrorsOut(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config: ./some/path
|
|
|
|
stackConfigDir: ./some/other/path`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.Nil(t, project, "Should NOT be able to load the project")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, projectError, "Should not use both config and stackConfigDir")
|
2022-09-24 12:32:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfigObjectAndStackConfigDirSuccessfullyLoadProject(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
stackConfigDir: ./some/other/path
|
|
|
|
config:
|
|
|
|
value: hello
|
|
|
|
`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.Nil(t, projectError, "There is no error")
|
|
|
|
assert.NotNil(t, project, "The project can be loaded correctly")
|
|
|
|
assert.Equal(t, "./some/other/path", project.StackConfigDir)
|
|
|
|
assert.Equal(t, 1, len(project.Config), "there is one config value")
|
|
|
|
}
|
|
|
|
|
2022-09-22 15:32:18 +00:00
|
|
|
func TestStackConfigIntegerTypeIsCorrectlyValidated(t *testing.T) {
|
2022-09-22 16:27:08 +00:00
|
|
|
t.Parallel()
|
2022-09-22 15:32:18 +00:00
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
importantNumber:
|
|
|
|
type: integer
|
|
|
|
`
|
|
|
|
|
|
|
|
projectStackYamlValid := `
|
|
|
|
config:
|
|
|
|
test:importantNumber: 20
|
|
|
|
`
|
|
|
|
|
|
|
|
projectStackYamlInvalid := `
|
|
|
|
config:
|
|
|
|
test:importantNumber: hello
|
|
|
|
`
|
|
|
|
|
2024-04-22 06:37:34 +00:00
|
|
|
ctx := context.Background()
|
2022-09-22 15:32:18 +00:00
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYamlValid)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
ctx,
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-09-22 15:32:18 +00:00
|
|
|
assert.NoError(t, configError, "there should no config type error")
|
|
|
|
|
|
|
|
invalidStackConfig, stackError := loadProjectStackFromText(t, project, projectStackYamlInvalid)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2022-10-19 08:12:25 +00:00
|
|
|
configError = ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
ctx,
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
invalidStackConfig.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2023-12-20 15:54:06 +00:00
|
|
|
assert.ErrorContains(t, configError,
|
2022-09-22 15:32:18 +00:00
|
|
|
"Stack 'dev' with configuration key 'importantNumber' must be of type 'integer'")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStackConfigErrorsWhenMissingStackValueForConfigTypeWithNoDefault(t *testing.T) {
|
2022-09-22 16:27:08 +00:00
|
|
|
t.Parallel()
|
2022-09-22 15:32:18 +00:00
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
values:
|
|
|
|
type: array
|
2022-10-19 08:12:25 +00:00
|
|
|
items:
|
2022-09-22 15:32:18 +00:00
|
|
|
type: string`
|
|
|
|
|
|
|
|
projectStackYaml := ``
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, configError, "Stack 'dev' is missing configuration value 'values'")
|
2022-10-04 19:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestStackConfigErrorsWhenMissingTwoStackValueForConfigTypeWithNoDefault(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
another:
|
|
|
|
type: string
|
|
|
|
values:
|
|
|
|
type: array
|
2022-10-19 08:12:25 +00:00
|
|
|
items:
|
2022-10-04 19:13:43 +00:00
|
|
|
type: string`
|
|
|
|
|
|
|
|
projectStackYaml := ``
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, configError, "Stack 'dev' is missing configuration values 'another' and 'values'")
|
2022-10-04 19:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestStackConfigErrorsWhenMissingMultipleStackValueForConfigTypeWithNoDefault(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
hello:
|
|
|
|
type: integer
|
|
|
|
values:
|
|
|
|
type: array
|
2022-10-19 08:12:25 +00:00
|
|
|
items:
|
2022-10-04 19:13:43 +00:00
|
|
|
type: string
|
|
|
|
world:
|
|
|
|
type: string`
|
|
|
|
|
|
|
|
projectStackYaml := ``
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, configError, "Stack 'dev' is missing configuration values 'hello', 'values' and 'world'")
|
2022-10-04 19:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestStackConfigDoesNotErrorWhenProjectHasNotDefinedConfig(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet`
|
|
|
|
|
|
|
|
projectStackYaml := `
|
|
|
|
config:
|
|
|
|
hello: 21
|
|
|
|
world: 42
|
|
|
|
another: 42`
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
config.NewPanicCrypter(),
|
|
|
|
config.NewPanicCrypter())
|
2022-10-04 19:13:43 +00:00
|
|
|
assert.Nil(t, configError, "there should not be a config type error")
|
2022-09-22 15:32:18 +00:00
|
|
|
}
|
|
|
|
|
2022-10-19 08:12:25 +00:00
|
|
|
func TestStackConfigSecretIsCorrectlyValidated(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
projectYaml := `
|
|
|
|
name: test
|
|
|
|
runtime: dotnet
|
|
|
|
config:
|
|
|
|
importantNumber:
|
|
|
|
type: integer
|
|
|
|
secret: true
|
|
|
|
`
|
|
|
|
|
|
|
|
crypter := config.Base64Crypter
|
|
|
|
encryptedValue, err := crypter.EncryptValue(context.Background(), "20")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
projectStackYamlValid := fmt.Sprintf(`
|
|
|
|
config:
|
|
|
|
test:importantNumber:
|
|
|
|
secure: %s
|
|
|
|
`, encryptedValue)
|
|
|
|
|
|
|
|
projectStackYamlInvalid := `
|
|
|
|
config:
|
|
|
|
test:importantNumber: 20
|
|
|
|
`
|
|
|
|
|
2024-04-22 06:37:34 +00:00
|
|
|
ctx := context.Background()
|
2022-10-19 08:12:25 +00:00
|
|
|
project, projectError := loadProjectFromText(t, projectYaml)
|
|
|
|
assert.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, projectStackYamlValid)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
ctx,
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
stack.Config,
|
|
|
|
crypter,
|
|
|
|
crypter)
|
2022-10-19 08:12:25 +00:00
|
|
|
assert.NoError(t, configError, "there should no config type error")
|
|
|
|
|
|
|
|
invalidStackConfig, stackError := loadProjectStackFromText(t, project, projectStackYamlInvalid)
|
|
|
|
assert.NoError(t, stackError, "Should be able to read the stack")
|
2023-10-10 01:35:39 +00:00
|
|
|
configError = ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
ctx,
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
esc.Value{},
|
|
|
|
invalidStackConfig.Config,
|
|
|
|
crypter,
|
|
|
|
crypter)
|
2023-12-20 15:54:06 +00:00
|
|
|
assert.ErrorContains(t, configError,
|
2022-10-19 08:12:25 +00:00
|
|
|
"Stack 'dev' with configuration key 'importantNumber' must be encrypted as it's secret")
|
|
|
|
}
|
|
|
|
|
2023-10-10 01:35:39 +00:00
|
|
|
//nolint:lll
|
|
|
|
func TestEnvironmentMerge(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYAML := `
|
|
|
|
name: test
|
|
|
|
runtime: nodejs`
|
|
|
|
|
|
|
|
stackYAML := `
|
|
|
|
config:
|
|
|
|
test:boolean: true
|
|
|
|
test:number: 42
|
|
|
|
test:string: foo
|
|
|
|
test:array:
|
|
|
|
- first
|
|
|
|
- second
|
|
|
|
test:object:
|
|
|
|
foo: bar
|
|
|
|
object:
|
|
|
|
baz: 42`
|
|
|
|
|
|
|
|
env := esc.NewValue(map[string]esc.Value{
|
|
|
|
"test:boolean": esc.NewValue(false),
|
|
|
|
"test:number": esc.NewValue(json.Number("42")),
|
|
|
|
"test:string": esc.NewValue("esc"),
|
|
|
|
"test:array": esc.NewValue([]esc.Value{esc.NewValue("second"), esc.NewValue("first")}),
|
|
|
|
"test:secret": esc.NewSecret("hunter2"),
|
|
|
|
"test:object": esc.NewValue(map[string]esc.Value{
|
|
|
|
"boolean": esc.NewValue(true),
|
|
|
|
"number": esc.NewValue(json.Number("42")),
|
|
|
|
"string": esc.NewValue("esc"),
|
|
|
|
"array": esc.NewValue([]esc.Value{esc.NewValue("first"), esc.NewValue("second")}),
|
|
|
|
"object": esc.NewValue(map[string]esc.Value{"foo": esc.NewValue("bar")}),
|
|
|
|
"foo": esc.NewValue("qux"),
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
project, projectError := loadProjectFromText(t, projectYAML)
|
|
|
|
require.NoError(t, projectError, "Shold be able to load the project")
|
|
|
|
stack, stackError := loadProjectStackFromText(t, project, stackYAML)
|
|
|
|
require.NoError(t, stackError, "Should be able to read the stack")
|
|
|
|
|
|
|
|
configError := ValidateStackConfigAndApplyProjectConfig(
|
2024-04-22 06:37:34 +00:00
|
|
|
context.Background(),
|
2023-10-10 01:35:39 +00:00
|
|
|
"dev",
|
|
|
|
project,
|
|
|
|
env,
|
|
|
|
stack.Config,
|
|
|
|
config.Base64Crypter,
|
|
|
|
config.Base64Crypter)
|
|
|
|
require.NoError(t, configError, "there should not be a config type error")
|
|
|
|
|
|
|
|
secureKeys := stack.Config.SecureKeys()
|
|
|
|
assert.Equal(t, []config.Key{config.MustMakeKey("test", "secret")}, secureKeys)
|
|
|
|
|
|
|
|
m, err := stack.Config.Decrypt(config.Base64Crypter)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expected := map[config.Key]string{
|
|
|
|
config.MustMakeKey("test", "array"): "[\"first\",\"second\"]",
|
|
|
|
config.MustMakeKey("test", "boolean"): "true",
|
|
|
|
config.MustMakeKey("test", "number"): "42",
|
|
|
|
config.MustMakeKey("test", "object"): "{\"array\":[\"first\",\"second\"],\"boolean\":true,\"foo\":\"bar\",\"number\":42,\"object\":{\"baz\":42,\"foo\":\"bar\"},\"string\":\"esc\"}",
|
|
|
|
config.MustMakeKey("test", "string"): "foo",
|
|
|
|
config.MustMakeKey("test", "secret"): "hunter2",
|
|
|
|
}
|
|
|
|
assert.Equal(t, expected, m)
|
|
|
|
}
|
|
|
|
|
2022-09-22 08:27:27 +00:00
|
|
|
func TestProjectLoadYAML(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-09-02 12:13:41 +00:00
|
|
|
|
|
|
|
// Test wrong type
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err := loadProjectFromText(t, "\"hello\"")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, "expected project to be an object")
|
2022-09-02 12:13:41 +00:00
|
|
|
|
|
|
|
// Test bad key
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err = loadProjectFromText(t, "4: hello")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, "expected only string keys, got '%!s(int=4)'")
|
2022-09-02 12:13:41 +00:00
|
|
|
|
|
|
|
// Test nested bad key
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err = loadProjectFromText(t, "hello:\n 6: bad")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, "project is missing a 'name' attribute")
|
2022-09-02 12:13:41 +00:00
|
|
|
|
|
|
|
// Test lack of name
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err = loadProjectFromText(t, "{}")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, "project is missing a 'name' attribute")
|
2022-09-02 12:13:41 +00:00
|
|
|
|
|
|
|
// Test bad name
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err = loadProjectFromText(t, "name:")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, "project is missing a non-empty string 'name' attribute")
|
2022-09-02 12:13:41 +00:00
|
|
|
|
|
|
|
// Test missing runtime
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err = loadProjectFromText(t, "name: project")
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, "project is missing a 'runtime' attribute")
|
2022-09-02 12:13:41 +00:00
|
|
|
|
|
|
|
// Test other schema errors
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err = loadProjectFromText(t, "name: project\nruntime: 4")
|
2022-09-02 17:25:45 +00:00
|
|
|
// These can vary in order, so contains not equals check
|
|
|
|
expected := []string{
|
|
|
|
"3 errors occurred:",
|
|
|
|
"* #/runtime: oneOf failed",
|
|
|
|
"* #/runtime: expected string, but got number",
|
2023-03-03 16:36:39 +00:00
|
|
|
"* #/runtime: expected object, but got number",
|
|
|
|
}
|
2022-09-02 17:25:45 +00:00
|
|
|
for _, e := range expected {
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, e)
|
2022-09-02 17:25:45 +00:00
|
|
|
}
|
2022-09-02 12:13:41 +00:00
|
|
|
|
2022-09-22 08:27:27 +00:00
|
|
|
_, err = loadProjectFromText(t, "name: project\nruntime: test\nbackend: 4\nmain: {}")
|
2022-09-02 17:25:45 +00:00
|
|
|
expected = []string{
|
|
|
|
"2 errors occurred:",
|
2022-09-16 15:38:14 +00:00
|
|
|
"* #/main: expected string or null, but got object",
|
2023-03-03 16:36:39 +00:00
|
|
|
"* #/backend: expected object or null, but got number",
|
|
|
|
}
|
2022-09-02 17:25:45 +00:00
|
|
|
for _, e := range expected {
|
2023-12-15 17:45:32 +00:00
|
|
|
assert.ErrorContains(t, err, e)
|
2022-09-02 17:25:45 +00:00
|
|
|
}
|
2022-09-02 12:13:41 +00:00
|
|
|
|
2022-09-02 12:23:32 +00:00
|
|
|
// Test success
|
2022-09-22 08:27:27 +00:00
|
|
|
proj, err := loadProjectFromText(t, "name: project\nruntime: test")
|
2022-09-02 12:23:32 +00:00
|
|
|
assert.NoError(t, err)
|
2022-09-02 17:25:45 +00:00
|
|
|
assert.Equal(t, tokens.PackageName("project"), proj.Name)
|
2022-09-02 12:23:32 +00:00
|
|
|
assert.Equal(t, "test", proj.Runtime.Name())
|
2022-09-16 15:38:14 +00:00
|
|
|
|
|
|
|
// Test null optionals should work
|
2022-09-22 08:27:27 +00:00
|
|
|
proj, err = loadProjectFromText(t, "name: project\nruntime: test\ndescription:\nmain: null\nbackend:\n")
|
2022-09-16 15:38:14 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Nil(t, proj.Description)
|
|
|
|
assert.Equal(t, "", proj.Main)
|
2022-09-02 12:13:41 +00:00
|
|
|
}
|
2023-02-10 17:05:21 +00:00
|
|
|
|
|
|
|
func TestProjectSaveLoadRoundtrip(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
project Project
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Numeric name",
|
|
|
|
project: Project{
|
|
|
|
Name: "1234",
|
|
|
|
Runtime: NewProjectRuntimeInfo("python", nil),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
tmp, err := os.CreateTemp("", "*.yaml")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer deleteFile(t, tmp)
|
|
|
|
|
|
|
|
path := tmp.Name()
|
|
|
|
|
|
|
|
err = tt.project.Save(path)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
loadedProject, err := LoadProject(path)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, loadedProject)
|
|
|
|
|
|
|
|
// Clear the raw data before we compare
|
|
|
|
loadedProject.raw = nil
|
|
|
|
assert.Equal(t, tt.project, *loadedProject)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProjectEditRoundtrip(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
yaml string
|
|
|
|
edit func(*Project)
|
|
|
|
expected string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Change name",
|
|
|
|
yaml: "name: test\nruntime: python\n",
|
|
|
|
edit: func(proj *Project) { proj.Name = "new" },
|
|
|
|
expected: "name: new\nruntime: python\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Add runtime option",
|
|
|
|
yaml: "name: test\nruntime: python\n",
|
|
|
|
edit: func(proj *Project) {
|
|
|
|
proj.Runtime = NewProjectRuntimeInfo(
|
|
|
|
proj.Runtime.Name(),
|
|
|
|
map[string]interface{}{
|
|
|
|
"setting": "test",
|
|
|
|
})
|
|
|
|
},
|
|
|
|
expected: "name: test\nruntime:\n name: python\n options:\n setting: test\n",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
tmp, err := os.CreateTemp("", "*.yaml")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer deleteFile(t, tmp)
|
|
|
|
|
|
|
|
path := tmp.Name()
|
|
|
|
err = os.WriteFile(path, []byte(tt.yaml), 0o600)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
loadedProject, err := LoadProject(path)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, loadedProject)
|
|
|
|
|
|
|
|
tt.edit(loadedProject)
|
|
|
|
err = loadedProject.Save(path)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
actualYaml, err := os.ReadFile(path)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, tt.expected, string(actualYaml))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-11-22 05:04:14 +00:00
|
|
|
|
|
|
|
func TestEnvironmentAppend(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
t.Run("JSON list", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackJSON := "{}"
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromJSONText(t, project, projectStackJSON)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env")
|
|
|
|
marshaled, err := encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "{\n \"environment\": [\n \"env\"\n ]\n}\n", string(marshaled))
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env2")
|
|
|
|
marshaled, err = encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "{\n \"environment\": [\n \"env\",\n \"env2\"\n ]\n}\n", string(marshaled))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("JSON literal", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackJSON := `{
|
|
|
|
"environment": {
|
|
|
|
"values": {
|
|
|
|
"pulumiConfig": {
|
|
|
|
"aws:region": "us-west-2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromJSONText(t, project, projectStackJSON)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expected := `{
|
|
|
|
"environment": {
|
|
|
|
"imports": [
|
|
|
|
"env"
|
|
|
|
],
|
|
|
|
"values": {
|
|
|
|
"pulumiConfig": {
|
|
|
|
"aws:region": "us-west-2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env")
|
|
|
|
marshaled, err := encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
expected = `{
|
|
|
|
"environment": {
|
|
|
|
"imports": [
|
|
|
|
"env",
|
|
|
|
"env2"
|
|
|
|
],
|
|
|
|
"values": {
|
|
|
|
"pulumiConfig": {
|
|
|
|
"aws:region": "us-west-2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env2")
|
|
|
|
marshaled, err = encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("YAML list", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackYaml := ""
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env")
|
|
|
|
marshaled, err := encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "environment:\n - env\n", string(marshaled))
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env2")
|
|
|
|
marshaled, err = encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "environment:\n - env\n - env2\n", string(marshaled))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("YAML literal", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackYaml := `environment:
|
|
|
|
values:
|
|
|
|
pulumiConfig:
|
|
|
|
aws:region: us-west-2`
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expected := `environment:
|
|
|
|
imports:
|
|
|
|
- env
|
|
|
|
values:
|
|
|
|
pulumiConfig:
|
|
|
|
aws:region: us-west-2
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env")
|
|
|
|
marshaled, err := encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
expected = `environment:
|
|
|
|
imports:
|
|
|
|
- env
|
|
|
|
- env2
|
|
|
|
values:
|
|
|
|
pulumiConfig:
|
|
|
|
aws:region: us-west-2
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Append("env2")
|
|
|
|
marshaled, err = encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEnvironmentRemove(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
t.Run("JSON list", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackJSON := `{
|
|
|
|
"environment": [
|
|
|
|
"env2",
|
|
|
|
"env",
|
|
|
|
"env2"
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromJSONText(t, project, projectStackJSON)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expected := `{
|
|
|
|
"environment": [
|
|
|
|
"env2",
|
|
|
|
"env"
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err := encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
expected = `{
|
|
|
|
"environment": [
|
|
|
|
"env2"
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env")
|
|
|
|
marshaled, err = encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err = encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "{}\n", string(marshaled))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("JSON literal", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackJSON := `{
|
|
|
|
"environment": {
|
|
|
|
"imports": [
|
|
|
|
{
|
|
|
|
"env2": {
|
|
|
|
"merge": false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"env",
|
|
|
|
"env2"
|
|
|
|
],
|
|
|
|
"values": {
|
|
|
|
"pulumiConfig": {
|
|
|
|
"aws:region": "us-west-2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromJSONText(t, project, projectStackJSON)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expected := `{
|
|
|
|
"environment": {
|
|
|
|
"imports": [
|
|
|
|
{
|
|
|
|
"env2": {
|
|
|
|
"merge": false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"env"
|
|
|
|
],
|
|
|
|
"values": {
|
|
|
|
"pulumiConfig": {
|
|
|
|
"aws:region": "us-west-2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err := encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
expected = `{
|
|
|
|
"environment": {
|
|
|
|
"imports": [
|
|
|
|
"env"
|
|
|
|
],
|
|
|
|
"values": {
|
|
|
|
"pulumiConfig": {
|
|
|
|
"aws:region": "us-west-2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err = encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
expected = `{
|
|
|
|
"environment": {
|
|
|
|
"imports": [],
|
|
|
|
"values": {
|
|
|
|
"pulumiConfig": {
|
|
|
|
"aws:region": "us-west-2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env")
|
|
|
|
marshaled, err = encoding.JSON.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("YAML list", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackYaml := `environment:
|
|
|
|
- env2
|
|
|
|
- env
|
|
|
|
- env2
|
|
|
|
`
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expected := `environment:
|
|
|
|
- env2
|
|
|
|
- env
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err := encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env")
|
|
|
|
marshaled, err = encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "environment:\n - env2\n", string(marshaled))
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err = encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "{}\n", string(marshaled))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("YAML literal", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
projectYaml := `name: test
|
|
|
|
runtime: yaml`
|
|
|
|
|
|
|
|
projectStackYaml := `environment:
|
|
|
|
imports:
|
|
|
|
- {env2: {merge: false}}
|
|
|
|
- env
|
|
|
|
- env2
|
|
|
|
values:
|
|
|
|
pulumiConfig:
|
|
|
|
aws:region: us-west-2`
|
|
|
|
|
|
|
|
project, err := loadProjectFromText(t, projectYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
stack, err := loadProjectStackFromText(t, project, projectStackYaml)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expected := `environment:
|
|
|
|
imports:
|
|
|
|
- {env2: {merge: false}}
|
|
|
|
- env
|
|
|
|
values:
|
|
|
|
pulumiConfig:
|
|
|
|
aws:region: us-west-2
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err := encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
expected = `environment:
|
|
|
|
imports:
|
|
|
|
- env
|
|
|
|
values:
|
|
|
|
pulumiConfig:
|
|
|
|
aws:region: us-west-2
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env2")
|
|
|
|
marshaled, err = encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
|
|
|
|
expected = `environment:
|
|
|
|
values:
|
|
|
|
pulumiConfig:
|
|
|
|
aws:region: us-west-2
|
|
|
|
`
|
|
|
|
|
|
|
|
stack.Environment = stack.Environment.Remove("env")
|
|
|
|
marshaled, err = encoding.YAML.Marshal(stack)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expected, string(marshaled))
|
|
|
|
})
|
|
|
|
}
|