Archived
Merge pull request #26 from beatz174-bit/codex/add-live-gated-tests-for-dns-records
Add live-gated acceptance tests for empty-content and CNAME DNS record behaviors
This commit is contained in:
@@ -2,8 +2,13 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||||
)
|
)
|
||||||
@@ -75,3 +80,222 @@ func TestAccDataSourceDNSRecords(t *testing.T) {
|
|||||||
t.Fatal("expected records slice, got nil")
|
t.Fatal("expected records slice, got nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccDNSRecordAWithEmptyContent(t *testing.T) {
|
||||||
|
testAccPreCheck(t)
|
||||||
|
hostname := testAccDomainFromEnv(t)
|
||||||
|
domainID, _, client := testAccDomainClient(t, hostname)
|
||||||
|
|
||||||
|
nodeName := testAccDisposableNodeName("acc-empty-a")
|
||||||
|
record := testAccCreateRecordMaybeSkipUnsupported(
|
||||||
|
t,
|
||||||
|
client,
|
||||||
|
domainID,
|
||||||
|
dynuclient.CreateDNSRecordRequest{
|
||||||
|
NodeName: nodeName,
|
||||||
|
RecordType: "A",
|
||||||
|
},
|
||||||
|
"A record with empty content",
|
||||||
|
)
|
||||||
|
defer testAccDeleteRecord(t, client, domainID, record.ID)
|
||||||
|
|
||||||
|
got := testAccFetchRecordFromList(t, client, domainID, record.ID)
|
||||||
|
if strings.TrimSpace(got.Content) != "" {
|
||||||
|
t.Fatalf("expected Dynu to return empty content for A record, got %q", got.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDNSRecordAAAAWithEmptyContent(t *testing.T) {
|
||||||
|
testAccPreCheck(t)
|
||||||
|
hostname := testAccDomainFromEnv(t)
|
||||||
|
domainID, _, client := testAccDomainClient(t, hostname)
|
||||||
|
|
||||||
|
nodeName := testAccDisposableNodeName("acc-empty-aaaa")
|
||||||
|
record := testAccCreateRecordMaybeSkipUnsupported(
|
||||||
|
t,
|
||||||
|
client,
|
||||||
|
domainID,
|
||||||
|
dynuclient.CreateDNSRecordRequest{
|
||||||
|
NodeName: nodeName,
|
||||||
|
RecordType: "AAAA",
|
||||||
|
},
|
||||||
|
"AAAA record with empty content",
|
||||||
|
)
|
||||||
|
defer testAccDeleteRecord(t, client, domainID, record.ID)
|
||||||
|
|
||||||
|
got := testAccFetchRecordFromList(t, client, domainID, record.ID)
|
||||||
|
if strings.TrimSpace(got.Content) != "" {
|
||||||
|
t.Fatalf("expected Dynu to return empty content for AAAA record, got %q", got.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDNSRecordCNAMELifecycle(t *testing.T) {
|
||||||
|
testAccPreCheck(t)
|
||||||
|
hostname := testAccDomainFromEnv(t)
|
||||||
|
domainID, _, client := testAccDomainClient(t, hostname)
|
||||||
|
|
||||||
|
nodeName := testAccDisposableNodeName("acc-cname")
|
||||||
|
|
||||||
|
created, err := client.CreateDNSRecord(context.Background(), domainID, dynuclient.CreateDNSRecordRequest{
|
||||||
|
NodeName: nodeName,
|
||||||
|
RecordType: "CNAME",
|
||||||
|
Content: "target1.example.com",
|
||||||
|
TTL: 120,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateDNSRecord() failed for CNAME create: %v", err)
|
||||||
|
}
|
||||||
|
defer testAccDeleteRecord(t, client, domainID, created.ID)
|
||||||
|
|
||||||
|
updated, err := client.UpdateDNSRecord(context.Background(), domainID, created.ID, dynuclient.UpdateDNSRecordRequest{
|
||||||
|
NodeName: nodeName,
|
||||||
|
RecordType: "CNAME",
|
||||||
|
Content: "target2.example.com",
|
||||||
|
TTL: 300,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateDNSRecord() failed for CNAME update: %v", err)
|
||||||
|
}
|
||||||
|
if got := strings.TrimSuffix(strings.ToLower(updated.Content), "."); got != "target2.example.com" {
|
||||||
|
t.Fatalf("unexpected updated content from create/update response: %q", updated.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
read := testAccFetchRecordFromList(t, client, domainID, created.ID)
|
||||||
|
if got := strings.TrimSuffix(strings.ToLower(read.Content), "."); got != "target2.example.com" {
|
||||||
|
t.Fatalf("expected ListDNSRecords() read-back to reflect updated CNAME target, got %q", read.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccDomainClient(t *testing.T, hostname string) (int64, string, *dynuclient.Client) {
|
||||||
|
t.Helper()
|
||||||
|
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
|
||||||
|
domainID, domainName, err := client.GetRootDomain(context.Background(), hostname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetRootDomain() failed: %v", err)
|
||||||
|
}
|
||||||
|
return domainID, domainName, client
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccDisposableNodeName(prefix string) string {
|
||||||
|
return fmt.Sprintf("%s-%d-%04d", prefix, time.Now().UnixNano(), rand.Intn(10000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCreateRecordMaybeSkipUnsupported(t *testing.T, client *dynuclient.Client, domainID int64, req dynuclient.CreateDNSRecordRequest, scenario string) *dynuclient.DNSRecord {
|
||||||
|
t.Helper()
|
||||||
|
record, err := client.CreateDNSRecord(context.Background(), domainID, req)
|
||||||
|
if err == nil {
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiErr *dynuclient.APIError
|
||||||
|
if errors.As(err, &apiErr) && isUnsupportedEmptyContentAPIError(apiErr) {
|
||||||
|
t.Skipf("Dynu account/API does not support %s in this environment (%v)", scenario, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("CreateDNSRecord() failed for %s: %v", scenario, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccFetchRecordFromList(t *testing.T, client *dynuclient.Client, domainID int64, recordID int64) dynuclient.DNSRecord {
|
||||||
|
t.Helper()
|
||||||
|
records, err := client.ListDNSRecords(context.Background(), domainID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListDNSRecords() failed: %v", err)
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
if record.ID == recordID {
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("record id %d not found in ListDNSRecords() response", recordID)
|
||||||
|
return dynuclient.DNSRecord{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccDeleteRecord(t *testing.T, client *dynuclient.Client, domainID int64, recordID int64) {
|
||||||
|
t.Helper()
|
||||||
|
if err := client.DeleteDNSRecord(context.Background(), domainID, recordID); err != nil {
|
||||||
|
t.Fatalf("DeleteDNSRecord() cleanup failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUnsupportedEmptyContentAPIError(apiErr *dynuclient.APIError) bool {
|
||||||
|
if apiErr == nil || apiErr.StatusCode != 400 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedType := strings.ToLower(strings.TrimSpace(apiErr.Type))
|
||||||
|
if normalizedType != "validation exception" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedMessage := strings.ToLower(strings.TrimSpace(apiErr.Message))
|
||||||
|
knownUnsupportedMessages := []string{
|
||||||
|
"content is required",
|
||||||
|
"ipv4address is required",
|
||||||
|
"ipv6address is required",
|
||||||
|
}
|
||||||
|
for _, fragment := range knownUnsupportedMessages {
|
||||||
|
if strings.Contains(normalizedMessage, fragment) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsUnsupportedEmptyContentAPIError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err *dynuclient.APIError
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "validation message with required content",
|
||||||
|
err: &dynuclient.APIError{
|
||||||
|
StatusCode: 400,
|
||||||
|
Type: "Validation Exception",
|
||||||
|
Message: "Content is required.",
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "validation message with ipv4 required",
|
||||||
|
err: &dynuclient.APIError{
|
||||||
|
StatusCode: 400,
|
||||||
|
Type: "Validation Exception",
|
||||||
|
Message: "IPv4Address is required for A records.",
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different validation error should fail",
|
||||||
|
err: &dynuclient.APIError{
|
||||||
|
StatusCode: 400,
|
||||||
|
Type: "Validation Exception",
|
||||||
|
Message: "recordType is invalid",
|
||||||
|
},
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "transient throttling should fail",
|
||||||
|
err: &dynuclient.APIError{
|
||||||
|
StatusCode: 429,
|
||||||
|
Type: "Too Many Requests",
|
||||||
|
Message: "rate limit exceeded",
|
||||||
|
},
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := isUnsupportedEmptyContentAPIError(tc.err); got != tc.expect {
|
||||||
|
t.Fatalf("unexpected result for %q: got %v, want %v", tc.name, got, tc.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user