pulumi/pkg/codegen/docs/gen_test.go
2020-03-26 12:42:15 -07:00

275 lines
7.9 KiB
Go

// Copyright 2016-2020, 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.
// Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the
// goconst linter's warning.
//
// nolint: lll, goconst
package docs
import (
"encoding/json"
"strings"
"testing"
"github.com/pulumi/pulumi/pkg/codegen/python"
"github.com/pulumi/pulumi/pkg/codegen/schema"
"github.com/stretchr/testify/assert"
)
const (
unitTestTool = "Pulumi Resource Docs Unit Test"
providerPackage = "prov"
)
var (
simpleProperties = map[string]schema.PropertySpec{
"stringProp": {
Description: "A string prop.",
TypeSpec: schema.TypeSpec{
Type: "string",
},
},
"boolProp": {
Description: "A bool prop.",
TypeSpec: schema.TypeSpec{
Type: "boolean",
},
},
}
// testPackageSpec represents a fake package spec for a Provider used for testing.
testPackageSpec schema.PackageSpec
)
func initTestPackageSpec(t *testing.T) {
t.Helper()
pythonMapCase := map[string]json.RawMessage{
"python": json.RawMessage(`{"mapCase":false}`),
}
testPackageSpec = schema.PackageSpec{
Name: providerPackage,
Description: "A fake provider package used for testing.",
Meta: &schema.MetadataSpec{
ModuleFormat: "(.*)(?:/[^/]*)",
},
Types: map[string]schema.ObjectTypeSpec{
// Package-level types.
"prov:/getPackageResourceOptions:getPackageResourceOptions": {
Description: "Options object for the package-level function getPackageResource.",
Type: "object",
Properties: simpleProperties,
},
// Module-level types.
"prov:module/getModuleResourceOptions:getModuleResourceOptions": {
Description: "Options object for the module-level function getModuleResource.",
Type: "object",
Properties: simpleProperties,
},
"prov:module/ResourceOptions:ResourceOptions": {
Description: "The resource options object.",
Type: "object",
Properties: map[string]schema.PropertySpec{
"stringProp": {
Description: "A string prop.",
Language: pythonMapCase,
TypeSpec: schema.TypeSpec{
Type: "string",
},
},
"boolProp": {
Description: "A bool prop.",
Language: pythonMapCase,
TypeSpec: schema.TypeSpec{
Type: "boolean",
},
},
},
},
"prov:module/ResourceOptions2:ResourceOptions2": {
Description: "The resource options object.",
Type: "object",
Properties: map[string]schema.PropertySpec{
"uniqueProp": {
Description: "This is a property unique to this type.",
Language: pythonMapCase,
TypeSpec: schema.TypeSpec{
Type: "number",
},
},
},
},
},
Resources: map[string]schema.ResourceSpec{
"prov:module/resource:Resource": {
InputProperties: map[string]schema.PropertySpec{
"integerProp": {
Description: "This is integerProp's description.",
TypeSpec: schema.TypeSpec{
Type: "integer",
},
},
"stringProp": {
Description: "This is stringProp's description.",
TypeSpec: schema.TypeSpec{
Type: "string",
},
},
"boolProp": {
Description: "A bool prop.",
TypeSpec: schema.TypeSpec{
Type: "boolean",
},
},
"optionsProp": {
TypeSpec: schema.TypeSpec{
Ref: "#/types/prov:module/ResourceOptions:ResourceOptions",
},
},
"options2Prop": {
TypeSpec: schema.TypeSpec{
Ref: "#/types/prov:module/ResourceOptions2:ResourceOptions2",
},
},
},
},
},
Functions: map[string]schema.FunctionSpec{
// Package-level Functions.
"prov:/getPackageResource:getPackageResource": {
Description: "A package-level function.",
Inputs: &schema.ObjectTypeSpec{
Description: "Inputs for getPackageResource.",
Type: "object",
Properties: map[string]schema.PropertySpec{
"options": {
TypeSpec: schema.TypeSpec{
Ref: "#/types/prov:/getPackageResourceOptions:getPackageResourceOptions",
},
},
},
},
Outputs: &schema.ObjectTypeSpec{
Description: "Outputs for getPackageResource.",
Properties: simpleProperties,
Type: "object",
},
},
// Module-level Functions.
"prov:module/getModuleResource:getModuleResource": {
Description: "A module-level function.",
Inputs: &schema.ObjectTypeSpec{
Description: "Inputs for getModuleResource.",
Type: "object",
Properties: map[string]schema.PropertySpec{
"options": {
TypeSpec: schema.TypeSpec{
Ref: "#/types/prov:module/getModuleResource:getModuleResource",
},
},
},
},
Outputs: &schema.ObjectTypeSpec{
Description: "Outputs for getModuleResource.",
Properties: simpleProperties,
Type: "object",
},
},
},
}
}
// TestResourceNestedPropertyPythonCasing tests that the properties
// of a nested object have the expected casing.
func TestResourceNestedPropertyPythonCasing(t *testing.T) {
initTestPackageSpec(t)
schemaPkg, err := schema.ImportSpec(testPackageSpec)
assert.NoError(t, err, "importing spec")
modules := generateModulesFromSchemaPackage(unitTestTool, schemaPkg)
mod := modules["module"]
for _, r := range mod.resources {
nestedTypes := mod.genNestedTypes(r, true)
if len(nestedTypes) == 0 {
t.Error("did not find any nested types")
return
}
t.Run("InputPropertiesAreSnakeCased", func(t *testing.T) {
props := mod.getProperties(r.InputProperties, "python", true, false)
for _, p := range props {
assert.True(t, strings.Contains(p.Name, "_"), "input property name in python must use snake_case")
}
})
// Non-unique nested properties are ones that have names that occur as direct input properties
// of the resource or elsewhere in the package and are mapped as snake_case even if the property
// itself has a "Language" spec with the `MapCase` value of `false`.
t.Run("NonUniqueNestedProperties", func(t *testing.T) {
n := nestedTypes[0]
assert.Equal(t, "Resource<wbr>Options", n.Name, "got %v instead of Resource<wbr>Options", n.Name)
pyProps := n.Properties["python"]
nestedObject, ok := testPackageSpec.Types["prov:module/ResourceOptions:ResourceOptions"]
if !ok {
t.Error("sample schema package spec does not contain known object type")
return
}
for name := range nestedObject.Properties {
found := false
pyName := python.PyName(name)
for _, prop := range pyProps {
if prop.Name == pyName {
found = true
break
}
}
assert.True(t, found, "expected to find %q", pyName)
}
})
// Unique nested properties are those that only appear inside a nested object and therefore
// are never mapped to their snake_case. Therefore, such properties must be rendered with a
// camelCase.
t.Run("UniqueNestedProperties", func(t *testing.T) {
n := nestedTypes[1]
assert.Equal(t, "Resource<wbr>Options2", n.Name, "got %v instead of Resource<wbr>Options2", n.Name)
pyProps := n.Properties["python"]
nestedObject, ok := testPackageSpec.Types["prov:module/ResourceOptions2:ResourceOptions2"]
if !ok {
t.Error("sample schema package spec does not contain known object type")
return
}
for name := range nestedObject.Properties {
found := false
for _, prop := range pyProps {
if prop.Name == name {
found = true
break
}
}
assert.True(t, found, "expected to find %q", name)
}
})
}
}