Harden read-only provider with tests, docs, and CI

This commit is contained in:
beatz174-bit committed 2026-04-21 14:39:36 +10:00
1 parent cf47a6ed48
commit 6cc1b206dc
24 files changed
+711 -246

No files matched your search

+30 -18
View File
@@ -7,6 +7,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -52,30 +53,34 @@ func (d *dnsRecordsDataSource) Metadata(_ context.Context, req datasource.Metada
func (d *dnsRecordsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Get DNS records from Dynu for the domain resolved from a hostname.",
Description: "Lists DNS records for the Dynu root domain resolved from a hostname.",
Attributes: map[string]schema.Attribute{
"hostname": schema.StringAttribute{
Required: true,
Description: "Any hostname under the target root domain.",
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
Description: "Fully-qualified hostname under the target root domain.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.RegexMatches(hostnameValidator, "must be a valid fully-qualified hostname"),
},
},
"domain_id": schema.Int64Attribute{Computed: true},
"domain_name": schema.StringAttribute{Computed: true},
"domain_id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID resolved from hostname."},
"domain_name": schema.StringAttribute{Computed: true, Description: "Dynu root domain name resolved from hostname."},
"records": schema.ListNestedAttribute{
Computed: true,
Computed: true,
Description: "Sorted DNS records for the resolved domain. Timestamps are returned as Dynu provides them.",
NestedObject: schema.NestedAttributeObject{Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{Computed: true},
"domain_id": schema.Int64Attribute{Computed: true},
"domain_name": schema.StringAttribute{Computed: true},
"node_name": schema.StringAttribute{Computed: true},
"hostname": schema.StringAttribute{Computed: true},
"record_type": schema.StringAttribute{Computed: true},
"ttl": schema.Int64Attribute{Computed: true},
"state": schema.BoolAttribute{Computed: true},
"content": schema.StringAttribute{Computed: true},
"updated_on": schema.StringAttribute{Computed: true},
"group": schema.StringAttribute{Computed: true},
"host": schema.StringAttribute{Computed: true},
"id": schema.Int64Attribute{Computed: true, Description: "Dynu DNS record ID."},
"domain_id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID for this record."},
"domain_name": schema.StringAttribute{Computed: true, Description: "Domain name for this record."},
"node_name": schema.StringAttribute{Computed: true, Description: "Node/label portion of the record."},
"hostname": schema.StringAttribute{Computed: true, Description: "Fully-qualified hostname for the record."},
"record_type": schema.StringAttribute{Computed: true, Description: "DNS record type (A, CNAME, TXT, etc.)."},
"ttl": schema.Int64Attribute{Computed: true, Description: "DNS TTL in seconds."},
"state": schema.BoolAttribute{Computed: true, Description: "Whether this DNS record is active."},
"content": schema.StringAttribute{Computed: true, Description: "Record content/value."},
"updated_on": schema.StringAttribute{Computed: true, Description: "Last update timestamp as returned by Dynu."},
"group": schema.StringAttribute{Computed: true, Description: "Dynu group value for this record."},
"host": schema.StringAttribute{Computed: true, Description: "Host field as returned by Dynu."},
}},
},
},
@@ -101,6 +106,11 @@ func (d *dnsRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
return
}
if state.Hostname.IsUnknown() || state.Hostname.IsNull() {
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "The hostname must be known and non-null.")
return
}
domainID, domainName, err := d.clientProvider.client.GetRootDomain(ctx, state.Hostname.ValueString())
if err != nil {
resp.Diagnostics.AddError("Unable to resolve Dynu domain from hostname", err.Error())
@@ -113,6 +123,8 @@ func (d *dnsRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
return
}
sortDNSRecords(records)
state.DomainID = types.Int64Value(domainID)
state.DomainName = types.StringValue(domainName)
state.Records = make([]dnsRecordStateItem, 0, len(records))
+12 -47
View File
@@ -3,9 +3,9 @@ package provider
import (
"context"
"fmt"
"regexp"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -18,6 +18,8 @@ var (
_ datasource.DataSourceWithConfigure = &domainDataSource{}
)
var hostnameValidator = regexp.MustCompile(`^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
type domainDataSource struct {
clientProvider *providerData
}
@@ -37,16 +39,19 @@ func (d *domainDataSource) Metadata(_ context.Context, req datasource.MetadataRe
func (d *domainDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Get a Dynu DNS domain by hostname.",
Description: "Resolves a hostname to its Dynu root domain and returns domain details.",
Attributes: map[string]schema.Attribute{
"hostname": schema.StringAttribute{
Required: true,
Description: "Hostname to resolve to a root domain.",
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
Description: "Fully-qualified hostname, such as www.example.com.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.RegexMatches(hostnameValidator, "must be a valid fully-qualified hostname"),
},
},
"domain": schema.SingleNestedAttribute{
Computed: true,
Description: "Resolved Dynu DNS domain details.",
Description: "Dynu root domain details for the supplied hostname.",
Attributes: domainAttributes(),
},
},
@@ -75,7 +80,7 @@ func (d *domainDataSource) Read(ctx context.Context, req datasource.ReadRequest,
}
if state.Hostname.IsUnknown() || state.Hostname.IsNull() {
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "hostname must be known and non-null.")
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "The hostname must be known and non-null.")
return
}
@@ -91,47 +96,7 @@ func (d *domainDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
domainValue := mapDomain(*domain)
domainObject, diags := types.ObjectValue(
map[string]attr.Type{
"id": types.Int64Type,
"name": types.StringType,
"unicode_name": types.StringType,
"token": types.StringType,
"state": types.StringType,
"group": types.StringType,
"ipv4_address": types.StringType,
"ipv6_address": types.StringType,
"ttl": types.Int64Type,
"ipv4": types.BoolType,
"ipv6": types.BoolType,
"ipv4_wildcard_alias": types.BoolType,
"ipv6_wildcard_alias": types.BoolType,
"allow_zone_transfer": types.BoolType,
"dnssec": types.BoolType,
"created_on": types.StringType,
"updated_on": types.StringType,
},
map[string]attr.Value{
"id": domainValue.ID,
"name": domainValue.Name,
"unicode_name": domainValue.UnicodeName,
"token": domainValue.Token,
"state": domainValue.State,
"group": domainValue.Group,
"ipv4_address": domainValue.IPv4Address,
"ipv6_address": domainValue.IPv6Address,
"ttl": domainValue.TTL,
"ipv4": domainValue.IPv4,
"ipv6": domainValue.IPv6,
"ipv4_wildcard_alias": domainValue.IPv4WildcardAlias,
"ipv6_wildcard_alias": domainValue.IPv6WildcardAlias,
"allow_zone_transfer": domainValue.AllowZoneTransfer,
"dnssec": domainValue.DNSSEC,
"created_on": domainValue.CreatedOn,
"updated_on": domainValue.UpdatedOn,
},
)
domainObject, diags := domainObjectValue(*domain)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
+21 -19
View File
@@ -52,11 +52,11 @@ func (d *domainsDataSource) Metadata(_ context.Context, req datasource.MetadataR
func (d *domainsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "List DNS domains from Dynu.",
Description: "Lists DNS domains visible to the configured Dynu API key.",
Attributes: map[string]schema.Attribute{
"domains": schema.ListNestedAttribute{
Computed: true,
Description: "List of Dynu DNS domains.",
Description: "Sorted list of domains. Timestamps are returned exactly as Dynu provides them.",
NestedObject: schema.NestedAttributeObject{Attributes: domainAttributes()},
},
},
@@ -84,6 +84,8 @@ func (d *domainsDataSource) Read(ctx context.Context, _ datasource.ReadRequest,
return
}
sortDomains(domains)
state := domainsDataSourceModel{Domains: make([]domainModel, 0, len(domains))}
for _, domain := range domains {
state.Domains = append(state.Domains, mapDomain(domain))
@@ -94,22 +96,22 @@ func (d *domainsDataSource) Read(ctx context.Context, _ datasource.ReadRequest,
func domainAttributes() map[string]schema.Attribute {
return map[string]schema.Attribute{
"id": schema.Int64Attribute{Computed: true},
"name": schema.StringAttribute{Computed: true},
"unicode_name": schema.StringAttribute{Computed: true},
"token": schema.StringAttribute{Computed: true, Sensitive: true},
"state": schema.StringAttribute{Computed: true},
"group": schema.StringAttribute{Computed: true},
"ipv4_address": schema.StringAttribute{Computed: true},
"ipv6_address": schema.StringAttribute{Computed: true},
"ttl": schema.Int64Attribute{Computed: true},
"ipv4": schema.BoolAttribute{Computed: true},
"ipv6": schema.BoolAttribute{Computed: true},
"ipv4_wildcard_alias": schema.BoolAttribute{Computed: true},
"ipv6_wildcard_alias": schema.BoolAttribute{Computed: true},
"allow_zone_transfer": schema.BoolAttribute{Computed: true},
"dnssec": schema.BoolAttribute{Computed: true},
"created_on": schema.StringAttribute{Computed: true},
"updated_on": schema.StringAttribute{Computed: true},
"id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID."},
"name": schema.StringAttribute{Computed: true, Description: "Primary domain name."},
"unicode_name": schema.StringAttribute{Computed: true, Description: "Unicode representation of the domain."},
"token": schema.StringAttribute{Computed: true, Sensitive: true, Description: "Dynu domain token."},
"state": schema.StringAttribute{Computed: true, Description: "Dynu domain state."},
"group": schema.StringAttribute{Computed: true, Description: "Dynu domain group."},
"ipv4_address": schema.StringAttribute{Computed: true, Description: "Configured IPv4 address, if any."},
"ipv6_address": schema.StringAttribute{Computed: true, Description: "Configured IPv6 address, if any."},
"ttl": schema.Int64Attribute{Computed: true, Description: "DNS TTL in seconds."},
"ipv4": schema.BoolAttribute{Computed: true, Description: "Whether IPv4 support is enabled."},
"ipv6": schema.BoolAttribute{Computed: true, Description: "Whether IPv6 support is enabled."},
"ipv4_wildcard_alias": schema.BoolAttribute{Computed: true, Description: "Whether IPv4 wildcard alias is enabled."},
"ipv6_wildcard_alias": schema.BoolAttribute{Computed: true, Description: "Whether IPv6 wildcard alias is enabled."},
"allow_zone_transfer": schema.BoolAttribute{Computed: true, Description: "Whether zone transfer is allowed."},
"dnssec": schema.BoolAttribute{Computed: true, Description: "Whether DNSSEC is enabled."},
"created_on": schema.StringAttribute{Computed: true, Description: "Creation timestamp as returned by Dynu."},
"updated_on": schema.StringAttribute{Computed: true, Description: "Last update timestamp as returned by Dynu."},
}
}
+73 -4
View File
@@ -1,11 +1,35 @@
package provider
import (
"sort"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
)
var domainObjectTypes = map[string]attr.Type{
"id": types.Int64Type,
"name": types.StringType,
"unicode_name": types.StringType,
"token": types.StringType,
"state": types.StringType,
"group": types.StringType,
"ipv4_address": types.StringType,
"ipv6_address": types.StringType,
"ttl": types.Int64Type,
"ipv4": types.BoolType,
"ipv6": types.BoolType,
"ipv4_wildcard_alias": types.BoolType,
"ipv6_wildcard_alias": types.BoolType,
"allow_zone_transfer": types.BoolType,
"dnssec": types.BoolType,
"created_on": types.StringType,
"updated_on": types.StringType,
}
func mapDomain(domain dynuclient.Domain) domainModel {
return domainModel{
ID: types.Int64Value(domain.ID),
@@ -14,8 +38,8 @@ func mapDomain(domain dynuclient.Domain) domainModel {
Token: types.StringValue(domain.Token),
State: types.StringValue(domain.State),
Group: types.StringValue(domain.Group),
IPv4Address: types.StringValue(domain.IPv4Address),
IPv6Address: types.StringValue(domain.IPv6Address),
IPv4Address: mapString(domain.IPv4Address),
IPv6Address: mapString(domain.IPv6Address),
TTL: types.Int64Value(domain.TTL),
IPv4: types.BoolValue(domain.IPv4),
IPv6: types.BoolValue(domain.IPv6),
@@ -23,14 +47,59 @@ func mapDomain(domain dynuclient.Domain) domainModel {
IPv6WildcardAlias: types.BoolValue(domain.IPv6WildcardAlias),
AllowZoneTransfer: types.BoolValue(domain.AllowZoneTransfer),
DNSSEC: types.BoolValue(domain.DNSSEC),
CreatedOn: types.StringValue(domain.CreatedOn),
UpdatedOn: types.StringValue(domain.UpdatedOn),
CreatedOn: mapString(domain.CreatedOn),
UpdatedOn: mapString(domain.UpdatedOn),
}
}
func domainObjectValue(domain dynuclient.Domain) (types.Object, diag.Diagnostics) {
mapped := mapDomain(domain)
return types.ObjectValue(domainObjectTypes, map[string]attr.Value{
"id": mapped.ID,
"name": mapped.Name,
"unicode_name": mapped.UnicodeName,
"token": mapped.Token,
"state": mapped.State,
"group": mapped.Group,
"ipv4_address": mapped.IPv4Address,
"ipv6_address": mapped.IPv6Address,
"ttl": mapped.TTL,
"ipv4": mapped.IPv4,
"ipv6": mapped.IPv6,
"ipv4_wildcard_alias": mapped.IPv4WildcardAlias,
"ipv6_wildcard_alias": mapped.IPv6WildcardAlias,
"allow_zone_transfer": mapped.AllowZoneTransfer,
"dnssec": mapped.DNSSEC,
"created_on": mapped.CreatedOn,
"updated_on": mapped.UpdatedOn,
})
}
func mapString(in string) types.String {
if in == "" {
return types.StringNull()
}
return types.StringValue(in)
}
func sortDomains(domains []dynuclient.Domain) {
sort.Slice(domains, func(i, j int) bool {
if domains[i].Name != domains[j].Name {
return domains[i].Name < domains[j].Name
}
return domains[i].ID < domains[j].ID
})
}
func sortDNSRecords(records []dynuclient.DNSRecord) {
sort.Slice(records, func(i, j int) bool {
if records[i].Hostname != records[j].Hostname {
return records[i].Hostname < records[j].Hostname
}
if records[i].RecordType != records[j].RecordType {
return records[i].RecordType < records[j].RecordType
}
return records[i].ID < records[j].ID
})
}
+44
View File
@@ -0,0 +1,44 @@
package provider
import (
"testing"
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
)
func TestSortDomains(t *testing.T) {
domains := []dynuclient.Domain{
{ID: 2, Name: "z.example.com"},
{ID: 1, Name: "a.example.com"},
{ID: 3, Name: "a.example.com"},
}
sortDomains(domains)
if domains[0].ID != 1 || domains[1].ID != 3 || domains[2].ID != 2 {
t.Fatalf("unexpected domain sort order: %#v", domains)
}
}
func TestSortDNSRecords(t *testing.T) {
records := []dynuclient.DNSRecord{
{ID: 3, Hostname: "b.example.com", RecordType: "TXT"},
{ID: 1, Hostname: "a.example.com", RecordType: "A"},
{ID: 2, Hostname: "a.example.com", RecordType: "A"},
}
sortDNSRecords(records)
if records[0].ID != 1 || records[1].ID != 2 || records[2].ID != 3 {
t.Fatalf("unexpected record sort order: %#v", records)
}
}
func TestMapString(t *testing.T) {
if !mapString("").IsNull() {
t.Fatal("expected empty string to map to null")
}
if mapString("value").ValueString() != "value" {
t.Fatal("expected non-empty string to map to value")
}
}
+27 -10
View File
@@ -3,6 +3,7 @@ package provider
import (
"context"
"os"
"strings"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -21,7 +22,8 @@ type dynuProvider struct {
}
type dynuProviderModel struct {
APIKey types.String `tfsdk:"api_key"`
APIKey types.String `tfsdk:"api_key"`
BaseURL types.String `tfsdk:"base_url"`
}
type providerData struct {
@@ -41,12 +43,16 @@ func (p *dynuProvider) Metadata(_ context.Context, _ provider.MetadataRequest, r
func (p *dynuProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Terraform provider for Dynu DNS read-only operations.",
Description: "Terraform provider for Dynu DNS read-only data sources.",
Attributes: map[string]schema.Attribute{
"api_key": schema.StringAttribute{
Optional: true,
Sensitive: true,
Description: "Dynu API key. Can also be provided using DYNU_API_KEY.",
Description: "Dynu API key. If omitted, the provider uses the DYNU_API_KEY environment variable.",
},
"base_url": schema.StringAttribute{
Optional: true,
Description: "Override Dynu API base URL. Primarily intended for automated tests.",
},
},
}
@@ -60,25 +66,28 @@ func (p *dynuProvider) Configure(ctx context.Context, req provider.ConfigureRequ
return
}
apiKey := os.Getenv("DYNU_API_KEY")
if !data.APIKey.IsNull() {
apiKey = data.APIKey.ValueString()
}
apiKey := resolveAPIKey(data.APIKey, os.Getenv("DYNU_API_KEY"))
if apiKey == "" {
resp.Diagnostics.AddAttributeError(
path.Root("api_key"),
"Missing Dynu API key",
"Set api_key in the provider configuration or DYNU_API_KEY in the environment.",
"Configure api_key in the provider block or set the DYNU_API_KEY environment variable.",
)
return
}
providerData := &providerData{client: dynuclient.New(apiKey)}
providerData := &providerData{client: newDynuClient(apiKey, data.BaseURL)}
resp.DataSourceData = providerData
resp.ResourceData = nil
}
func resolveAPIKey(configValue types.String, envValue string) string {
if !configValue.IsNull() && !configValue.IsUnknown() {
return strings.TrimSpace(configValue.ValueString())
}
return strings.TrimSpace(envValue)
}
func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewDomainsDataSource,
@@ -90,3 +99,11 @@ func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSo
func (p *dynuProvider) Resources(_ context.Context) []func() resource.Resource {
return nil
}
func newDynuClient(apiKey string, baseURL types.String) *dynuclient.Client {
if !baseURL.IsNull() && !baseURL.IsUnknown() && strings.TrimSpace(baseURL.ValueString()) != "" {
return dynuclient.New(apiKey, dynuclient.WithBaseURL(strings.TrimSpace(baseURL.ValueString())))
}
return dynuclient.New(apiKey)
}
+68 -9
View File
@@ -1,18 +1,77 @@
package provider
import (
"context"
"os"
"testing"
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
)
func TestAccScaffold(t *testing.T) {
if os.Getenv("TF_ACC") != "1" || os.Getenv("DYNU_API_KEY") == "" {
t.Skip("set TF_ACC=1 and DYNU_API_KEY to enable acceptance tests")
func testAccPreCheck(t *testing.T) {
t.Helper()
if os.Getenv("TF_ACC") != "1" {
t.Skip("set TF_ACC=1 to run acceptance tests")
}
if os.Getenv("DYNU_API_KEY") == "" {
t.Skip("set DYNU_API_KEY to run acceptance tests")
}
}
func testAccDomainFromEnv(t *testing.T) string {
t.Helper()
domain := os.Getenv("DYNU_DOMAIN")
if domain == "" {
t.Skip("set DYNU_DOMAIN to run domain-specific acceptance tests")
}
return domain
}
func TestAccDataSourceDomains(t *testing.T) {
testAccPreCheck(t)
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
domains, err := client.ListDomains(context.Background())
if err != nil {
t.Fatalf("ListDomains() failed: %v", err)
}
if len(domains) == 0 {
t.Fatal("expected at least one domain")
}
}
func TestAccDataSourceDomain(t *testing.T) {
testAccPreCheck(t)
hostname := testAccDomainFromEnv(t)
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
domainID, _, err := client.GetRootDomain(context.Background(), hostname)
if err != nil {
t.Fatalf("GetRootDomain() failed: %v", err)
}
domain, err := client.GetDomainByID(context.Background(), domainID)
if err != nil {
t.Fatalf("GetDomainByID() failed: %v", err)
}
if domain.ID == 0 || domain.Name == "" {
t.Fatalf("unexpected domain payload: %#v", domain)
}
}
func TestAccDataSourceDNSRecords(t *testing.T) {
testAccPreCheck(t)
hostname := testAccDomainFromEnv(t)
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
domainID, _, err := client.GetRootDomain(context.Background(), hostname)
if err != nil {
t.Fatalf("GetRootDomain() failed: %v", err)
}
records, err := client.ListDNSRecords(context.Background(), domainID)
if err != nil {
t.Fatalf("ListDNSRecords() failed: %v", err)
}
if records == nil {
t.Fatal("expected records slice, got nil")
}
// Optional: DYNU_DOMAIN may be used by future acceptance test cases.
_ = os.Getenv("DYNU_DOMAIN")
// Acceptance tests for read-only data sources are intentionally scaffolded in phase 1.
// Add terraform-plugin-testing based test cases in a follow-up with stable fixtures.
}
+30
View File
@@ -0,0 +1,30 @@
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-framework/types"
)
func TestResolveAPIKey(t *testing.T) {
tests := []struct {
name string
config types.String
env string
want string
}{
{name: "config wins", config: types.StringValue("config-key"), env: "env-key", want: "config-key"},
{name: "env fallback", config: types.StringNull(), env: "env-key", want: "env-key"},
{name: "trim spaces", config: types.StringValue(" config-key "), env: " env-key ", want: "config-key"},
{name: "unknown uses env", config: types.StringUnknown(), env: "env-key", want: "env-key"},
{name: "empty when missing", config: types.StringNull(), env: "", want: ""},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := resolveAPIKey(tc.config, tc.env); got != tc.want {
t.Fatalf("resolveAPIKey() = %q, want %q", got, tc.want)
}
})
}
}