From a927bda4c5afedf1fbc42d4142beeead846d7334 Mon Sep 17 00:00:00 2001 From: favonia Date: Thu, 5 Aug 2021 11:02:31 -0500 Subject: [PATCH 1/5] test(api): CloudflareAuth.New --- internal/api/cloudflare.go | 6 +- internal/api/cloudflare_test.go | 118 ++++++++++++++++++++++++++++++++ internal/config/config.go | 2 +- 3 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 internal/api/cloudflare_test.go diff --git a/internal/api/cloudflare.go b/internal/api/cloudflare.go index 5d86454f..6a162d97 100644 --- a/internal/api/cloudflare.go +++ b/internal/api/cloudflare.go @@ -30,7 +30,7 @@ const ( type CloudflareAuth struct { Token string AccountID string - URL string + BaseURL string } func (t *CloudflareAuth) New(ctx context.Context, indent pp.Indent, cacheExpiration time.Duration) (Handle, bool) { @@ -41,8 +41,8 @@ func (t *CloudflareAuth) New(ctx context.Context, indent pp.Indent, cacheExpirat } // set the base URL (mostly for testing) - if t.URL != "" { - handle.BaseURL = t.URL + if t.BaseURL != "" { + handle.BaseURL = t.BaseURL } // this is not needed, but is helpful for diagnosing the problem diff --git a/internal/api/cloudflare_test.go b/internal/api/cloudflare_test.go new file mode 100644 index 00000000..71baf9c0 --- /dev/null +++ b/internal/api/cloudflare_test.go @@ -0,0 +1,118 @@ +package api_test + +import ( + "context" + "crypto/sha512" + "encoding/hex" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/favonia/cloudflare-ddns/internal/api" +) + +// mockID returns a hex string of length 32, suitable for all kinds of IDs +// used in the Cloudflare API. +func mockID(seed string) string { + arr := sha512.Sum512([]byte(seed)) //nolint:gosec + return hex.EncodeToString(arr[:16]) +} + +const ( + mockToken = "token123" + mockAccount = "account456" +) + +func TestCloudflareAuthNewSuccess(t *testing.T) { + t.Parallel() + + mux := http.NewServeMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + mux.HandleFunc("/user/tokens/verify", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, []string{fmt.Sprintf("Bearer %s", mockToken)}, r.Header["Authorization"]) + + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, + `{ + "result": { "id": "%s", "status": "active" }, + "success": true, + "errors": [], + "messages": [ + { + "code": 10000, + "message": "This API Token is valid and active", + "type": null + } + ] + }`, mockID("result")) + }) + + auth := api.CloudflareAuth{ + Token: mockToken, + AccountID: mockAccount, + BaseURL: ts.URL, + } + + h, ok := auth.New(context.Background(), 3, time.Second) + require.NotNil(t, h) + require.True(t, ok) +} + +func TestCloudflareAuthNewEmpty(t *testing.T) { + t.Parallel() + + mux := http.NewServeMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + auth := api.CloudflareAuth{ + Token: "", + AccountID: mockAccount, + BaseURL: ts.URL, + } + + h, ok := auth.New(context.Background(), 3, time.Second) + require.Nil(t, h) + require.False(t, ok) +} + +func TestCloudflareAuthNewInvalid(t *testing.T) { + t.Parallel() + + mux := http.NewServeMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + mux.HandleFunc("/user/tokens/verify", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, []string{fmt.Sprintf("Bearer %s", mockToken)}, r.Header["Authorization"]) + + w.WriteHeader(http.StatusUnauthorized) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, + `{ + "success": false, + "errors": [{ "code": 1000, "message": "Invalid API Token" }], + "messages": [], + "result": null + }`) + }) + + auth := api.CloudflareAuth{ + Token: mockToken, + AccountID: mockAccount, + BaseURL: ts.URL, + } + + h, ok := auth.New(context.Background(), 3, time.Second) + require.Nil(t, h) + require.False(t, ok) +} diff --git a/internal/config/config.go b/internal/config/config.go index c5042736..3f70738c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -95,7 +95,7 @@ func readAuth(quiet quiet.Quiet, indent pp.Indent, field *api.Auth) bool { accountID := Getenv("CF_ACCOUNT_ID") - *field = &api.CloudflareAuth{Token: token, AccountID: accountID, URL: ""} + *field = &api.CloudflareAuth{Token: token, AccountID: accountID, BaseURL: ""} return true } From a507411a7a9fc59e5d5fdd2554a97a8df3cfdcd2 Mon Sep 17 00:00:00 2001 From: favonia Date: Thu, 5 Aug 2021 17:24:47 -0500 Subject: [PATCH 2/5] fix(api): always use ASCII forms to access Cloudflare API --- internal/api/cloudflare.go | 41 +++++++++++++++++++------------------ internal/api/fqdn.go | 21 +++++++++++++------ internal/config/config.go | 2 +- internal/updator/updator.go | 17 +++++++-------- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/internal/api/cloudflare.go b/internal/api/cloudflare.go index 6a162d97..f93a2a5e 100644 --- a/internal/api/cloudflare.go +++ b/internal/api/cloudflare.go @@ -82,7 +82,7 @@ func (h *CloudflareHandle) ActiveZones(ctx context.Context, indent pp.Indent, na res, err := h.cf.ListZonesContext(ctx, cloudflare.WithZoneFilters(name, h.cf.AccountID, "active")) if err != nil { - pp.Printf(indent, pp.EmojiError, "Failed to check the existence of a zone named %s: %v", name, err) + pp.Printf(indent, pp.EmojiError, "Failed to check the existence of a zone named %q: %v", name, err) return nil, false } @@ -97,7 +97,7 @@ func (h *CloudflareHandle) ActiveZones(ctx context.Context, indent pp.Indent, na } func (h *CloudflareHandle) ZoneOfDomain(ctx context.Context, indent pp.Indent, domain FQDN) (string, bool) { - if id, found := h.cache.zoneOfDomain.Get(domain.String()); found { + if id, found := h.cache.zoneOfDomain.Get(domain.ToASCII()); found { return id.(string), true } @@ -113,7 +113,7 @@ zoneSearch: case 0: // len(zones) == 0 continue zoneSearch case 1: // len(zones) == 1 - h.cache.zoneOfDomain.SetDefault(domain.String(), zones[0]) + h.cache.zoneOfDomain.SetDefault(domain.ToASCII(), zones[0]) return zones[0], true @@ -124,13 +124,13 @@ zoneSearch: } } - pp.Printf(indent, pp.EmojiError, "Failed to find the zone of %s.", domain) + pp.Printf(indent, pp.EmojiError, "Failed to find the zone of %q.", domain.Describe()) return "", false } func (h *CloudflareHandle) ListRecords(ctx context.Context, indent pp.Indent, domain FQDN, ipNet ipnet.Type) (map[string]net.IP, bool) { - if rmap, found := h.cache.listRecords[ipNet].Get(domain.String()); found { + if rmap, found := h.cache.listRecords[ipNet].Get(domain.ToASCII()); found { return rmap.(map[string]net.IP), true } @@ -141,11 +141,11 @@ func (h *CloudflareHandle) ListRecords(ctx context.Context, indent pp.Indent, //nolint:exhaustivestruct // Other fields are intentionally unspecified rs, err := h.cf.DNSRecords(ctx, zone, cloudflare.DNSRecord{ - Name: domain.String(), + Name: domain.ToASCII(), Type: ipNet.RecordType(), }) if err != nil { - pp.Printf(indent, pp.EmojiError, "Failed to retrieve records of %s: %v", domain, err) + pp.Printf(indent, pp.EmojiError, "Failed to retrieve records of %q: %v", domain.Describe(), err) return nil, false } @@ -165,15 +165,15 @@ func (h *CloudflareHandle) DeleteRecord(ctx context.Context, indent pp.Indent, } if err := h.cf.DeleteDNSRecord(ctx, zone, id); err != nil { - pp.Printf(indent, pp.EmojiError, "Failed to delete a stale %s record of %s (ID: %s): %v", - ipNet.RecordType(), domain, id, err) + pp.Printf(indent, pp.EmojiError, "Failed to delete a stale %s record of %q (ID: %s): %v", + ipNet.RecordType(), domain.Describe(), id, err) - h.cache.listRecords[ipNet].Delete(domain.String()) + h.cache.listRecords[ipNet].Delete(domain.ToASCII()) return false } - if rmap, found := h.cache.listRecords[ipNet].Get(domain.String()); found { + if rmap, found := h.cache.listRecords[ipNet].Get(domain.ToASCII()); found { delete(rmap.(map[string]net.IP), id) } @@ -189,21 +189,21 @@ func (h *CloudflareHandle) UpdateRecord(ctx context.Context, indent pp.Indent, //nolint:exhaustivestruct // Other fields are intentionally omitted payload := cloudflare.DNSRecord{ - Name: domain.String(), + Name: domain.ToASCII(), Type: ipNet.RecordType(), Content: ip.String(), } if err := h.cf.UpdateDNSRecord(ctx, zone, id, payload); err != nil { - pp.Printf(indent, pp.EmojiError, "Failed to update a stale %s record of %s (ID: %s): %v", - ipNet.RecordType(), domain, id, err) + pp.Printf(indent, pp.EmojiError, "Failed to update a stale %s record of %q (ID: %s): %v", + ipNet.RecordType(), domain.Describe(), id, err) - h.cache.listRecords[ipNet].Delete(domain.String()) + h.cache.listRecords[ipNet].Delete(domain.ToASCII()) return false } - if rmap, found := h.cache.listRecords[ipNet].Get(domain.String()); found { + if rmap, found := h.cache.listRecords[ipNet].Get(domain.ToASCII()); found { rmap.(map[string]net.IP)[id] = ip } @@ -219,7 +219,7 @@ func (h *CloudflareHandle) CreateRecord(ctx context.Context, indent pp.Indent, //nolint:exhaustivestruct // Other fields are intentionally omitted payload := cloudflare.DNSRecord{ - Name: domain.String(), + Name: domain.ToASCII(), Type: ipNet.RecordType(), Content: ip.String(), TTL: ttl, @@ -228,14 +228,15 @@ func (h *CloudflareHandle) CreateRecord(ctx context.Context, indent pp.Indent, res, err := h.cf.CreateDNSRecord(ctx, zone, payload) if err != nil { - pp.Printf(indent, pp.EmojiError, "Failed to add a new %s record of %s: %v", ipNet.RecordType(), domain, err) + pp.Printf(indent, pp.EmojiError, "Failed to add a new %s record of %q: %v", + ipNet.RecordType(), domain.Describe(), err) - h.cache.listRecords[ipNet].Delete(domain.String()) + h.cache.listRecords[ipNet].Delete(domain.ToASCII()) return "", false } - if rmap, found := h.cache.listRecords[ipNet].Get(domain.String()); found { + if rmap, found := h.cache.listRecords[ipNet].Get(domain.ToASCII()); found { rmap.(map[string]net.IP)[res.Result.ID] = ip } diff --git a/internal/api/fqdn.go b/internal/api/fqdn.go index 34b58258..e5752075 100644 --- a/internal/api/fqdn.go +++ b/internal/api/fqdn.go @@ -23,17 +23,26 @@ type FQDN string // safelyToUnicode takes an ASCII form and returns the Unicode form // when the round trip gives the same ASCII form back without errors. // Otherwise, the input ASCII form is returned. -func safelyToUnicode(ascii string) string { +func safelyToUnicode(ascii string) (string, bool) { unicode, errToA := profile.ToUnicode(ascii) roundTrip, errToU := profile.ToASCII(unicode) if errToA != nil || errToU != nil || roundTrip != ascii { - return ascii + return ascii, false } - return unicode + return unicode, true } -func (f FQDN) String() string { return string(f) } +func (f FQDN) ToASCII() string { return string(f) } + +func (f FQDN) Describe() string { + best, ok := safelyToUnicode(string(f)) + if !ok { + return string(f) + } + + return best +} // NewFQDN normalizes a domain to its ASCII form and then stores // the normalized domain in its Unicode form when the round trip @@ -45,7 +54,7 @@ func NewFQDN(domain string) (FQDN, error) { // Remove the final dot for consistency normalized = strings.TrimSuffix(normalized, ".") - return FQDN(safelyToUnicode(normalized)), err + return FQDN(normalized), err } func SortFQDNs(s []FQDN) { sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) } @@ -58,7 +67,7 @@ type FQDNSplitter struct { func NewFQDNSplitter(domain FQDN) *FQDNSplitter { return &FQDNSplitter{ - domain: domain.String(), + domain: domain.ToASCII(), cursor: 0, exhausted: false, } diff --git a/internal/config/config.go b/internal/config/config.go index 3f70738c..ba0bf3ae 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -225,7 +225,7 @@ func (c *Config) checkUselessDomains(indent pp.Indent) { // calculate domainSet[IP4], domainSet[IP6], and unionSet for ipNet, domains := range c.Domains { for _, domain := range domains { - domainString := domain.String() + domainString := domain.ToASCII() domainSet[ipNet][domainString] = true unionSet[domainString] = true } diff --git a/internal/updator/updator.go b/internal/updator/updator.go index ea07f7a3..ee4f66d0 100644 --- a/internal/updator/updator.go +++ b/internal/updator/updator.go @@ -37,7 +37,7 @@ func Do(ctx context.Context, indent pp.Indent, quiet quiet.Quiet, args *Args) bo rs, ok := args.Handle.ListRecords(ctx, indent, args.Domain, args.IPNetwork) if !ok { - pp.Printf(indent, pp.EmojiError, "Failed to update %s records of %s.", recordType, args.Domain) + pp.Printf(indent, pp.EmojiError, "Failed to update %s records of %q.", recordType, args.Domain.Describe()) return false } @@ -60,7 +60,8 @@ func Do(ctx context.Context, indent pp.Indent, quiet quiet.Quiet, args *Args) bo if uptodate && len(matchedIDs) == 0 && len(unmatchedIDs) == 0 { if !quiet { - pp.Printf(indent, pp.EmojiAlreadyDone, "The %s records of %s are already up to date.", recordType, args.Domain) + pp.Printf(indent, pp.EmojiAlreadyDone, "The %s records of %q are already up to date.", + recordType, args.Domain.Describe()) } return true @@ -72,7 +73,7 @@ func Do(ctx context.Context, indent pp.Indent, quiet quiet.Quiet, args *Args) bo for i, id := range unmatchedIDs { if args.Handle.UpdateRecord(ctx, indent, args.Domain, args.IPNetwork, id, args.IP) { pp.Printf(indent, pp.EmojiUpdateRecord, - "Updated a stale %s record of %s (ID: %s).", recordType, args.Domain, id) + "Updated a stale %s record of %q (ID: %s).", recordType, args.Domain.Describe(), id) uptodate = true numUnmatched-- @@ -82,7 +83,7 @@ func Do(ctx context.Context, indent pp.Indent, quiet quiet.Quiet, args *Args) bo } else { if args.Handle.DeleteRecord(ctx, indent, args.Domain, args.IPNetwork, id) { pp.Printf(indent, pp.EmojiDelRecord, - "Deleted a stale %s record of %s instead (ID: %s).", recordType, args.Domain, id) + "Deleted a stale %s record of %q instead (ID: %s).", recordType, args.Domain.Describe(), id) numUnmatched-- } continue @@ -96,7 +97,7 @@ func Do(ctx context.Context, indent pp.Indent, quiet quiet.Quiet, args *Args) bo if id, ok := args.Handle.CreateRecord(ctx, indent, args.Domain, args.IPNetwork, args.IP, args.TTL.Int(), args.Proxied); ok { pp.Printf(indent, pp.EmojiAddRecord, - "Added a new %s record of %s (ID: %s).", recordType, args.Domain, id) + "Added a new %s record of %q (ID: %s).", recordType, args.Domain.Describe(), id) uptodate = true } } @@ -104,7 +105,7 @@ func Do(ctx context.Context, indent pp.Indent, quiet quiet.Quiet, args *Args) bo for _, id := range unmatchedIDs { if args.Handle.DeleteRecord(ctx, indent, args.Domain, args.IPNetwork, id) { pp.Printf(indent, pp.EmojiDelRecord, - "Deleted a stale %s record of %s (ID: %s).", recordType, args.Domain, id) + "Deleted a stale %s record of %q (ID: %s).", recordType, args.Domain.Describe(), id) numUnmatched-- } } @@ -112,13 +113,13 @@ func Do(ctx context.Context, indent pp.Indent, quiet quiet.Quiet, args *Args) bo for _, id := range matchedIDs { if args.Handle.DeleteRecord(ctx, indent, args.Domain, args.IPNetwork, id) { pp.Printf(indent, pp.EmojiDelRecord, - "Deleted a duplicate %s record of %s (ID: %s).", recordType, args.Domain, id) + "Deleted a duplicate %s record of %q (ID: %s).", recordType, args.Domain.Describe(), id) } } if !uptodate || numUnmatched > 0 { pp.Printf(indent, pp.EmojiError, - "Failed to update %s records of %s.", recordType, args.Domain) + "Failed to update %s records of %q.", recordType, args.Domain.Describe()) return false } From cf7ea832a62054ae5848e32e1b5884c7fa41ac78 Mon Sep 17 00:00:00 2001 From: favonia Date: Thu, 5 Aug 2021 18:14:38 -0500 Subject: [PATCH 3/5] test(api): update the test of api --- README.markdown | 4 +- internal/api/cloudflare_test.go | 2 +- internal/api/fqdn_test.go | 99 ++++++++++++++++++++++++--------- 3 files changed, 77 insertions(+), 28 deletions(-) diff --git a/README.markdown b/README.markdown index c7731980..ca255309 100644 --- a/README.markdown +++ b/README.markdown @@ -16,8 +16,8 @@ A small and fast DDNS updater for Cloudflare. 🔸 Effective GID: 1000 🔸 Supplementary GIDs: (empty) 🔇 Quiet mode enabled. -🐣 Added a new A record of …… (ID: ……). -🐣 Added a new AAAA record of …… (ID: ……). +🐣 Added a new A record of "……" (ID: ……). +🐣 Added a new AAAA record of "……" (ID: ……). ``` ## 📜 Highlights diff --git a/internal/api/cloudflare_test.go b/internal/api/cloudflare_test.go index 71baf9c0..f3603d8b 100644 --- a/internal/api/cloudflare_test.go +++ b/internal/api/cloudflare_test.go @@ -19,7 +19,7 @@ import ( // mockID returns a hex string of length 32, suitable for all kinds of IDs // used in the Cloudflare API. func mockID(seed string) string { - arr := sha512.Sum512([]byte(seed)) //nolint:gosec + arr := sha512.Sum512([]byte(seed)) return hex.EncodeToString(arr[:16]) } diff --git a/internal/api/fqdn_test.go b/internal/api/fqdn_test.go index eafe8b51..774fdb48 100644 --- a/internal/api/fqdn_test.go +++ b/internal/api/fqdn_test.go @@ -11,17 +11,66 @@ import ( "github.com/favonia/cloudflare-ddns/internal/api" ) -func TestFQDNString(t *testing.T) { +func TestFQDNToASCII(t *testing.T) { t.Parallel() require.NoError(t, quick.Check( func(s string) bool { - return api.FQDN(s).String() == s + return api.FQDN(s).ToASCII() == s }, nil, )) } +func TestFQDNDescribe(t *testing.T) { + t.Parallel() + for _, tc := range [...]struct { + input string + expected string + ok bool + errString string + }{ + // The following examples were adapted from https://unicode.org/cldr/utility/idna.jsp + {"fass.de", "fass.de", true, ""}, + {"xn--fa-hia.de", "faß.de", true, ""}, + {"xn--f-qfao.de", "fäß.de", true, ""}, + {"xn--fa-hia.de", "faß.de", true, ""}, + {"xn--yzg.com", "₹.com", true, ""}, + {"xn--n00d.com", "𑀓.com", true, ""}, + {"xn--a.com", "xn--a.com", false, "idna: disallowed rune U+0080"}, + {"xn--a.com", "xn--a.com", false, "idna: invalid label \"\\u0080\""}, + {"xn--ab-j1t", "xn--ab-j1t", false, "idna: invalid label \"a\\u200cb\""}, + {"xn--ab-j1t", "xn--ab-j1t", false, "idna: invalid label \"a\\u200cb\""}, + {"xn--bb-eka.at", "öbb.at", true, ""}, + {"xn--og-09a.de", "ȡog.de", true, ""}, + {"xn--53h.de", "☕.de", true, ""}, + {"xn--iny-zx5a.de", "i♥ny.de", true, ""}, + {"xn--abc-rs4b422ycvb.co.jp", "abc・日本.co.jp", true, ""}, + {"xn--wgv71a.co.jp", "日本.co.jp", true, ""}, + {"xn--co-wuw5954azlb.jp", "xn--co-wuw5954azlb.jp", false, "idna: disallowed rune U+2488"}, + {"xn--x-xbb7i.de", "x̧́.de", true, ""}, + {"xn--wxaijb9b.gr", "σόλος.gr", true, ""}, + { + "xn--wxaikc6b.xn--gr-gtd9a1b0g.de", + "xn--wxaikc6b.xn--gr-gtd9a1b0g.de", + false, "idna: invalid label \"σόλοσ.grعربي.de\"", + }, + {"xn--ngbrx4e.de", "عربي.de", true, ""}, + {"xn--mgba3gch31f.de", "نامهای.de", true, ""}, + {"xn--mgba3gch31f060k.de", "نامه\u200cای.de", true, ""}, + // some other test cases + {"xn--a.xn--a.xn--a.com", "xn--a.xn--a.xn--a.com", false, "idna: invalid label \"\\u0080\""}, + {"a.com....", "a.com....", true, ""}, + {"a.com", "a.com", true, ""}, + } { + tc := tc + t.Run(tc.input, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.expected, api.FQDN(tc.input).Describe()) + }) + } +} + func TestNewFQDN(t *testing.T) { t.Parallel() for _, tc := range [...]struct { @@ -30,40 +79,40 @@ func TestNewFQDN(t *testing.T) { ok bool errString string }{ - // The following examples were reproduced from https://unicode.org/cldr/utility/idna.jsp + // The following examples were adapted from https://unicode.org/cldr/utility/idna.jsp {"fass.de", "fass.de", true, ""}, - {"faß.de", "faß.de", true, ""}, - {"fäß.de", "fäß.de", true, ""}, - {"xn--fa-hia.de", "faß.de", true, ""}, - {"₹.com", "₹.com", true, ""}, - {"𑀓.com", "𑀓.com", true, ""}, + {"faß.de", "xn--fa-hia.de", true, ""}, + {"fäß.de", "xn--f-qfao.de", true, ""}, + {"xn--fa-hia.de", "xn--fa-hia.de", true, ""}, + {"₹.com", "xn--yzg.com", true, ""}, + {"𑀓.com", "xn--n00d.com", true, ""}, {"\u0080.com", "xn--a.com", false, "idna: disallowed rune U+0080"}, {"xn--a.com", "xn--a.com", false, "idna: invalid label \"\\u0080\""}, {"a\u200Cb", "xn--ab-j1t", false, "idna: invalid label \"a\\u200cb\""}, {"xn--ab-j1t", "xn--ab-j1t", false, "idna: invalid label \"a\\u200cb\""}, - {"öbb.at", "öbb.at", true, ""}, - {"ÖBB.at", "öbb.at", true, ""}, - {"ÖBB.at", "öbb.at", true, ""}, - {"ȡog.de", "ȡog.de", true, ""}, - {"☕.de", "☕.de", true, ""}, - {"I♥NY.de", "i♥ny.de", true, ""}, - {"ABC・日本.co.jp", "abc・日本.co.jp", true, ""}, - {"日本。co。jp", "日本.co.jp", true, ""}, - {"日本。co.jp", "日本.co.jp", true, ""}, + {"\u00F6bb.at", "xn--bb-eka.at", true, ""}, + {"o\u0308bb.at", "xn--bb-eka.at", true, ""}, + {"\u00D6BB.at", "xn--bb-eka.at", true, ""}, + {"O\u0308BB.at", "xn--bb-eka.at", true, ""}, + {"ȡog.de", "xn--og-09a.de", true, ""}, + {"☕.de", "xn--53h.de", true, ""}, + {"I♥NY.de", "xn--iny-zx5a.de", true, ""}, + {"ABC・日本.co.jp", "xn--abc-rs4b422ycvb.co.jp", true, ""}, + {"日本。co。jp", "xn--wgv71a.co.jp", true, ""}, + {"日本。co.jp", "xn--wgv71a.co.jp", true, ""}, {"日本⒈co.jp", "xn--co-wuw5954azlb.jp", false, "idna: disallowed rune U+2488"}, - {"x\u0327\u0301.de", "x̧́.de", true, ""}, - {"x\u0301\u0327.de", "x̧́.de", true, ""}, - {"σόλος.gr", "σόλος.gr", true, ""}, - {"Σόλος.gr", "σόλος.gr", true, ""}, + {"x\u0327\u0301.de", "xn--x-xbb7i.de", true, ""}, + {"x\u0301\u0327.de", "xn--x-xbb7i.de", true, ""}, + {"σόλος.gr", "xn--wxaijb9b.gr", true, ""}, + {"Σόλος.gr", "xn--wxaijb9b.gr", true, ""}, {"ΣΌΛΟΣ.grﻋﺮﺑﻲ.de", "xn--wxaikc6b.xn--gr-gtd9a1b0g.de", false, "idna: invalid label \"σόλοσ.grعربي.de\""}, - {"عربي.de", "عربي.de", true, ""}, - {"نامهای.de", "نامهای.de", true, ""}, - {"نامه\u200Cای.de", "نامه\u200cای.de", true, ""}, + {"عربي.de", "xn--ngbrx4e.de", true, ""}, + {"نامهای.de", "xn--mgba3gch31f.de", true, ""}, + {"نامه\u200Cای.de", "xn--mgba3gch31f060k.de", true, ""}, // some other test cases {"xn--a.xn--a.xn--a.com", "xn--a.xn--a.xn--a.com", false, "idna: invalid label \"\\u0080\""}, {"a.com...。", "a.com...", true, ""}, {"..。..a.com", "a.com", true, ""}, - {"O\u0308", "\u00F6", true, ""}, } { tc := tc t.Run(tc.input, func(t *testing.T) { From 1274d3239e4a50a96450078e0582d0847c04cc8b Mon Sep 17 00:00:00 2001 From: favonia Date: Thu, 5 Aug 2021 20:08:39 -0500 Subject: [PATCH 4/5] docs(api): remove the bogus comment --- internal/api/fqdn_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/api/fqdn_test.go b/internal/api/fqdn_test.go index 774fdb48..a51d4fca 100644 --- a/internal/api/fqdn_test.go +++ b/internal/api/fqdn_test.go @@ -156,7 +156,6 @@ func TestSortFQDNSplitter(t *testing.T) { input string expected []string }{ - // The following examples were adapted from https://unicode.org/cldr/utility/idna.jsp {"...", ss{"...", "..", ".", ""}}, {"aaa...", ss{"aaa...", "..", ".", ""}}, {".aaa..", ss{".aaa..", "aaa..", ".", ""}}, From 20d4fe1cee40ca815f73119828d6447c961c8c94 Mon Sep 17 00:00:00 2001 From: favonia Date: Thu, 5 Aug 2021 20:15:28 -0500 Subject: [PATCH 5/5] test(api): rename test --- internal/api/cloudflare_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/cloudflare_test.go b/internal/api/cloudflare_test.go index f3603d8b..15c5771b 100644 --- a/internal/api/cloudflare_test.go +++ b/internal/api/cloudflare_test.go @@ -28,7 +28,7 @@ const ( mockAccount = "account456" ) -func TestCloudflareAuthNewSuccess(t *testing.T) { +func TestCloudflareAuthNewValid(t *testing.T) { t.Parallel() mux := http.NewServeMux()