Files
opentf/internal/tofu/schemas_test.go
2025-11-20 17:11:07 +02:00

317 lines
8.9 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"context"
"fmt"
"testing"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/providers"
)
// TestResourceTypeConfig checks that ResourceTypeConfig works correctly in all possible combinations:
// * Provider has schema for all types (managed && data && ephemeral)
// * Provider has schema for only 2 types (managed && data or managed && ephemeral or data && ephemeral)
// * Provider has schema for only 1 type (managed or data or ephemeral)
//
// This is due to a check that was in [ResourceTypeConfig] that could have created issues
// but was guarded by an initialisation of the [providers.ProviderSchema.ResourceTypes] during
// proto.GetProviderSchema (both, for protov5 and protov6).
func TestResourceTypeConfig(t *testing.T) {
type resCheck struct {
resourceMode addrs.ResourceMode
resourceType string
exist bool
}
cases := map[string]struct {
schema *Schemas
providerAddr addrs.Provider
checks []resCheck
}{
"all types": {
schema: &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
ResourceTypes: map[string]providers.Schema{
"testmanaged": {Block: &configschema.Block{}},
},
DataSources: map[string]providers.Schema{
"testdata": {Block: &configschema.Block{}},
},
EphemeralResources: map[string]providers.Schema{
"testephemeral": {Block: &configschema.Block{}},
},
},
},
},
providerAddr: addrs.NewDefaultProvider("test"),
checks: []resCheck{
{
resourceMode: addrs.ManagedResourceMode,
resourceType: "testmanaged",
exist: true,
},
{
resourceMode: addrs.DataResourceMode,
resourceType: "testdata",
exist: true,
},
{
resourceMode: addrs.EphemeralResourceMode,
resourceType: "testephemeral",
exist: true,
},
},
},
"only managed and data": {
schema: &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
ResourceTypes: map[string]providers.Schema{
"testmanaged": {Block: &configschema.Block{}},
},
DataSources: map[string]providers.Schema{
"testdata": {Block: &configschema.Block{}},
},
},
},
},
providerAddr: addrs.NewDefaultProvider("test"),
checks: []resCheck{
{
resourceMode: addrs.ManagedResourceMode,
resourceType: "testmanaged",
exist: true,
},
{
resourceMode: addrs.DataResourceMode,
resourceType: "testdata",
exist: true,
},
{
resourceMode: addrs.EphemeralResourceMode,
resourceType: "testephemeral",
exist: false,
},
},
},
"only managed and ephemeral": {
schema: &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
ResourceTypes: map[string]providers.Schema{
"testmanaged": {Block: &configschema.Block{}},
},
EphemeralResources: map[string]providers.Schema{
"testephemeral": {Block: &configschema.Block{}},
},
},
},
},
providerAddr: addrs.NewDefaultProvider("test"),
checks: []resCheck{
{
resourceMode: addrs.ManagedResourceMode,
resourceType: "testmanaged",
exist: true,
},
{
resourceMode: addrs.DataResourceMode,
resourceType: "testdata",
exist: false,
},
{
resourceMode: addrs.EphemeralResourceMode,
resourceType: "testephemeral",
exist: true,
},
},
},
"only data and ephemeral": {
schema: &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
DataSources: map[string]providers.Schema{
"testdata": {Block: &configschema.Block{}},
},
EphemeralResources: map[string]providers.Schema{
"testephemeral": {Block: &configschema.Block{}},
},
},
},
},
providerAddr: addrs.NewDefaultProvider("test"),
checks: []resCheck{
{
resourceMode: addrs.ManagedResourceMode,
resourceType: "testmanaged",
exist: false,
},
{
resourceMode: addrs.DataResourceMode,
resourceType: "testdata",
exist: true,
},
{
resourceMode: addrs.EphemeralResourceMode,
resourceType: "testephemeral",
exist: true,
},
},
},
"only managed": {
schema: &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
ResourceTypes: map[string]providers.Schema{
"testmanaged": {Block: &configschema.Block{}},
},
},
},
},
providerAddr: addrs.NewDefaultProvider("test"),
checks: []resCheck{
{
resourceMode: addrs.ManagedResourceMode,
resourceType: "testmanaged",
exist: true,
},
{
resourceMode: addrs.DataResourceMode,
resourceType: "testdata",
exist: false,
},
{
resourceMode: addrs.EphemeralResourceMode,
resourceType: "testephemeral",
exist: false,
},
},
},
"only data": {
schema: &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
DataSources: map[string]providers.Schema{
"testdata": {Block: &configschema.Block{}},
},
},
},
},
providerAddr: addrs.NewDefaultProvider("test"),
checks: []resCheck{
{
resourceMode: addrs.ManagedResourceMode,
resourceType: "testmanaged",
exist: false,
},
{
resourceMode: addrs.DataResourceMode,
resourceType: "testdata",
exist: true,
},
{
resourceMode: addrs.EphemeralResourceMode,
resourceType: "testephemeral",
exist: false,
},
},
},
"only ephemeral": {
schema: &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
EphemeralResources: map[string]providers.Schema{
"testephemeral": {Block: &configschema.Block{}},
},
},
},
},
providerAddr: addrs.NewDefaultProvider("test"),
checks: []resCheck{
{
resourceMode: addrs.ManagedResourceMode,
resourceType: "testmanaged",
exist: false,
},
{
resourceMode: addrs.DataResourceMode,
resourceType: "testdata",
exist: false,
},
{
resourceMode: addrs.EphemeralResourceMode,
resourceType: "testephemeral",
exist: true,
},
},
},
}
for name, tt := range cases {
t.Run(name, func(t *testing.T) {
for _, check := range tt.checks {
checkTestName := fmt.Sprintf("%s:%s.%s", name, check.resourceMode.String(), check.resourceType)
t.Run(checkTestName, func(t *testing.T) {
b, _ := tt.schema.ResourceTypeConfig(tt.providerAddr, check.resourceMode, check.resourceType)
if b == nil && check.exist {
t.Fatalf("expected to have a schema for resource mode %q and type %q for the provider %q but got nothing. schema:\n%+v", check.resourceMode, check.resourceType, tt.providerAddr, tt.schema)
} else if b != nil && !check.exist {
t.Fatalf("expected to have no schema for resource mode %q and type %q for the provider %q but got one. schema:\n%+v", check.resourceMode, check.resourceType, tt.providerAddr, tt.schema)
}
})
}
})
}
}
func simpleTestSchemas() *Schemas {
provider := simpleMockProvider()
provisioner := simpleMockProvisioner()
return &Schemas{
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): provider.GetProviderSchema(context.TODO()),
},
Provisioners: map[string]*configschema.Block{
"test": provisioner.GetSchemaResponse.Provisioner,
},
}
}
// schemaOnlyProvidersForTesting is a testing helper that constructs a
// plugin library that contains a set of providers that only know how to
// return schema, and will exhibit undefined behavior if used for any other
// purpose.
//
// The intended use for this is in testing components that use schemas to
// drive other behavior, such as reference analysis during graph construction,
// but that don't actually need to interact with providers otherwise.
func schemaOnlyProvidersForTesting(schemas map[addrs.Provider]providers.ProviderSchema, t *testing.T) *contextPlugins {
factories := make(map[addrs.Provider]providers.Factory, len(schemas))
for providerAddr, schema := range schemas {
schema := schema
// mark ephemeral resources blocks accordingly
for _, s := range schema.EphemeralResources {
s.Block.Ephemeral = true
}
provider := &MockProvider{
GetProviderSchemaResponse: &schema,
}
factories[providerAddr] = func() (providers.Interface, error) {
return provider, nil
}
}
return newContextPlugins(factories, nil)
}