From 6c7e5df06b3f05e7e982882378009c23a0bac3fd Mon Sep 17 00:00:00 2001 From: Marcel Jacek Date: Tue, 28 Jan 2025 17:04:41 +0100 Subject: [PATCH 1/4] add function to format DNS TXT records --- internal/cmd/dns/record-set/create/create.go | 21 ++++++++++++-- .../cmd/dns/record-set/create/create_test.go | 29 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/internal/cmd/dns/record-set/create/create.go b/internal/cmd/dns/record-set/create/create.go index 36c065d1c..a3a7f5c7e 100644 --- a/internal/cmd/dns/record-set/create/create.go +++ b/internal/cmd/dns/record-set/create/create.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/goccy/go-yaml" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" @@ -16,6 +15,7 @@ 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" + "math" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/dns" @@ -31,6 +31,7 @@ const ( typeFlag = "type" defaultType = "A" + txtType = "TXT" ) type inputModel struct { @@ -152,7 +153,23 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClient) dns.ApiCreateRecordSetRequest { records := make([]dns.RecordPayload, 0) for _, r := range model.Records { - records = append(records, dns.RecordPayload{Content: utils.Ptr(r)}) + result := r + if len(r) > 255 && model.Type == txtType { + result = "" + length := float64(len(r)) + 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", r[0+skip:]) + } else { + // Add 255 characters of the record data quoted to the result + result += fmt.Sprintf("%q ", r[0+skip:255+skip]) + } + } + } + records = append(records, dns.RecordPayload{Content: utils.Ptr(result)}) } req := apiClient.CreateRecordSet(ctx, model.ProjectId, model.ZoneId) diff --git a/internal/cmd/dns/record-set/create/create_test.go b/internal/cmd/dns/record-set/create/create_test.go index 79044ecde..7ef8b45b5 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, @@ -326,6 +334,27 @@ func TestBuildRequest(t *testing.T) { Type: utils.Ptr(defaultType), }), }, + { + description: "add TXT record with > 255 characters", + model: fixtureInputModel(func(model *inputModel) { + model.Type = txtType + model.Records = []string{strings.Join(recordTxtOver255Char, "")} + }), + expectedRequest: testClient.CreateRecordSet(testCtx, testProjectId, testZoneId). + CreateRecordSetPayload(dns.CreateRecordSetPayload{ + Name: utils.Ptr("example.com"), + Records: &[]dns.RecordPayload{ + { + Content: utils.Ptr( + fmt.Sprintf("\"%s\"", strings.Join(recordTxtOver255Char, "\" \"")), + ), + }, + }, + Type: utils.Ptr(txtType), + Comment: utils.Ptr("comment"), + Ttl: utils.Ptr(int64(3600)), + }), + }, } for _, tt := range tests { From cef38938fea88fc6b30826fd1ca483349ca4008b Mon Sep 17 00:00:00 2001 From: Marcel Jacek Date: Thu, 30 Jan 2025 11:52:20 +0100 Subject: [PATCH 2/4] add support for updating DNS TXT records with values > 255 characters --- internal/cmd/dns/record-set/update/update.go | 27 +++++++++++++++++- .../cmd/dns/record-set/update/update_test.go | 28 +++++++++++++++++++ internal/pkg/services/dns/utils/utils.go | 9 ++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/internal/cmd/dns/record-set/update/update.go b/internal/cmd/dns/record-set/update/update.go index 77a054a14..ead2bdd4e 100644 --- a/internal/cmd/dns/record-set/update/update.go +++ b/internal/cmd/dns/record-set/update/update.go @@ -3,6 +3,7 @@ package update import ( "context" "fmt" + "math" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" @@ -28,6 +29,7 @@ const ( nameFlag = "name" recordFlag = "record" ttlFlag = "ttl" + txtType = "TXT" ) type inputModel struct { @@ -38,6 +40,7 @@ type inputModel struct { Name *string Records *[]string TTL *int64 + Type *string } func NewCmd(p *print.Printer) *cobra.Command { @@ -76,6 +79,12 @@ 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 !model.AssumeYes { prompt := fmt.Sprintf("Are you sure you want to update record set %s of zone %s?", recordSetLabel, zoneLabel) err = p.PromptForConfirmation(prompt) @@ -170,7 +179,23 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClie if model.Records != nil { records = utils.Ptr(make([]dns.RecordPayload, 0)) for _, r := range *model.Records { - records = utils.Ptr(append(*records, dns.RecordPayload{Content: utils.Ptr(r)})) + result := r + if len(r) > 255 && utils.PtrString(model.Type) == txtType { + result = "" + length := float64(len(r)) + 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", r[0+skip:]) + } else { + // Add 255 characters of the record data quoted to the result + result += fmt.Sprintf("%q ", r[0+skip:255+skip]) + } + } + } + records = utils.Ptr(append(*records, dns.RecordPayload{Content: utils.Ptr(result)})) } } diff --git a/internal/cmd/dns/record-set/update/update_test.go b/internal/cmd/dns/record-set/update/update_test.go index 19e544a76..63f642723 100644 --- a/internal/cmd/dns/record-set/update/update_test.go +++ b/internal/cmd/dns/record-set/update/update_test.go @@ -2,6 +2,8 @@ package update import ( "context" + "fmt" + "strings" "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" @@ -24,6 +26,12 @@ var testProjectId = uuid.NewString() var testZoneId = uuid.NewString() var testRecordSetId = uuid.NewString() +var recordTxtOver255Char = []string{ + "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo", + "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo", + "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar", +} + func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ testRecordSetId, @@ -330,6 +338,26 @@ func TestBuildRequest(t *testing.T) { expectedRequest: testClient.PartialUpdateRecordSet(testCtx, testProjectId, testZoneId, testRecordSetId). PartialUpdateRecordSetPayload(dns.PartialUpdateRecordSetPayload{}), }, + { + description: "update TXT record with > 255 characters", + model: fixtureInputModel(func(model *inputModel) { + model.Type = utils.Ptr(txtType) + model.Records = utils.Ptr([]string{strings.Join(recordTxtOver255Char, "")}) + }), + expectedRequest: testClient.PartialUpdateRecordSet(testCtx, testProjectId, testZoneId, testRecordSetId). + PartialUpdateRecordSetPayload(dns.PartialUpdateRecordSetPayload{ + Name: utils.Ptr("example.com"), + Records: &[]dns.RecordPayload{ + { + Content: utils.Ptr( + fmt.Sprintf("\"%s\"", strings.Join(recordTxtOver255Char, "\" \"")), + ), + }, + }, + Comment: utils.Ptr("comment"), + Ttl: utils.Ptr(int64(3600)), + }), + }, } for _, tt := range tests { diff --git a/internal/pkg/services/dns/utils/utils.go b/internal/pkg/services/dns/utils/utils.go index da57eb96c..ada332eba 100644 --- a/internal/pkg/services/dns/utils/utils.go +++ b/internal/pkg/services/dns/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/dns" ) @@ -27,3 +28,11 @@ 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 +} From 602c42fb93d9b4db3626f44e1c74e89fef73a55a Mon Sep 17 00:00:00 2001 From: Marcel Jacek Date: Thu, 30 Jan 2025 15:57:47 +0100 Subject: [PATCH 3/4] fix: linter issues --- internal/cmd/dns/record-set/create/create.go | 3 +- .../cmd/dns/record-set/create/create_test.go | 34 ++++++++++------- .../cmd/dns/record-set/update/update_test.go | 37 ++++++++++++------- internal/pkg/services/dns/utils/utils.go | 2 +- 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/internal/cmd/dns/record-set/create/create.go b/internal/cmd/dns/record-set/create/create.go index a3a7f5c7e..80f9746f8 100644 --- a/internal/cmd/dns/record-set/create/create.go +++ b/internal/cmd/dns/record-set/create/create.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "math" + "github.com/goccy/go-yaml" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" @@ -15,7 +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" - "math" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/dns" diff --git a/internal/cmd/dns/record-set/create/create_test.go b/internal/cmd/dns/record-set/create/create_test.go index 7ef8b45b5..d8bec210b 100644 --- a/internal/cmd/dns/record-set/create/create_test.go +++ b/internal/cmd/dns/record-set/create/create_test.go @@ -340,20 +340,28 @@ func TestBuildRequest(t *testing.T) { model.Type = txtType model.Records = []string{strings.Join(recordTxtOver255Char, "")} }), - expectedRequest: testClient.CreateRecordSet(testCtx, testProjectId, testZoneId). - CreateRecordSetPayload(dns.CreateRecordSetPayload{ - Name: utils.Ptr("example.com"), - Records: &[]dns.RecordPayload{ - { - Content: utils.Ptr( - fmt.Sprintf("\"%s\"", strings.Join(recordTxtOver255Char, "\" \"")), - ), + expectedRequest: func() dns.ApiCreateRecordSetRequest { + var content string + for idx, val := range recordTxtOver255Char { + content += fmt.Sprintf("%q", val) + if idx != len(recordTxtOver255Char)-1 { + content += " " + } + } + + return testClient.CreateRecordSet(testCtx, testProjectId, testZoneId). + CreateRecordSetPayload(dns.CreateRecordSetPayload{ + Name: utils.Ptr("example.com"), + Records: &[]dns.RecordPayload{ + { + Content: utils.Ptr(content), + }, }, - }, - Type: utils.Ptr(txtType), - Comment: utils.Ptr("comment"), - Ttl: utils.Ptr(int64(3600)), - }), + Type: utils.Ptr(txtType), + Comment: utils.Ptr("comment"), + Ttl: utils.Ptr(int64(3600)), + }) + }(), }, } diff --git a/internal/cmd/dns/record-set/update/update_test.go b/internal/cmd/dns/record-set/update/update_test.go index 63f642723..528906b71 100644 --- a/internal/cmd/dns/record-set/update/update_test.go +++ b/internal/cmd/dns/record-set/update/update_test.go @@ -86,10 +86,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) { @@ -344,19 +345,27 @@ func TestBuildRequest(t *testing.T) { model.Type = utils.Ptr(txtType) model.Records = utils.Ptr([]string{strings.Join(recordTxtOver255Char, "")}) }), - expectedRequest: testClient.PartialUpdateRecordSet(testCtx, testProjectId, testZoneId, testRecordSetId). - PartialUpdateRecordSetPayload(dns.PartialUpdateRecordSetPayload{ - Name: utils.Ptr("example.com"), - Records: &[]dns.RecordPayload{ - { - Content: utils.Ptr( - fmt.Sprintf("\"%s\"", strings.Join(recordTxtOver255Char, "\" \"")), - ), + expectedRequest: func() dns.ApiPartialUpdateRecordSetRequest { + var content string + for idx, val := range recordTxtOver255Char { + content += fmt.Sprintf("%q", val) + if idx != len(recordTxtOver255Char)-1 { + content += " " + } + } + + return testClient.PartialUpdateRecordSet(testCtx, testProjectId, testZoneId, testRecordSetId). + PartialUpdateRecordSetPayload(dns.PartialUpdateRecordSetPayload{ + Name: utils.Ptr("example.com"), + Records: &[]dns.RecordPayload{ + { + Content: utils.Ptr(content), + }, }, - }, - Comment: utils.Ptr("comment"), - Ttl: utils.Ptr(int64(3600)), - }), + Comment: utils.Ptr("comment"), + Ttl: utils.Ptr(int64(3600)), + }) + }(), }, } diff --git a/internal/pkg/services/dns/utils/utils.go b/internal/pkg/services/dns/utils/utils.go index ada332eba..15c581732 100644 --- a/internal/pkg/services/dns/utils/utils.go +++ b/internal/pkg/services/dns/utils/utils.go @@ -3,8 +3,8 @@ package utils import ( "context" "fmt" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/dns" ) From 8fd0f0f4e78fc2e950fe463d16bd1cd4c54752f4 Mon Sep 17 00:00:00 2001 From: Marcel Jacek Date: Fri, 31 Jan 2025 13:14:48 +0100 Subject: [PATCH 4/4] fix: review feedback - create new function which formats the dns records - add test cases - moved the formatting of txt records - in create command to parseInput - in update to separate function --- internal/cmd/dns/record-set/create/create.go | 36 +++-- .../cmd/dns/record-set/create/create_test.go | 52 +++----- internal/cmd/dns/record-set/update/update.go | 47 ++++--- .../cmd/dns/record-set/update/update_test.go | 106 ++++++++++----- internal/pkg/services/dns/utils/utils.go | 27 ++++ internal/pkg/services/dns/utils/utils_test.go | 124 +++++++++++++++++- 6 files changed, 286 insertions(+), 106 deletions(-) diff --git a/internal/cmd/dns/record-set/create/create.go b/internal/cmd/dns/record-set/create/create.go index 80f9746f8..ed77e5735 100644 --- a/internal/cmd/dns/record-set/create/create.go +++ b/internal/cmd/dns/record-set/create/create.go @@ -4,9 +4,9 @@ import ( "context" "encoding/json" "fmt" - "math" "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" @@ -17,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" ) @@ -139,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 { @@ -154,23 +166,7 @@ func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClient) dns.ApiCreateRecordSetRequest { records := make([]dns.RecordPayload, 0) for _, r := range model.Records { - result := r - if len(r) > 255 && model.Type == txtType { - result = "" - length := float64(len(r)) - 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", r[0+skip:]) - } else { - // Add 255 characters of the record data quoted to the result - result += fmt.Sprintf("%q ", r[0+skip:255+skip]) - } - } - } - records = append(records, dns.RecordPayload{Content: utils.Ptr(result)}) + records = append(records, dns.RecordPayload{Content: utils.Ptr(r)}) } req := apiClient.CreateRecordSet(ctx, model.ProjectId, model.ZoneId) diff --git a/internal/cmd/dns/record-set/create/create_test.go b/internal/cmd/dns/record-set/create/create_test.go index d8bec210b..f1841d7bb 100644 --- a/internal/cmd/dns/record-set/create/create_test.go +++ b/internal/cmd/dns/record-set/create/create_test.go @@ -84,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 @@ -244,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() @@ -334,35 +353,6 @@ func TestBuildRequest(t *testing.T) { Type: utils.Ptr(defaultType), }), }, - { - description: "add TXT record with > 255 characters", - model: fixtureInputModel(func(model *inputModel) { - model.Type = txtType - model.Records = []string{strings.Join(recordTxtOver255Char, "")} - }), - expectedRequest: func() dns.ApiCreateRecordSetRequest { - var content string - for idx, val := range recordTxtOver255Char { - content += fmt.Sprintf("%q", val) - if idx != len(recordTxtOver255Char)-1 { - content += " " - } - } - - return testClient.CreateRecordSet(testCtx, testProjectId, testZoneId). - CreateRecordSetPayload(dns.CreateRecordSetPayload{ - Name: utils.Ptr("example.com"), - Records: &[]dns.RecordPayload{ - { - Content: utils.Ptr(content), - }, - }, - Type: utils.Ptr(txtType), - Comment: utils.Ptr("comment"), - Ttl: utils.Ptr(int64(3600)), - }) - }(), - }, } for _, tt := range tests { diff --git a/internal/cmd/dns/record-set/update/update.go b/internal/cmd/dns/record-set/update/update.go index ead2bdd4e..2b3d258aa 100644 --- a/internal/cmd/dns/record-set/update/update.go +++ b/internal/cmd/dns/record-set/update/update.go @@ -3,7 +3,6 @@ package update import ( "context" "fmt" - "math" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" @@ -85,6 +84,13 @@ func NewCmd(p *print.Printer) *cobra.Command { } 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) @@ -174,28 +180,33 @@ 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 { records = utils.Ptr(make([]dns.RecordPayload, 0)) for _, r := range *model.Records { - result := r - if len(r) > 255 && utils.PtrString(model.Type) == txtType { - result = "" - length := float64(len(r)) - 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", r[0+skip:]) - } else { - // Add 255 characters of the record data quoted to the result - result += fmt.Sprintf("%q ", r[0+skip:255+skip]) - } - } - } - records = utils.Ptr(append(*records, dns.RecordPayload{Content: utils.Ptr(result)})) + records = utils.Ptr(append(*records, dns.RecordPayload{Content: utils.Ptr(r)})) } } diff --git a/internal/cmd/dns/record-set/update/update_test.go b/internal/cmd/dns/record-set/update/update_test.go index 528906b71..9b07ffabb 100644 --- a/internal/cmd/dns/record-set/update/update_test.go +++ b/internal/cmd/dns/record-set/update/update_test.go @@ -2,8 +2,6 @@ package update import ( "context" - "fmt" - "strings" "testing" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" @@ -26,11 +24,12 @@ var testProjectId = uuid.NewString() var testZoneId = uuid.NewString() var testRecordSetId = uuid.NewString() -var recordTxtOver255Char = []string{ - "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo", - "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo", - "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar", -} +var ( + text255Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo" + text256Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob" + result256Characters = "\"foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo\" \"b\"" + text4050Characters = "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoofoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo" +) func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ @@ -315,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 @@ -339,34 +403,6 @@ func TestBuildRequest(t *testing.T) { expectedRequest: testClient.PartialUpdateRecordSet(testCtx, testProjectId, testZoneId, testRecordSetId). PartialUpdateRecordSetPayload(dns.PartialUpdateRecordSetPayload{}), }, - { - description: "update TXT record with > 255 characters", - model: fixtureInputModel(func(model *inputModel) { - model.Type = utils.Ptr(txtType) - model.Records = utils.Ptr([]string{strings.Join(recordTxtOver255Char, "")}) - }), - expectedRequest: func() dns.ApiPartialUpdateRecordSetRequest { - var content string - for idx, val := range recordTxtOver255Char { - content += fmt.Sprintf("%q", val) - if idx != len(recordTxtOver255Char)-1 { - content += " " - } - } - - return testClient.PartialUpdateRecordSet(testCtx, testProjectId, testZoneId, testRecordSetId). - PartialUpdateRecordSetPayload(dns.PartialUpdateRecordSetPayload{ - Name: utils.Ptr("example.com"), - Records: &[]dns.RecordPayload{ - { - Content: utils.Ptr(content), - }, - }, - Comment: utils.Ptr("comment"), - Ttl: utils.Ptr(int64(3600)), - }) - }(), - }, } for _, tt := range tests { diff --git a/internal/pkg/services/dns/utils/utils.go b/internal/pkg/services/dns/utils/utils.go index 15c581732..351cf2e47 100644 --- a/internal/pkg/services/dns/utils/utils.go +++ b/internal/pkg/services/dns/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "context" "fmt" + "math" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/dns" @@ -36,3 +37,29 @@ func GetRecordSetType(ctx context.Context, apiClient DNSClient, projectId, zoneI } 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) + } + }) + } +}