From 5aab874d7fcc64ec6b19cb2405f66539eeefeda3 Mon Sep 17 00:00:00 2001 From: Myroslav Vivcharyk Date: Thu, 30 Jan 2025 11:31:59 +0100 Subject: [PATCH] chore(testing): added TF templates for tests --- internal/acctest/template.go | 223 ------------------ internal/acctest/template/builder.go | 128 ++++++++++ internal/acctest/template/generator.go | 186 +++++++++++++++ internal/acctest/template/registry.go | 121 ++++++++++ internal/acctest/template/template.go | 67 ++++++ internal/acctest/template/template_set.go | 109 +++++++++ .../acctest/{ => template}/template_test.go | 16 +- .../acctest/template/usage_example_test.go | 62 +++++ .../service/kafka/kafka_quota_test.go | 5 +- 9 files changed, 684 insertions(+), 233 deletions(-) delete mode 100644 internal/acctest/template.go create mode 100644 internal/acctest/template/builder.go create mode 100644 internal/acctest/template/generator.go create mode 100644 internal/acctest/template/registry.go create mode 100644 internal/acctest/template/template.go create mode 100644 internal/acctest/template/template_set.go rename internal/acctest/{ => template}/template_test.go (97%) create mode 100644 internal/acctest/template/usage_example_test.go diff --git a/internal/acctest/template.go b/internal/acctest/template.go deleted file mode 100644 index 1cca67355..000000000 --- a/internal/acctest/template.go +++ /dev/null @@ -1,223 +0,0 @@ -package acctest - -import ( - "bytes" - "fmt" - "html/template" - "sort" - "strings" - "testing" -) - -// ResourceConfig is the interface that all resource configs must implement -type resourceConfig interface { - // ToMap converts the config to a map for template rendering - ToMap() map[string]any -} - -// Template represents a single Terraform configuration template -type Template struct { - Name string - Template string -} - -// TemplateRegistry holds templates for a specific resource type -type TemplateRegistry struct { - resourceName string - templates map[string]*template.Template - funcMap template.FuncMap -} - -// NewTemplateRegistry creates a new template registry for a resource -func NewTemplateRegistry(resourceName string) *TemplateRegistry { - return &TemplateRegistry{ - resourceName: resourceName, - templates: make(map[string]*template.Template), - funcMap: make(template.FuncMap), - } -} - -// AddTemplate adds a new template to the registry -func (r *TemplateRegistry) AddTemplate(t testing.TB, name, templateStr string) error { - t.Helper() - - tmpl := template.New(name) - if len(r.funcMap) > 0 { - tmpl = tmpl.Funcs(r.funcMap) - } - - parsed, err := tmpl.Parse(templateStr) - if err != nil { - return fmt.Errorf("failed to parse template: %w", err) - } - r.templates[name] = parsed - - return nil -} - -// MustAddTemplate is like AddTemplate but panics on error -func (r *TemplateRegistry) MustAddTemplate(t testing.TB, name, templateStr string) { - t.Helper() - - if err := r.AddTemplate(t, name, templateStr); err != nil { - t.Fatal(err) - } -} - -// Render renders a template with the given config -func (r *TemplateRegistry) Render(t testing.TB, templateKey string, cfg map[string]any) (string, error) { - t.Helper() - - tmpl, exists := r.templates[templateKey] - if !exists { - availableTemplates := r.getAvailableTemplates() - - return "", fmt.Errorf("template %q does not exist for resource %s. Available templates: %v", - templateKey, - r.resourceName, - availableTemplates, - ) - } - - var buf bytes.Buffer - if err := tmpl.Execute(&buf, cfg); err != nil { - return "", fmt.Errorf("failed to render template: %w", err) - } - - return buf.String(), nil -} - -// MustRender is like Render but fails the test on error -func (r *TemplateRegistry) MustRender(t testing.TB, templateKey string, cfg map[string]any) string { - t.Helper() - - result, err := r.Render(t, templateKey, cfg) - if err != nil { - t.Fatal(err) - } - - return result -} - -// AddFunction adds a custom function to the template registry -func (r *TemplateRegistry) AddFunction(name string, fn interface{}) { - if r.funcMap == nil { - r.funcMap = make(template.FuncMap) - } - r.funcMap[name] = fn -} - -// HasTemplate checks if a template exists in the registry -func (r *TemplateRegistry) HasTemplate(key string) bool { - _, exists := r.templates[key] - return exists -} - -// RemoveTemplate removes a template from the registry -func (r *TemplateRegistry) RemoveTemplate(key string) { - delete(r.templates, key) -} - -// getAvailableTemplates returns a sorted list of available template keys -func (r *TemplateRegistry) getAvailableTemplates() []string { - templates := make([]string, 0, len(r.templates)) - for k := range r.templates { - templates = append(templates, k) - } - sort.Strings(templates) - - return templates -} - -// compositionEntry represents a combination of template and its config -type compositionEntry struct { - TemplateKey string - Config map[string]any -} - -// CompositionBuilder helps build complex compositions of templates -type CompositionBuilder struct { - registry *TemplateRegistry - compositions []compositionEntry -} - -// NewCompositionBuilder creates a new composition builder -func (r *TemplateRegistry) NewCompositionBuilder() *CompositionBuilder { - return &CompositionBuilder{ - registry: r, - compositions: make([]compositionEntry, 0), - } -} - -// Add adds a new template and config to the composition -func (b *CompositionBuilder) Add(templateKey string, cfg map[string]any) *CompositionBuilder { - b.compositions = append(b.compositions, compositionEntry{ - TemplateKey: templateKey, - Config: cfg, - }) - return b -} - -// AddWithConfig adds a new template and config to the composition using a resourceConfig -func (b *CompositionBuilder) AddWithConfig(templateKey string, cfg resourceConfig) *CompositionBuilder { - b.compositions = append(b.compositions, compositionEntry{ - TemplateKey: templateKey, - Config: cfg.ToMap(), - }) - return b -} - -// AddIf conditional method to CompositionBuilder -func (b *CompositionBuilder) AddIf(condition bool, templateKey string, cfg map[string]any) *CompositionBuilder { - if condition { - return b.Add(templateKey, cfg) - } - - return b -} - -func (b *CompositionBuilder) Remove(templateKey string) *CompositionBuilder { - var newCompositions []compositionEntry - for _, comp := range b.compositions { - if comp.TemplateKey != templateKey { - newCompositions = append(newCompositions, comp) - } - } - b.compositions = newCompositions - - return b -} - -// Render renders all templates in the composition and combines them -func (b *CompositionBuilder) Render(t testing.TB) (string, error) { - t.Helper() - - var renderedParts = make([]string, 0, len(b.compositions)) - - // Render each template - for _, comp := range b.compositions { - rendered, err := b.registry.Render(t, comp.TemplateKey, comp.Config) - if err != nil { - return "", fmt.Errorf("failed to render template %s: %w", comp.TemplateKey, err) - } - renderedParts = append(renderedParts, rendered) - } - - // Combine all rendered parts - combined := strings.Join(renderedParts, "\n\n") - - //TODO: add HCL validation? - - return combined, nil -} - -// MustRender is like Render but fails the test on error -func (b *CompositionBuilder) MustRender(t testing.TB) string { - t.Helper() - - result, err := b.Render(t) - if err != nil { - t.Fatal(err) - } - return result -} diff --git a/internal/acctest/template/builder.go b/internal/acctest/template/builder.go new file mode 100644 index 000000000..9643d2bab --- /dev/null +++ b/internal/acctest/template/builder.go @@ -0,0 +1,128 @@ +package template + +import ( + "fmt" + "strings" + "testing" +) + +// CompositionBuilder helps build complex compositions of templates +type CompositionBuilder struct { + registry *Registry + compositions []compositionEntry +} + +// Add adds a new template and config to the composition +func (b *CompositionBuilder) Add(templateKey string, cfg map[string]any) *CompositionBuilder { + b.compositions = append(b.compositions, compositionEntry{ + TemplateKey: templateKey, + Config: cfg, + ResourceType: templateKey, // For custom templates, use template key as resource type + }) + return b +} + +// AddResource helper methods to explicitly add resources or data sources +func (b *CompositionBuilder) AddResource(resourceType string, cfg map[string]any) *CompositionBuilder { + b.compositions = append(b.compositions, compositionEntry{ + TemplateKey: templateKey(resourceType, ResourceKindResource), + Config: cfg, + ResourceType: resourceType, + ResourceKind: ResourceKindResource, + }) + return b +} + +func (b *CompositionBuilder) AddDataSource(resourceType string, cfg map[string]any) *CompositionBuilder { + b.compositions = append(b.compositions, compositionEntry{ + TemplateKey: templateKey(resourceType, ResourceKindDataSource), + Config: cfg, + ResourceType: resourceType, + ResourceKind: ResourceKindDataSource, + }) + return b +} + +// AddWithConfig adds a new template and config to the composition using a resourceConfig +func (b *CompositionBuilder) AddWithConfig(templateKey string, cfg resourceConfig) *CompositionBuilder { + b.compositions = append(b.compositions, compositionEntry{ + TemplateKey: templateKey, + Config: cfg.ToMap(), + }) + return b +} + +// AddIf conditional method to CompositionBuilder +func (b *CompositionBuilder) AddIf(condition bool, templateKey string, cfg map[string]any) *CompositionBuilder { + if condition { + return b.Add(templateKey, cfg) + } + + return b +} + +// todo: fix +func (b *CompositionBuilder) Remove(templateKey string) *CompositionBuilder { + var newCompositions []compositionEntry + for _, comp := range b.compositions { + if comp.TemplateKey != templateKey { + newCompositions = append(newCompositions, comp) + } + } + b.compositions = newCompositions + + return b +} + +// Render renders all templates in the composition and combines them +func (b *CompositionBuilder) Render(t testing.TB) (string, error) { + t.Helper() + + var renderedParts = make([]string, 0, len(b.compositions)) + + // Render each template + for _, comp := range b.compositions { + rendered, err := b.registry.Render(t, comp.TemplateKey, comp.Config) + if err != nil { + return "", fmt.Errorf("failed to render template %s: %w", comp.TemplateKey, err) + } + renderedParts = append(renderedParts, rendered) + } + + // Combine all rendered parts + combined := strings.Join(renderedParts, "\n\n") + + //TODO: add HCL validation? + + return combined, nil +} + +func (b *CompositionBuilder) RemoveByResourceName(name string) *CompositionBuilder { + var newCompositions []compositionEntry + for _, comp := range b.compositions { + if resName, ok := comp.Config["resource_name"]; !ok || resName != name { + newCompositions = append(newCompositions, comp) + } + } + b.compositions = newCompositions + return b +} + +// MustRender is like Render but fails the test on error +func (b *CompositionBuilder) MustRender(t testing.TB) string { + t.Helper() + + result, err := b.Render(t) + if err != nil { + t.Fatal(err) + } + return result +} + +// compositionEntry represents a combination of template and its config +type compositionEntry struct { + TemplateKey string + Config map[string]any + ResourceType string // Add this to track the resource type + ResourceKind ResourceKind +} diff --git a/internal/acctest/template/generator.go b/internal/acctest/template/generator.go new file mode 100644 index 000000000..9fc28f57c --- /dev/null +++ b/internal/acctest/template/generator.go @@ -0,0 +1,186 @@ +package template + +import ( + "fmt" + "html/template" + "sort" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ResourceKind represents the type of terraform configuration item +type ResourceKind int + +const ( + ResourceKindResource ResourceKind = iota + ResourceKindDataSource +) + +// String returns the string representation of ResourceKind +func (k ResourceKind) String() string { + switch k { + case ResourceKindResource: + return "resource" + case ResourceKindDataSource: + return "data" + default: + return "unknown" + } +} + +// SchemaTemplateGenerator generates test templates from Terraform schemas +type SchemaTemplateGenerator struct { + funcMap template.FuncMap +} + +// NewSchemaTemplateGenerator creates a new template generator for a specific resource type +func NewSchemaTemplateGenerator() *SchemaTemplateGenerator { + return &SchemaTemplateGenerator{ + funcMap: template.FuncMap{ + "required": func(v any) any { + if v == nil { + panic("required field is missing") + } + return v + }, + "renderValue": func(v interface{}) template.HTML { + var result string + switch val := v.(type) { + case TemplateValue: + if val.IsLiteral { + result = fmt.Sprintf("%q", val.Value) + } else { + result = val.Value + } + case string: + result = fmt.Sprintf("%q", val) + case int, int64, float64: + result = fmt.Sprintf("%v", val) + case bool: + result = fmt.Sprintf("%v", val) + default: + result = fmt.Sprintf("%q", val) + } + return template.HTML(result) + }, + }, + } +} + +// GenerateTemplate generates a template for either a resource or data source +func (g *SchemaTemplateGenerator) GenerateTemplate(r *schema.Resource, resourceType string, kind ResourceKind) string { + var b strings.Builder + + // Header differs based on kind + _, _ = fmt.Fprintf(&b, "%s %q %q {\n", kind, resourceType, "{{ required .resource_name }}") + + g.generateFields(&b, r.Schema, 1) + + b.WriteString("}") + + return b.String() +} + +func (g *SchemaTemplateGenerator) generateFields(b *strings.Builder, s map[string]*schema.Schema, indent int) { + // Create indent string once + indentStr := strings.Repeat(" ", indent) + + // Collect fields by type + var ( + required []string + optional []string + lists []string + maps []string + ) + + for k, field := range s { + if field.Computed && !field.Optional && !field.Required { + continue + } + + switch field.Type { + case schema.TypeList, schema.TypeSet: + lists = append(lists, k) + case schema.TypeMap: + maps = append(maps, k) + default: + if field.Required { + required = append(required, k) + } else { + optional = append(optional, k) + } + } + } + + // Sort all field groups for consistent ordering + sort.Strings(required) + sort.Strings(optional) + sort.Strings(lists) + sort.Strings(maps) + + // Process fields in specified order + for _, field := range required { + g.generateField(b, field, s[field], indentStr) + } + + for _, field := range optional { + g.generateField(b, field, s[field], indentStr) + } + + for _, field := range lists { + g.generateField(b, field, s[field], indentStr) + } + + for _, field := range maps { + g.generateField(b, field, s[field], indentStr) + } +} + +func (g *SchemaTemplateGenerator) generateField(b *strings.Builder, field string, schemaField *schema.Schema, indent string) { + switch schemaField.Type { + case schema.TypeString: + if schemaField.Required { + fmt.Fprintf(b, "%s%s = {{ renderValue (required .%s) }}\n", indent, field, field) + } else { + fmt.Fprintf(b, "%s{{- if .%s }}\n", indent, field) + fmt.Fprintf(b, "%s%s = {{ renderValue .%s }}\n", indent, field, field) + fmt.Fprintf(b, "%s{{- end }}\n", indent) + } + + case schema.TypeInt, schema.TypeFloat: + if schemaField.Required { + fmt.Fprintf(b, "%s%s = {{ required .%s }}\n", indent, field, field) + } else { + fmt.Fprintf(b, "%s{{- if ne .%s nil }}\n", indent, field) + fmt.Fprintf(b, "%s%s = {{ .%s }}\n", indent, field, field) + fmt.Fprintf(b, "%s{{- end }}\n", indent) + } + + case schema.TypeMap: + if schemaField.Required { + fmt.Fprintf(b, "%s%s = {\n", indent, field) + fmt.Fprintf(b, "%s {{- range $k, $v := (required .%s) }}\n", indent, field) + fmt.Fprintf(b, "%s {{ renderValue $k }} = {{ renderValue $v }}\n", indent) + fmt.Fprintf(b, "%s {{- end }}\n", indent) + fmt.Fprintf(b, "%s}\n", indent) + } else { + fmt.Fprintf(b, "%s{{- if .%s }}\n", indent, field) + fmt.Fprintf(b, "%s%s = {\n", indent, field) + fmt.Fprintf(b, "%s {{- range $k, $v := .%s }}\n", indent, field) + fmt.Fprintf(b, "%s {{ renderValue $k }} = {{ renderValue $v }}\n", indent) + fmt.Fprintf(b, "%s {{- end }}\n", indent) + fmt.Fprintf(b, "%s}\n", indent) + fmt.Fprintf(b, "%s{{- end }}\n", indent) + } + + default: + if schemaField.Required { + fmt.Fprintf(b, "%s%s = {{ renderValue (required .%s) }}\n", indent, field, field) + } else { + fmt.Fprintf(b, "%s{{- if .%s }}\n", indent, field) + fmt.Fprintf(b, "%s%s = {{ renderValue .%s }}\n", indent, field, field) + fmt.Fprintf(b, "%s{{- end }}\n", indent) + } + } +} diff --git a/internal/acctest/template/registry.go b/internal/acctest/template/registry.go new file mode 100644 index 000000000..c5f83dd31 --- /dev/null +++ b/internal/acctest/template/registry.go @@ -0,0 +1,121 @@ +package template + +import ( + "bytes" + "fmt" + "html/template" + "sort" + "testing" +) + +// Registry holds templates for a specific resource type +type Registry struct { + templates map[string]*template.Template + funcMap template.FuncMap +} + +// NewTemplateRegistry creates a new template registry for a resource +func NewTemplateRegistry() *Registry { + return &Registry{ + templates: make(map[string]*template.Template), + funcMap: make(template.FuncMap), + } +} + +// AddTemplate adds a new template to the registry +func (r *Registry) AddTemplate(t testing.TB, name, templateStr string) error { + t.Helper() + + tmpl := template.New(name) + if len(r.funcMap) > 0 { + tmpl = tmpl.Funcs(r.funcMap) + } + + parsed, err := tmpl.Parse(templateStr) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + r.templates[name] = parsed + + return nil +} + +// MustAddTemplate is like AddTemplate but panics on error +func (r *Registry) MustAddTemplate(t testing.TB, name, templateStr string) { + t.Helper() + + if err := r.AddTemplate(t, name, templateStr); err != nil { + t.Fatalf("failed to add template %q: %s", templateStr, err) + } +} + +// Render renders a template with the given config +func (r *Registry) Render(t testing.TB, templateKey string, cfg map[string]any) (string, error) { + t.Helper() + + tmpl, exists := r.templates[templateKey] + if !exists { + availableTemplates := r.getAvailableTemplates() + return "", fmt.Errorf("template %q does not exist for resource. Available templates: %v", + templateKey, + availableTemplates, + ) + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, cfg); err != nil { + return "", fmt.Errorf("failed to render template: %w", err) + } + + return buf.String(), nil +} + +// MustRender is like Render but fails the test on error +func (r *Registry) MustRender(t testing.TB, templateKey string, cfg map[string]any) string { + t.Helper() + + result, err := r.Render(t, templateKey, cfg) + if err != nil { + t.Fatal(err) + } + + return result +} + +// AddFunction adds a custom function to the template registry +func (r *Registry) AddFunction(name string, fn interface{}) { + if r.funcMap == nil { + r.funcMap = make(template.FuncMap) + } + r.funcMap[name] = fn +} + +// HasTemplate checks if a template exists in the registry +func (r *Registry) HasTemplate(key string) bool { + _, exists := r.templates[key] + return exists +} + +// RemoveTemplate removes a template from the registry +func (r *Registry) RemoveTemplate(key string) { + delete(r.templates, key) +} + +// NewCompositionBuilder creates a new composition builder +func (r *Registry) NewCompositionBuilder() *CompositionBuilder { + return &CompositionBuilder{ + registry: r, + compositions: make([]compositionEntry, 0), + } +} + +// getAvailableTemplates returns a sorted list of available template keys +func (r *Registry) getAvailableTemplates() []string { + templates := make([]string, 0, len(r.templates)) + for k := range r.templates { + templates = append(templates, k) + } + sort.Strings(templates) + + return templates +} diff --git a/internal/acctest/template/template.go b/internal/acctest/template/template.go new file mode 100644 index 000000000..6593d18b0 --- /dev/null +++ b/internal/acctest/template/template.go @@ -0,0 +1,67 @@ +package template + +import ( + "fmt" + "html/template" +) + +// ResourceConfig is the interface that all resource configs must implement +type resourceConfig interface { + // ToMap converts the config to a map for template rendering + ToMap() map[string]any +} + +// Template represents a single Terraform configuration template +type Template struct { + Name string + Template string +} + +// In internal/acctest/template/value.go + +// TemplateValue represents a value that can either be a literal string or a reference +type TemplateValue struct { + Value string + IsLiteral bool +} + +// Literal creates a new literal value +func Literal(v string) TemplateValue { + return TemplateValue{Value: v, IsLiteral: true} +} + +// Reference creates a new reference value +func Reference(v string) TemplateValue { + return TemplateValue{Value: v, IsLiteral: false} +} + +// String returns the properly formatted value based on whether it's a literal or reference +func (v TemplateValue) String() string { + if v.IsLiteral { + return fmt.Sprintf("%q", v.Value) + } + return v.Value +} + +// MarshalText implements encoding.TextMarshaler +func (v TemplateValue) MarshalText() ([]byte, error) { + return []byte(v.String()), nil +} + +// Add template functions for the generator +func templateValueFuncs() template.FuncMap { + return template.FuncMap{ + "renderValue": func(v interface{}) string { + switch val := v.(type) { + case TemplateValue: + return val.String() + case string: + return fmt.Sprintf("%q", val) + case int, int64, float64, bool: + return fmt.Sprintf("%v", val) + default: + return fmt.Sprintf("%v", val) + } + }, + } +} diff --git a/internal/acctest/template/template_set.go b/internal/acctest/template/template_set.go new file mode 100644 index 000000000..f29d98565 --- /dev/null +++ b/internal/acctest/template/template_set.go @@ -0,0 +1,109 @@ +package template + +import ( + "fmt" + "sync" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/aiven/terraform-provider-aiven/internal/sdkprovider/provider" +) + +var ( + globalTemplateSet *ResourceTemplateSet + gtSetOnce sync.Once +) + +// ResourceTemplateSet represents a set of related resources and their templates +type ResourceTemplateSet struct { + resources map[string]*schema.Resource + registry *Registry + t testing.TB +} + +// NewTemplateSet creates a new template set +func NewTemplateSet(t testing.TB) *ResourceTemplateSet { + return &ResourceTemplateSet{ + resources: make(map[string]*schema.Resource), + registry: NewTemplateRegistry(), + t: t, + } +} + +// GetTemplateSet returns a singleton instance of ResourceTemplateSet with all provider resources pre-registered +func GetTemplateSet(t testing.TB) *ResourceTemplateSet { + gtSetOnce.Do(func() { + globalTemplateSet = initTemplateSet(t) + }) + + globalTemplateSet.t = t // Update testing.TB for the current test context + + return globalTemplateSet +} + +// AddTemplate adds a custom template to the template set +func (s *ResourceTemplateSet) AddTemplate(name, templateStr string) *ResourceTemplateSet { + s.registry.MustAddTemplate(s.t, name, templateStr) + + return s +} + +// NewBuilder creates a new composition builder +func (s *ResourceTemplateSet) NewBuilder() *CompositionBuilder { + return &CompositionBuilder{ + registry: s.registry, + compositions: make([]compositionEntry, 0), + } +} + +// initTemplateSet initializes a new template set with all provider resources +func initTemplateSet(t testing.TB) *ResourceTemplateSet { + p, err := provider.Provider("dev") + if err != nil { + t.Fatalf("failed to get provider: %v", err) + } + + set := &ResourceTemplateSet{ + registry: NewTemplateRegistry(), + t: t, + } + + // Register all resources + for resourceType, resource := range p.ResourcesMap { + set.registerResource(resourceType, resource, ResourceKindResource) + } + + // Register all data sources + for resourceType, resource := range p.DataSourcesMap { + set.registerResource(resourceType, resource, ResourceKindDataSource) + } + + return set +} + +// registerResource handles the registration of a single resource or data source +func (s *ResourceTemplateSet) registerResource(resourceType string, r *schema.Resource, kind ResourceKind) { + generator := NewSchemaTemplateGenerator() + + // Register template functions + for name, fn := range generator.funcMap { + s.registry.AddFunction(name, fn) + } + + // Generate and register the template + template := generator.GenerateTemplate(r, resourceType, kind) + s.registry.MustAddTemplate(s.t, templateKey(resourceType, kind), template) +} + +// templateKey generates a unique template key based on resource type and kind +func templateKey(resourceType string, kind ResourceKind) string { + switch kind { + case ResourceKindResource: + return fmt.Sprintf("resource.%s", resourceType) + case ResourceKindDataSource: + return fmt.Sprintf("data.%s", resourceType) + default: + return resourceType + } +} diff --git a/internal/acctest/template_test.go b/internal/acctest/template/template_test.go similarity index 97% rename from internal/acctest/template_test.go rename to internal/acctest/template/template_test.go index 7ffdc769c..962bb3572 100644 --- a/internal/acctest/template_test.go +++ b/internal/acctest/template/template_test.go @@ -1,4 +1,4 @@ -package acctest +package template import ( "fmt" @@ -9,7 +9,7 @@ import ( ) func TestCompositionBuilder_SingleResource(t *testing.T) { - registry := NewTemplateRegistry("test") + registry := NewTemplateRegistry() template := `resource "aiven_project" "example_project" { project = "{{ .project_name }}" @@ -30,7 +30,7 @@ func TestCompositionBuilder_SingleResource(t *testing.T) { } func TestCompositionBuilder_TwoIndependentResources(t *testing.T) { - registry := NewTemplateRegistry("test") + registry := NewTemplateRegistry() registry.MustAddTemplate(t, "org_unit", `resource "aiven_organizational_unit" "example_unit" { name = "{{ .name }}" @@ -62,7 +62,7 @@ resource "aiven_billing_group" "example_billing_group" { } func TestCompositionBuilder_DependentResources(t *testing.T) { - registry := NewTemplateRegistry("test") + registry := NewTemplateRegistry() registry.MustAddTemplate(t, "billing_group", `resource "aiven_billing_group" "example_billing_group" { name = "{{ .name }}" @@ -95,7 +95,7 @@ resource "aiven_project" "example_project" { } func TestCompositionBuilder_ConditionalResource(t *testing.T) { - registry := NewTemplateRegistry("test") + registry := NewTemplateRegistry() registry.MustAddTemplate(t, "project", `resource "aiven_project" "example_project" { project = "{{ .name }}" @@ -152,7 +152,7 @@ resource "aiven_redis" "redis1" { } func TestCompositionBuilder_DataSourceAndResource(t *testing.T) { - registry := NewTemplateRegistry("test") + registry := NewTemplateRegistry() registry.MustAddTemplate(t, "org_data", `data "aiven_organization" "main" { name = "{{ .name }}" @@ -183,7 +183,7 @@ resource "aiven_project" "example_project" { } func TestTemplateNilAndMissingValues(t *testing.T) { - registry := NewTemplateRegistry("test") + registry := NewTemplateRegistry() templateStr := `resource "aiven_service" "example" { project = "{{ .project }}" @@ -394,7 +394,7 @@ resource "aiven_kafka_user" "example_service_user" { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - registry := NewTemplateRegistry("test") + registry := NewTemplateRegistry() // Add all templates for key, tmpl := range tt.templates { diff --git a/internal/acctest/template/usage_example_test.go b/internal/acctest/template/usage_example_test.go new file mode 100644 index 000000000..040b1b1fb --- /dev/null +++ b/internal/acctest/template/usage_example_test.go @@ -0,0 +1,62 @@ +package template + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// This is the test to demonstrate what we would like to achieve +func TestExpectedBehaviour(t *testing.T) { + templateSet := GetTemplateSet(t) // all existing resources are pre-registered + + // you can add new resources to the template if needed + templateSet.AddTemplate("aiven_kafka", ` +resource "aiven_kafka" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-2" + service_name = "{{ .service_name }}" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" +}`) + + // you can add new external resources to the template + templateSet.AddTemplate("gcp_provider", ` +provider "google" { + project = "{{ .gcp_project }}" + region = "{{ .gcp_region }}" +}`) + + builder := templateSet.NewBuilder(). + AddDataSource("aiven_project", map[string]any{ //provide configuration for the data source + "resource_name": "foo", + "project": "test_project", + }). + AddResource("aiven_kafka_quota", map[string]any{ + "resource_name": "example_quota_1", // add a resource + "project": "test_project", + "service_name": "test-service", + "user": "test_user", + "client_id": "test_client_id", + "consumer_byte_rate": 1000, + }). + AddResource("aiven_kafka_quota", map[string]any{ // add a second with different resource name + "resource_name": "example_quota_2", + "project": Reference("data.aiven_project.foo.project"), // reference to the existing resource + "service_name": "test_service_2", + "user": "test_user_2", + "client_id": "test_client_id_2", + "consumer_byte_rate": 1000, + }). + Add("aiven_kafka", map[string]any{"service_name": "test-service"}). + Add("gcp_provider", map[string]any{"gcp_project": "test-project", "gcp_region": "us-central1"}) + + str := builder.MustRender(t) // render the template + assert.NotEmpty(t, str) + + builder.RemoveByResourceName("example_quota_1") // remove a resource by its name + + nextStep := builder.MustRender(t) // render the template again + assert.NotEmpty(t, nextStep) +} diff --git a/internal/sdkprovider/service/kafka/kafka_quota_test.go b/internal/sdkprovider/service/kafka/kafka_quota_test.go index ca4d64b19..1a85a2162 100644 --- a/internal/sdkprovider/service/kafka/kafka_quota_test.go +++ b/internal/sdkprovider/service/kafka/kafka_quota_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" acc "github.com/aiven/terraform-provider-aiven/internal/acctest" + "github.com/aiven/terraform-provider-aiven/internal/acctest/template" "github.com/aiven/terraform-provider-aiven/internal/common" "github.com/aiven/terraform-provider-aiven/internal/schemautil" ) @@ -22,7 +23,7 @@ const kafkaQuotaResource = "aiven_kafka_quota" func TestAccAivenKafkaQuota(t *testing.T) { var ( - registry = acc.NewTemplateRegistry(kafkaQuotaResource) + registry = template.NewTemplateRegistry() randName = acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) serviceName = fmt.Sprintf("test-acc-sr-%s", randName) projectName = os.Getenv("AIVEN_PROJECT_NAME") @@ -68,7 +69,7 @@ resource "aiven_kafka_quota" "{{ .resource_name }}" { {{- end }} }`) - var newComposition = func() *acc.CompositionBuilder { + var newComposition = func() *template.CompositionBuilder { return registry.NewCompositionBuilder(). Add("project_data", map[string]interface{}{ "project": projectName}).