diff --git a/internal/cmd/dns/record-set/create/create.go b/internal/cmd/dns/record-set/create/create.go index 36c065d1c..ed77e5735 100644 --- a/internal/cmd/dns/record-set/create/create.go +++ b/internal/cmd/dns/record-set/create/create.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/goccy/go-yaml" + "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" @@ -16,8 +17,6 @@ import ( dnsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - - "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/dns" "github.com/stackitcloud/stackit-sdk-go/services/dns/wait" ) @@ -31,6 +30,7 @@ const ( typeFlag = "type" defaultType = "A" + txtType = "TXT" ) type inputModel struct { @@ -137,6 +137,20 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { Type: flags.FlagWithDefaultToStringValue(p, cmd, typeFlag), } + if model.Type == txtType { + for idx := range model.Records { + // Based on RFC 1035 section 2.3.4, TXT Records are limited to 255 Characters + // Longer strings need to be split into multiple records + if len(model.Records[idx]) > 255 { + var err error + model.Records[idx], err = dnsUtils.FormatTxtRecord(model.Records[idx]) + if err != nil { + return nil, err + } + } + } + } + if p.IsVerbosityDebug() { modelStr, err := print.BuildDebugStrFromInputModel(model) if err != nil { diff --git a/internal/cmd/dns/record-set/create/create_test.go b/internal/cmd/dns/record-set/create/create_test.go index 79044ecde..f1841d7bb 100644 --- a/internal/cmd/dns/record-set/create/create_test.go +++ b/internal/cmd/dns/record-set/create/create_test.go @@ -2,6 +2,8 @@ package create import ( "context" + "fmt" + "strings" "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" @@ -23,6 +25,12 @@ var testClient = &dns.APIClient{} var testProjectId = uuid.NewString() var testZoneId = uuid.NewString() +var recordTxtOver255Char = []string{ + "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo", + "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo", + "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar", +} + func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ projectIdFlag: testProjectId, @@ -76,7 +84,7 @@ func fixtureRequest(mods ...func(request *dns.ApiCreateRecordSetRequest)) dns.Ap } func TestParseInput(t *testing.T) { - tests := []struct { + var tests = []struct { description string flagValues map[string]string recordFlagValues []string @@ -236,8 +244,27 @@ func TestParseInput(t *testing.T) { model.Records = append(model.Records, "1.2.3.4", "5.6.7.8") }), }, - } + { + description: "TXT record with > 255 characters", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[typeFlag] = txtType + flagValues[recordFlag] = strings.Join(recordTxtOver255Char, "") + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + var content string + for idx, val := range recordTxtOver255Char { + content += fmt.Sprintf("%q", val) + if idx != len(recordTxtOver255Char)-1 { + content += " " + } + } + model.Records = []string{content} + model.Type = txtType + }), + }, + } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { p := print.NewPrinter() diff --git a/internal/cmd/dns/record-set/update/update.go b/internal/cmd/dns/record-set/update/update.go index 77a054a14..2b3d258aa 100644 --- a/internal/cmd/dns/record-set/update/update.go +++ b/internal/cmd/dns/record-set/update/update.go @@ -28,6 +28,7 @@ const ( nameFlag = "name" recordFlag = "record" ttlFlag = "ttl" + txtType = "TXT" ) type inputModel struct { @@ -38,6 +39,7 @@ type inputModel struct { Name *string Records *[]string TTL *int64 + Type *string } func NewCmd(p *print.Printer) *cobra.Command { @@ -76,6 +78,19 @@ func NewCmd(p *print.Printer) *cobra.Command { recordSetLabel = model.RecordSetId } + typeLabel, err := dnsUtils.GetRecordSetType(ctx, apiClient, model.ProjectId, model.ZoneId, model.RecordSetId) + if err != nil { + p.Debug(print.ErrorLevel, "get record set type: %v", err) + } + model.Type = typeLabel + + if utils.PtrString(model.Type) == txtType { + err = parseTxtRecord(model.Records) + if err != nil { + return err + } + } + if !model.AssumeYes { prompt := fmt.Sprintf("Are you sure you want to update record set %s of zone %s?", recordSetLabel, zoneLabel) err = p.PromptForConfirmation(prompt) @@ -165,6 +180,27 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return &model, nil } +func parseTxtRecord(records *[]string) error { + if records == nil { + return nil + } + if len(*records) == 0 { + return nil + } + + for idx := range *records { + var err error + // Based on RFC 1035 section 2.3.4, TXT Records are limited to 255 Characters. + // Longer strings need to be split into multiple records + (*records)[idx], err = dnsUtils.FormatTxtRecord((*records)[idx]) + if err != nil { + return err + } + } + + return nil +} + func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClient) dns.ApiPartialUpdateRecordSetRequest { var records *[]dns.RecordPayload = nil if model.Records != nil { diff --git a/internal/cmd/dns/record-set/update/update_test.go b/internal/cmd/dns/record-set/update/update_test.go index 19e544a76..9b07ffabb 100644 --- a/internal/cmd/dns/record-set/update/update_test.go +++ b/internal/cmd/dns/record-set/update/update_test.go @@ -24,6 +24,13 @@ var testProjectId = uuid.NewString() var testZoneId = uuid.NewString() var testRecordSetId = uuid.NewString() +var ( + text255Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo" + text256Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob" + result256Characters = "\"foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo\" \"b\"" + text4050Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo" +) + func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ testRecordSetId, @@ -78,10 +85,11 @@ func fixtureRequest(mods ...func(request *dns.ApiPartialUpdateRecordSetRequest)) }, Ttl: utils.Ptr(int64(3600)), }) + req := &request for _, mod := range mods { - mod(&request) + mod(req) } - return request + return *req } func TestParseInput(t *testing.T) { @@ -306,6 +314,71 @@ func TestParseInput(t *testing.T) { } } +func TestParseTxtRecord(t *testing.T) { + tests := []struct { + description string + records *[]string + expectedResult *[]string + isValid bool + shouldErr bool + }{ + { + description: "empty", + records: nil, + expectedResult: nil, + isValid: true, + }, + { + description: "base", + records: &[]string{"foobar"}, + expectedResult: &[]string{"foobar"}, + isValid: true, + }, + { + description: "input has length of 255 characters and should not split", + records: &[]string{text255Characters}, + expectedResult: &[]string{text255Characters}, + isValid: true, + }, + { + description: "input has length 256 characters and should split", + records: &[]string{text256Characters}, + expectedResult: &[]string{result256Characters}, + isValid: true, + }, + { + description: "input has length 4050 characters and should fail", + records: &[]string{text4050Characters}, + isValid: false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + err := parseTxtRecord(tt.records) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("should not fail but got error: %v", err) + return + } + if err == nil && !tt.isValid { + t.Fatalf("should fail but got none") + return + } + + if !tt.isValid { + t.Fatalf("should fail but got none") + return + } + diff := cmp.Diff(tt.expectedResult, tt.records) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + func TestBuildRequest(t *testing.T) { tests := []struct { description string diff --git a/internal/pkg/services/dns/utils/utils.go b/internal/pkg/services/dns/utils/utils.go index da57eb96c..351cf2e47 100644 --- a/internal/pkg/services/dns/utils/utils.go +++ b/internal/pkg/services/dns/utils/utils.go @@ -3,7 +3,9 @@ package utils import ( "context" "fmt" + "math" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/dns" ) @@ -27,3 +29,37 @@ func GetRecordSetName(ctx context.Context, apiClient DNSClient, projectId, zoneI } return *resp.Rrset.Name, nil } + +func GetRecordSetType(ctx context.Context, apiClient DNSClient, projectId, zoneId, recordSetId string) (*string, error) { + resp, err := apiClient.GetRecordSetExecute(ctx, projectId, zoneId, recordSetId) + if err != nil { + return utils.Ptr(""), fmt.Errorf("get DNS recordset: %w", err) + } + return resp.Rrset.Type, nil +} + +func FormatTxtRecord(input string) (string, error) { + length := float64(len(input)) + if length <= 255 { + return input, nil + } + // Max length with quotes and white spaces is 4096. Without the quotes and white spaces the max length is 4049 + if length > 4049 { + return "", fmt.Errorf("max input length is 4049. The length of the input is %v", length) + } + + result := "" + chunks := int(math.Ceil(length / 255)) + for i := range chunks { + skip := 255 * i + if i == chunks-1 { + // Append the left record content + result += fmt.Sprintf("%q", input[0+skip:]) + } else { + // Add 255 characters of the record data quoted to the result + result += fmt.Sprintf("%q ", input[0+skip:255+skip]) + } + } + + return result, nil +} diff --git a/internal/pkg/services/dns/utils/utils_test.go b/internal/pkg/services/dns/utils/utils_test.go index 2c972218c..12cae8fdc 100644 --- a/internal/pkg/services/dns/utils/utils_test.go +++ b/internal/pkg/services/dns/utils/utils_test.go @@ -5,9 +5,8 @@ import ( "fmt" "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" - "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/dns" ) @@ -15,11 +14,17 @@ var ( testProjectId = uuid.NewString() testZoneId = uuid.NewString() testRecordSetId = uuid.NewString() + + text255Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo" + text256Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob" + result256Characters = "\"foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo\" \"b\"" + text4050Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo" ) const ( testZoneName = "zone" testRecordSetName = "record-set" + testRecordSetType = "A" ) type dnsClientMocked struct { @@ -142,3 +147,118 @@ func TestGetRecordSetName(t *testing.T) { }) } } + +func TestGetRecordSetType(t *testing.T) { + tests := []struct { + description string + getRecordSetFails bool + getRecordSetResp *dns.RecordSetResponse + isValid bool + expectedOutput string + }{ + { + description: "base", + getRecordSetResp: &dns.RecordSetResponse{ + Rrset: &dns.RecordSet{ + Name: utils.Ptr(testRecordSetType), + }, + }, + isValid: true, + expectedOutput: testRecordSetType, + }, + { + description: "get record set fails", + getRecordSetFails: true, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + client := &dnsClientMocked{ + getRecordSetFails: tt.getRecordSetFails, + getRecordSetResp: tt.getRecordSetResp, + } + + output, err := GetRecordSetName(context.Background(), client, testProjectId, testZoneId, testRecordSetId) + + if tt.isValid && err != nil { + t.Errorf("failed on valid input") + } + if !tt.isValid && err == nil { + t.Errorf("did not fail on invalid input") + } + if !tt.isValid { + return + } + if output != tt.expectedOutput { + t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) + } + }) + } +} + +func TestFormatTxtRecord(t *testing.T) { + tests := []struct { + description string + input string + expected string + isValid bool + }{ + { + description: "base", + input: "foobar", + expected: "foobar", + isValid: true, + }, + { + description: "empty", + input: "", + expected: "", + isValid: true, + }, + { + description: "255 characters", + input: text255Characters, + expected: text255Characters, + isValid: true, + }, + { + description: "256 characters", + input: text256Characters, + expected: result256Characters, + isValid: true, + }, + { + description: "> 4049 characters should throw error", + input: text4050Characters, + isValid: false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + result, err := FormatTxtRecord(tt.input) + + if err != nil { + if !tt.isValid { + return + } + t.Errorf("failed on valid input, got %v", err) + return + } + + if err == nil && !tt.isValid { + t.Errorf("did not fail on invalid input") + return + } + + if !tt.isValid { + t.Errorf("did not fail on invalid input") + return + } + if result != tt.expected { + t.Errorf("expected result to be %s, got %s", tt.expected, result) + } + }) + } +}