We're not going to generate language-specific API docs for the Azure NextGen provider, only resource docs. This change makes it so the resource docs do not emit any links to nonexistent API docs.
419 lines
12 KiB
Go
419 lines
12 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/v2/codegen/python"
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
unitTestTool = "Pulumi Resource Docs Unit Test"
|
|
providerPackage = "prov"
|
|
codeFence = "```"
|
|
)
|
|
|
|
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.ComplexTypeSpec{
|
|
// Package-level types.
|
|
"prov:/getPackageResourceOptions:getPackageResourceOptions": {
|
|
ObjectTypeSpec: schema.ObjectTypeSpec{
|
|
Description: "Options object for the package-level function getPackageResource.",
|
|
Type: "object",
|
|
Properties: simpleProperties,
|
|
},
|
|
},
|
|
|
|
// Module-level types.
|
|
"prov:module/getModuleResourceOptions:getModuleResourceOptions": {
|
|
ObjectTypeSpec: schema.ObjectTypeSpec{
|
|
Description: "Options object for the module-level function getModuleResource.",
|
|
Type: "object",
|
|
Properties: simpleProperties,
|
|
},
|
|
},
|
|
"prov:module/ResourceOptions:ResourceOptions": {
|
|
ObjectTypeSpec: schema.ObjectTypeSpec{
|
|
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",
|
|
},
|
|
},
|
|
"recursiveType": {
|
|
Description: "I am a recursive type.",
|
|
Language: pythonMapCase,
|
|
TypeSpec: schema.TypeSpec{
|
|
Ref: "#/types/prov:module/ResourceOptions:ResourceOptions",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"prov:module/ResourceOptions2:ResourceOptions2": {
|
|
ObjectTypeSpec: schema.ObjectTypeSpec{
|
|
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": {
|
|
ObjectTypeSpec: schema.ObjectTypeSpec{
|
|
Description: `This is a module-level resource called Resource.
|
|
{{% examples %}}
|
|
## Example Usage
|
|
|
|
{{% example %}}
|
|
### Basic Example
|
|
|
|
` + codeFence + `typescript
|
|
// Some TypeScript code.
|
|
` + codeFence + `
|
|
` + codeFence + `python
|
|
# Some Python code.
|
|
` + codeFence + `
|
|
{{% /example %}}
|
|
{{% example %}}
|
|
### Custom Sub-Domain Example
|
|
|
|
` + codeFence + `typescript
|
|
// Some typescript code
|
|
` + codeFence + `
|
|
` + codeFence + `python
|
|
# Some Python code.
|
|
` + codeFence + `
|
|
{{% /example %}}
|
|
{{% /examples %}}
|
|
`,
|
|
},
|
|
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",
|
|
},
|
|
},
|
|
"recursiveType": {
|
|
Description: "I am a recursive type.",
|
|
TypeSpec: schema.TypeSpec{
|
|
Ref: "#/types/prov:module/ResourceOptions:ResourceOptions",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"prov:/packageLevelResource:PackageLevelResource": {
|
|
ObjectTypeSpec: schema.ObjectTypeSpec{
|
|
Description: "This is a package-level resource.",
|
|
},
|
|
InputProperties: map[string]schema.PropertySpec{
|
|
"prop": {
|
|
Description: "An input property.",
|
|
TypeSpec: schema.TypeSpec{
|
|
Type: "string",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
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, nil)
|
|
assert.NoError(t, err, "importing spec")
|
|
|
|
modules := generateModulesFromSchemaPackage(unitTestTool, schemaPkg, true)
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func getResourceFromModule(resource string, mod *modContext) *schema.Resource {
|
|
for _, r := range mod.resources {
|
|
if resourceName(r) != resource {
|
|
continue
|
|
}
|
|
return r
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestResourceDocHeader(t *testing.T) {
|
|
initTestPackageSpec(t)
|
|
|
|
schemaPkg, err := schema.ImportSpec(testPackageSpec, nil)
|
|
assert.NoError(t, err, "importing spec")
|
|
|
|
tests := []struct {
|
|
Name string
|
|
ExpectedTitleTag string
|
|
ResourceName string
|
|
ModuleName string
|
|
}{
|
|
{
|
|
Name: "PackageLevelResourceHeader",
|
|
ResourceName: "PackageLevelResource",
|
|
// Empty string indicates the package-level root module.
|
|
ModuleName: "",
|
|
ExpectedTitleTag: "Resource PackageLevelResource | Package prov",
|
|
},
|
|
{
|
|
Name: "ModuleLevelResourceHeader",
|
|
ResourceName: "Resource",
|
|
ModuleName: "module",
|
|
ExpectedTitleTag: "Resource Resource | Module module | Package prov",
|
|
},
|
|
}
|
|
|
|
modules := generateModulesFromSchemaPackage(unitTestTool, schemaPkg, true)
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
mod, ok := modules[test.ModuleName]
|
|
if !ok {
|
|
t.Fatalf("could not find the module %s in modules map", test.ModuleName)
|
|
}
|
|
|
|
r := getResourceFromModule(test.ResourceName, mod)
|
|
if r == nil {
|
|
t.Fatalf("could not find %s in modules", test.ResourceName)
|
|
}
|
|
h := mod.genResourceHeader(r)
|
|
assert.Equal(t, test.ExpectedTitleTag, h.TitleTag)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExamplesProcessing(t *testing.T) {
|
|
initTestPackageSpec(t)
|
|
|
|
description := testPackageSpec.Resources["prov:module/resource:Resource"].Description
|
|
docInfo := decomposeDocstring(description)
|
|
examplesSection := docInfo.examples
|
|
|
|
// The resource under test has two examples and both have TS and Python examples.
|
|
assert.Equal(t, 2, len(examplesSection))
|
|
assert.Equal(t, "### Basic Example", examplesSection[0].Title)
|
|
assert.Equal(t, "### Custom Sub-Domain Example", examplesSection[1].Title)
|
|
expectedLangSnippets := []string{"typescript", "python"}
|
|
otherLangSnippets := []string{"csharp", "go"}
|
|
for _, e := range examplesSection {
|
|
for _, lang := range expectedLangSnippets {
|
|
_, ok := e.Snippets[lang]
|
|
assert.True(t, ok, "Could not find %s snippet", lang)
|
|
}
|
|
for _, lang := range otherLangSnippets {
|
|
snippet, ok := e.Snippets[lang]
|
|
assert.True(t, ok, "Expected to find default placeholders for other languages")
|
|
assert.Contains(t, "Coming soon!", snippet)
|
|
}
|
|
}
|
|
}
|