Files
kustomize/kyaml/openapi/openapi_test.go
Karl Isenberg 43868688d5 Use require for Error and NoError
Assert keeps going after failure, but require immediately fails
the tests, making it easier to find the output related to the test
failure, rather than having to comb through a bunch of subsequent
assertion failures. For equality tests, we may or may not want to
continue, but for error checks we almost always want to immediately
fail the test. Exceptions can be changed as-needed.
2024-03-20 13:19:18 -07:00

396 lines
9.0 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package openapi
import (
"fmt"
"os"
"sync"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestAddSchema(t *testing.T) {
// reset package vars
globalSchema = openapiData{}
err := AddSchema(additionalSchema)
if !assert.NoError(t, err) {
t.FailNow()
}
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.config.setters.replicas"}`, Schema())
if !assert.Greater(t, len(globalSchema.schema.Definitions), 200) {
t.FailNow()
}
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Equal(t, `map[x-kustomize:map[setBy:Jane setter:map[name:replicas value:5]]]`,
fmt.Sprintf("%v", s.Schema.Extensions))
}
var additionalSchema = []byte(`
{
"definitions": {
"io.k8s.config.setters.replicas": {
"description": "replicas description.",
"type": "integer",
"x-kustomize": {"setBy":"Jane","setter": {"name":"replicas","value":"5"}}
}
},
"invalid": "field"
}
`)
func TestSchemaForResourceType(t *testing.T) {
// reset package vars
globalSchema = openapiData{}
s := SchemaForResourceType(
yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
if !assert.NotNil(t, s) {
t.FailNow()
}
f := s.Field("spec")
if !assert.NotNil(t, f) {
t.FailNow()
}
if !assert.Equal(t, "DeploymentSpec is the specification of the desired behavior of the Deployment.",
f.Schema.Description) {
t.FailNow()
}
replicas := f.Field("replicas")
if !assert.NotNil(t, replicas) {
t.FailNow()
}
if !assert.Equal(t, "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.",
replicas.Schema.Description) {
t.FailNow()
}
temp := f.Field("template")
if !assert.NotNil(t, temp) {
t.FailNow()
}
if !assert.Equal(t, "PodTemplateSpec describes the data a pod should have when created from a template",
temp.Schema.Description) {
t.FailNow()
}
containers := temp.Field("spec").Field("containers").Elements()
if !assert.NotNil(t, containers) {
t.FailNow()
}
targetPort := containers.Field("ports").Elements().Field("containerPort")
if !assert.NotNil(t, targetPort) {
t.FailNow()
}
if !assert.Equal(t, "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.",
targetPort.Schema.Description) {
t.FailNow()
}
arg := containers.Field("args").Elements()
if !assert.NotNil(t, arg) {
t.FailNow()
}
if !assert.Equal(t, "string", arg.Schema.Type[0]) {
t.FailNow()
}
}
func TestSchemaFromFile(t *testing.T) {
ResetOpenAPI()
inputyaml := `
openAPI:
definitions:
io.k8s.cli.setters.image-name:
x-k8s-cli:
setter:
name: image-name
value: "nginx"
`
f, err := os.CreateTemp("", "openapi-")
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, os.WriteFile(f.Name(), []byte(inputyaml), 0o600)) {
t.FailNow()
}
sc, err := SchemaFromFile(f.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.setters.image-name"}`, sc)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Greater(t, len(sc.Definitions), 0) {
t.FailNow()
}
assert.Equal(t, `map[x-k8s-cli:map[setter:map[name:image-name value:nginx]]]`,
fmt.Sprintf("%v", s.Schema.Extensions))
}
func TestPopulateDefsInOpenAPI_Substitution(t *testing.T) {
ResetOpenAPI()
inputyaml := `
openAPI:
definitions:
io.k8s.cli.setters.image-name:
x-k8s-cli:
setter:
name: image-name
value: "nginx"
io.k8s.cli.setters.image-tag:
x-k8s-cli:
setter:
name: image-tag
value: "1.8.1"
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE_NAME:IMAGE_TAG
values:
- marker: "IMAGE_NAME"
ref: "#/definitions/io.k8s.cli.setters.image-name"
- marker: "IMAGE_TAG"
ref: "#/definitions/io.k8s.cli.setters.image-tag"
`
f, err := os.CreateTemp("", "openapi-")
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, os.WriteFile(f.Name(), []byte(inputyaml), 0o600)) {
t.FailNow()
}
sc, err := SchemaFromFile(f.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.substitutions.image"}`, sc)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, len(sc.Definitions), 3) {
t.FailNow()
}
assert.Equal(t,
`map[x-k8s-cli:map[substitution:map[name:image pattern:IMAGE_NAME:IMAGE_TAG`+
` values:[map[marker:IMAGE_NAME ref:#/definitions/io.k8s.cli.setters.image-name]`+
` map[marker:IMAGE_TAG ref:#/definitions/io.k8s.cli.setters.image-tag]]]]]`,
fmt.Sprintf("%v", s.Schema.Extensions))
}
func TestAddSchemaFromFile_empty(t *testing.T) {
ResetOpenAPI()
inputyaml := `
kind: Example
`
f, err := os.CreateTemp("", "openapi-")
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, os.WriteFile(f.Name(), []byte(inputyaml), 0o600)) {
t.FailNow()
}
sc, err := SchemaFromFile(f.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Nil(t, sc) {
t.FailNow()
}
}
func TestIsNamespaceScoped_builtin(t *testing.T) {
testCases := []struct {
name string
typeMeta yaml.TypeMeta
expectIsFound bool
expectIsNamespaced bool
}{
{
name: "namespacescoped resource",
typeMeta: yaml.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
expectIsFound: true,
expectIsNamespaced: true,
},
{
name: "clusterscoped resource",
typeMeta: yaml.TypeMeta{
APIVersion: "v1",
Kind: "Namespace",
},
expectIsFound: true,
expectIsNamespaced: false,
},
{
name: "unknown resource",
typeMeta: yaml.TypeMeta{
APIVersion: "custom.io/v1",
Kind: "Custom",
},
expectIsFound: false,
},
}
for i := range testCases {
test := testCases[i]
t.Run(test.name, func(t *testing.T) {
ResetOpenAPI()
isNamespaceable, isFound := IsNamespaceScoped(test.typeMeta)
if !test.expectIsFound {
assert.False(t, isFound)
return
}
assert.True(t, isFound)
assert.Equal(t, test.expectIsNamespaced, isNamespaceable)
})
}
}
// TestIsNamespaceScopedPrecompute checks that the precomputed result meets the actual result
func TestIsNamespaceScopedPrecompute(t *testing.T) {
initSchema()
if diff := cmp.Diff(globalSchema.namespaceabilityByResourceType, precomputedIsNamespaceScoped); diff != "" {
t.Fatalf(diff)
}
}
func TestIsNamespaceScoped_custom(t *testing.T) {
SuppressBuiltInSchemaUse()
err := AddSchema([]byte(`
{
"definitions": {},
"paths": {
"/apis/custom.io/v1/namespaces/{namespace}/customs/{name}": {
"get": {
"x-kubernetes-action": "get",
"x-kubernetes-group-version-kind": {
"group": "custom.io",
"kind": "Custom",
"version": "v1"
}
}
},
"/apis/custom.io/v1/clustercustoms": {
"get": {
"x-kubernetes-action": "get",
"x-kubernetes-group-version-kind": {
"group": "custom.io",
"kind": "ClusterCustom",
"version": "v1"
}
}
}
}
}
`))
require.NoError(t, err)
isNamespaceable, isFound := IsNamespaceScoped(yaml.TypeMeta{
APIVersion: "custom.io/v1",
Kind: "ClusterCustom",
})
assert.True(t, isFound)
assert.False(t, isNamespaceable)
isNamespaceable, isFound = IsNamespaceScoped(yaml.TypeMeta{
APIVersion: "custom.io/v1",
Kind: "Custom",
})
assert.True(t, isFound)
assert.True(t, isNamespaceable)
}
func TestCanSetAndResetSchemaConcurrently(t *testing.T) {
t.Run("SetSchema doesn't cause a data race when called concurrently", func(t *testing.T) {
set := func(wg *sync.WaitGroup) {
defer wg.Done()
err := SetSchema(
map[string]string{
"/apis/custom.io/v1": "true",
},
[]byte(`
{
"definitions": {},
"paths": {
"/apis/custom.io/v1/namespaces/{namespace}/customs/{name}": {
"get": {
"x-kubernetes-action": "get",
"x-kubernetes-group-version-kind": {
"group": "custom.io",
"kind": "Custom",
"version": "v1"
}
}
},
"/apis/custom.io/v1/clustercustoms": {
"get": {
"x-kubernetes-action": "get",
"x-kubernetes-group-version-kind": {
"group": "custom.io",
"kind": "ClusterCustom",
"version": "v1"
}
}
}
}
}
`),
true,
)
require.NoError(t, err)
}
var wg sync.WaitGroup
require.NotPanics(t, func() {
for i := 0; i < 100; i++ {
wg.Add(1)
go set(&wg)
}
})
wg.Wait()
})
t.Run("ResetOpenAPI doesn't cause a data race when called concurrently", func(t *testing.T) {
reset := func(wg *sync.WaitGroup) {
defer wg.Done()
ResetOpenAPI()
}
var wg sync.WaitGroup
require.NotPanics(t, func() {
for i := 0; i < 100; i++ {
wg.Add(1)
go reset(&wg)
}
})
wg.Wait()
})
}