github.com/opentofu/opentofu@v1.7.1/internal/registry/regsrc/friendly_host_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package regsrc
     7  
     8  import (
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  func TestFriendlyHost(t *testing.T) {
    14  	tests := []struct {
    15  		name        string
    16  		source      string
    17  		wantHost    string
    18  		wantDisplay string
    19  		wantNorm    string
    20  		wantValid   bool
    21  	}{
    22  		{
    23  			name:        "simple ascii",
    24  			source:      "registry.opentofu.org",
    25  			wantHost:    "registry.opentofu.org",
    26  			wantDisplay: "registry.opentofu.org",
    27  			wantNorm:    "registry.opentofu.org",
    28  			wantValid:   true,
    29  		},
    30  		{
    31  			name:        "mixed-case ascii",
    32  			source:      "Registry.OpenTofu.org",
    33  			wantHost:    "Registry.OpenTofu.org",
    34  			wantDisplay: "registry.opentofu.org", // Display case folded
    35  			wantNorm:    "registry.opentofu.org",
    36  			wantValid:   true,
    37  		},
    38  		{
    39  			name:        "IDN",
    40  			source:      "ʎɹʇsıƃǝɹ.ɯɹoɟɐɹɹǝʇ.io",
    41  			wantHost:    "ʎɹʇsıƃǝɹ.ɯɹoɟɐɹɹǝʇ.io",
    42  			wantDisplay: "ʎɹʇsıƃǝɹ.ɯɹoɟɐɹɹǝʇ.io",
    43  			wantNorm:    "xn--s-fka0wmm0zea7g8b.xn--o-8ta85a3b1dwcda1k.io",
    44  			wantValid:   true,
    45  		},
    46  		{
    47  			name:        "IDN TLD",
    48  			source:      "zhongwen.中国",
    49  			wantHost:    "zhongwen.中国",
    50  			wantDisplay: "zhongwen.中国",
    51  			wantNorm:    "zhongwen.xn--fiqs8s",
    52  			wantValid:   true,
    53  		},
    54  		{
    55  			name:        "IDN Case Folding",
    56  			source:      "Испытание.com",
    57  			wantHost:    "Испытание.com", // Raw input retains case
    58  			wantDisplay: "испытание.com", // Display form is unicode but case-folded
    59  			wantNorm:    "xn--80akhbyknj4f.com",
    60  			wantValid:   true,
    61  		},
    62  		{
    63  			name:        "Punycode is invalid as an input format",
    64  			source:      "xn--s-fka0wmm0zea7g8b.xn--o-8ta85a3b1dwcda1k.io",
    65  			wantHost:    "xn--s-fka0wmm0zea7g8b.xn--o-8ta85a3b1dwcda1k.io",
    66  			wantDisplay: "ʎɹʇsıƃǝɹ.ɯɹoɟɐɹɹǝʇ.io",
    67  			wantNorm:    InvalidHostString,
    68  			wantValid:   false,
    69  		},
    70  		{
    71  			name:        "non-host prefix is left alone",
    72  			source:      "foo/bar/baz",
    73  			wantHost:    "",
    74  			wantDisplay: "",
    75  			wantNorm:    "",
    76  			wantValid:   false,
    77  		},
    78  	}
    79  	for _, tt := range tests {
    80  		// Matrix each test with prefix and total match variants
    81  		for _, sfx := range []string{"", "/", "/foo/bar/baz"} {
    82  			t.Run(tt.name+" suffix:"+sfx, func(t *testing.T) {
    83  				gotHost, gotRest := ParseFriendlyHost(tt.source + sfx)
    84  
    85  				if gotHost == nil {
    86  					if tt.wantHost != "" {
    87  						t.Fatalf("ParseFriendlyHost() gotHost = nil, want %v", tt.wantHost)
    88  					}
    89  					// If we return nil host, the whole input string should be in rest
    90  					if gotRest != (tt.source + sfx) {
    91  						t.Fatalf("ParseFriendlyHost() was nil rest = %s, want %v", gotRest, tt.source+sfx)
    92  					}
    93  					return
    94  				}
    95  
    96  				if tt.wantHost == "" {
    97  					t.Fatalf("ParseFriendlyHost() gotHost.Raw = %v, want nil", gotHost.Raw)
    98  				}
    99  
   100  				if v := gotHost.String(); v != tt.wantHost {
   101  					t.Fatalf("String() = %v, want %v", v, tt.wantHost)
   102  				}
   103  				if v := gotHost.Display(); v != tt.wantDisplay {
   104  					t.Fatalf("Display() = %v, want %v", v, tt.wantDisplay)
   105  				}
   106  				if v := gotHost.Normalized(); v != tt.wantNorm {
   107  					t.Fatalf("Normalized() = %v, want %v", v, tt.wantNorm)
   108  				}
   109  				if v := gotHost.Valid(); v != tt.wantValid {
   110  					t.Fatalf("Valid() = %v, want %v", v, tt.wantValid)
   111  				}
   112  				if gotRest != strings.TrimLeft(sfx, "/") {
   113  					t.Fatalf("ParseFriendlyHost() rest = %v, want %v", gotRest, strings.TrimLeft(sfx, "/"))
   114  				}
   115  
   116  				// Also verify that host compares equal with all the variants.
   117  				if gotHost.Valid() && !gotHost.Equal(&FriendlyHost{Raw: tt.wantDisplay}) {
   118  					t.Fatalf("Equal() should be true for %s and %s", tt.wantHost, tt.wantDisplay)
   119  				}
   120  			})
   121  		}
   122  	}
   123  }
   124  
   125  func TestInvalidHostEquals(t *testing.T) {
   126  	invalid := NewFriendlyHost("NOT_A_HOST_NAME")
   127  	valid := PublicRegistryHost
   128  
   129  	// invalid hosts are not comparable
   130  	if invalid.Equal(invalid) {
   131  		t.Fatal("invalid host names are not comparable")
   132  	}
   133  
   134  	if valid.Equal(invalid) {
   135  		t.Fatalf("%q is not equal to %q", valid, invalid)
   136  	}
   137  
   138  	puny := NewFriendlyHost("xn--s-fka0wmm0zea7g8b.xn--o-8ta85a3b1dwcda1k.io")
   139  	display := NewFriendlyHost("ʎɹʇsıƃǝɹ.ɯɹoɟɐɹɹǝʇ.io")
   140  
   141  	// The pre-normalized host is not a valid source, and therefore not
   142  	// comparable to the display version.
   143  	if display.Equal(puny) {
   144  		t.Fatalf("invalid host %q should not be comparable", puny)
   145  	}
   146  }