github.com/salme4/terraform@v0.11.12-beta1/svchost/disco/host_test.go (about)

     1  package disco
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"net/url"
     8  	"os"
     9  	"path"
    10  	"reflect"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  )
    15  
    16  func TestHostServiceURL(t *testing.T) {
    17  	baseURL, _ := url.Parse("https://example.com/disco/foo.json")
    18  	host := Host{
    19  		discoURL: baseURL,
    20  		hostname: "test-server",
    21  		services: map[string]interface{}{
    22  			"absolute.v1":         "http://example.net/foo/bar",
    23  			"absolutewithport.v1": "http://example.net:8080/foo/bar",
    24  			"relative.v1":         "./stu/",
    25  			"rootrelative.v1":     "/baz",
    26  			"protorelative.v1":    "//example.net/",
    27  			"withfragment.v1":     "http://example.org/#foo",
    28  			"querystring.v1":      "https://example.net/baz?foo=bar",
    29  			"nothttp.v1":          "ftp://127.0.0.1/pub/",
    30  			"invalid.v1":          "***not A URL at all!:/<@@@@>***",
    31  		},
    32  	}
    33  
    34  	tests := []struct {
    35  		ID   string
    36  		want string
    37  		err  string
    38  	}{
    39  		{"absolute.v1", "http://example.net/foo/bar", ""},
    40  		{"absolutewithport.v1", "http://example.net:8080/foo/bar", ""},
    41  		{"relative.v1", "https://example.com/disco/stu/", ""},
    42  		{"rootrelative.v1", "https://example.com/baz", ""},
    43  		{"protorelative.v1", "https://example.net/", ""},
    44  		{"withfragment.v1", "http://example.org/", ""},
    45  		{"querystring.v1", "https://example.net/baz?foo=bar", ""},
    46  		{"nothttp.v1", "<nil>", "unsupported scheme"},
    47  		{"invalid.v1", "<nil>", "Failed to parse service URL"},
    48  	}
    49  
    50  	for _, test := range tests {
    51  		t.Run(test.ID, func(t *testing.T) {
    52  			url, err := host.ServiceURL(test.ID)
    53  			if (err != nil || test.err != "") &&
    54  				(err == nil || !strings.Contains(err.Error(), test.err)) {
    55  				t.Fatalf("unexpected service URL error: %s", err)
    56  			}
    57  
    58  			var got string
    59  			if url != nil {
    60  				got = url.String()
    61  			} else {
    62  				got = "<nil>"
    63  			}
    64  
    65  			if got != test.want {
    66  				t.Errorf("wrong result\ngot:  %s\nwant: %s", got, test.want)
    67  			}
    68  		})
    69  	}
    70  }
    71  
    72  func TestVersionConstrains(t *testing.T) {
    73  	baseURL, _ := url.Parse("https://example.com/disco/foo.json")
    74  
    75  	t.Run("exact service version is provided", func(t *testing.T) {
    76  		portStr, close := testVersionsServer(func(w http.ResponseWriter, r *http.Request) {
    77  			resp := []byte(`
    78  {
    79  	"service": "%s",
    80  	"product": "%s",
    81  	"minimum": "0.11.8",
    82  	"maximum": "0.12.0"
    83  }`)
    84  			// Add the requested service and product to the response.
    85  			service := path.Base(r.URL.Path)
    86  			product := r.URL.Query().Get("product")
    87  			resp = []byte(fmt.Sprintf(string(resp), service, product))
    88  
    89  			w.Header().Add("Content-Type", "application/json")
    90  			w.Header().Add("Content-Length", strconv.Itoa(len(resp)))
    91  			w.Write(resp)
    92  		})
    93  		defer close()
    94  
    95  		host := Host{
    96  			discoURL:  baseURL,
    97  			hostname:  "test-server",
    98  			transport: httpTransport,
    99  			services: map[string]interface{}{
   100  				"thingy.v1":   "/api/v1/",
   101  				"thingy.v2":   "/api/v2/",
   102  				"versions.v1": "https://localhost" + portStr + "/v1/versions/",
   103  			},
   104  		}
   105  
   106  		expected := &Constraints{
   107  			Service: "thingy.v1",
   108  			Product: "terraform",
   109  			Minimum: "0.11.8",
   110  			Maximum: "0.12.0",
   111  		}
   112  
   113  		actual, err := host.VersionConstraints("thingy.v1", "terraform")
   114  		if err != nil {
   115  			t.Fatalf("unexpected version constraints error: %s", err)
   116  		}
   117  
   118  		if !reflect.DeepEqual(actual, expected) {
   119  			t.Fatalf("expected %#v, got: %#v", expected, actual)
   120  		}
   121  	})
   122  
   123  	t.Run("service provided with different versions", func(t *testing.T) {
   124  		portStr, close := testVersionsServer(func(w http.ResponseWriter, r *http.Request) {
   125  			resp := []byte(`
   126  {
   127  	"service": "%s",
   128  	"product": "%s",
   129  	"minimum": "0.11.8",
   130  	"maximum": "0.12.0"
   131  }`)
   132  			// Add the requested service and product to the response.
   133  			service := path.Base(r.URL.Path)
   134  			product := r.URL.Query().Get("product")
   135  			resp = []byte(fmt.Sprintf(string(resp), service, product))
   136  
   137  			w.Header().Add("Content-Type", "application/json")
   138  			w.Header().Add("Content-Length", strconv.Itoa(len(resp)))
   139  			w.Write(resp)
   140  		})
   141  		defer close()
   142  
   143  		host := Host{
   144  			discoURL:  baseURL,
   145  			hostname:  "test-server",
   146  			transport: httpTransport,
   147  			services: map[string]interface{}{
   148  				"thingy.v2":   "/api/v2/",
   149  				"thingy.v3":   "/api/v3/",
   150  				"versions.v1": "https://localhost" + portStr + "/v1/versions/",
   151  			},
   152  		}
   153  
   154  		expected := &Constraints{
   155  			Service: "thingy.v3",
   156  			Product: "terraform",
   157  			Minimum: "0.11.8",
   158  			Maximum: "0.12.0",
   159  		}
   160  
   161  		actual, err := host.VersionConstraints("thingy.v1", "terraform")
   162  		if err != nil {
   163  			t.Fatalf("unexpected version constraints error: %s", err)
   164  		}
   165  
   166  		if !reflect.DeepEqual(actual, expected) {
   167  			t.Fatalf("expected %#v, got: %#v", expected, actual)
   168  		}
   169  	})
   170  
   171  	t.Run("service not provided", func(t *testing.T) {
   172  		host := Host{
   173  			discoURL:  baseURL,
   174  			hostname:  "test-server",
   175  			transport: httpTransport,
   176  			services: map[string]interface{}{
   177  				"versions.v1": "https://localhost/v1/versions/",
   178  			},
   179  		}
   180  
   181  		_, err := host.VersionConstraints("thingy.v1", "terraform")
   182  		if _, ok := err.(*ErrServiceNotProvided); !ok {
   183  			t.Fatalf("expected service not provided error, got: %v", err)
   184  		}
   185  	})
   186  
   187  	t.Run("versions service returns a 404", func(t *testing.T) {
   188  		portStr, close := testVersionsServer(nil)
   189  		defer close()
   190  
   191  		host := Host{
   192  			discoURL:  baseURL,
   193  			hostname:  "test-server",
   194  			transport: httpTransport,
   195  			services: map[string]interface{}{
   196  				"thingy.v1":   "/api/v1/",
   197  				"versions.v1": "https://localhost" + portStr + "/v1/non-existent/",
   198  			},
   199  		}
   200  
   201  		_, err := host.VersionConstraints("thingy.v1", "terraform")
   202  		if _, ok := err.(*ErrNoVersionConstraints); !ok {
   203  			t.Fatalf("expected service not provided error, got: %v", err)
   204  		}
   205  	})
   206  
   207  	t.Run("checkpoint is disabled", func(t *testing.T) {
   208  		if err := os.Setenv("CHECKPOINT_DISABLE", "1"); err != nil {
   209  			t.Fatalf("unexpected error: %v", err)
   210  		}
   211  		defer os.Unsetenv("CHECKPOINT_DISABLE")
   212  
   213  		host := Host{
   214  			discoURL:  baseURL,
   215  			hostname:  "test-server",
   216  			transport: httpTransport,
   217  			services: map[string]interface{}{
   218  				"thingy.v1":   "/api/v1/",
   219  				"versions.v1": "https://localhost/v1/versions/",
   220  			},
   221  		}
   222  
   223  		_, err := host.VersionConstraints("thingy.v1", "terraform")
   224  		if _, ok := err.(*ErrNoVersionConstraints); !ok {
   225  			t.Fatalf("expected service not provided error, got: %v", err)
   226  		}
   227  	})
   228  
   229  	t.Run("versions service not discovered", func(t *testing.T) {
   230  		host := Host{
   231  			discoURL:  baseURL,
   232  			hostname:  "test-server",
   233  			transport: httpTransport,
   234  			services: map[string]interface{}{
   235  				"thingy.v1": "/api/v1/",
   236  			},
   237  		}
   238  
   239  		_, err := host.VersionConstraints("thingy.v1", "terraform")
   240  		if _, ok := err.(*ErrServiceNotProvided); !ok {
   241  			t.Fatalf("expected service not provided error, got: %v", err)
   242  		}
   243  	})
   244  
   245  	t.Run("versions service version not discovered", func(t *testing.T) {
   246  		host := Host{
   247  			discoURL:  baseURL,
   248  			hostname:  "test-server",
   249  			transport: httpTransport,
   250  			services: map[string]interface{}{
   251  				"thingy.v1":   "/api/v1/",
   252  				"versions.v2": "https://localhost/v2/versions/",
   253  			},
   254  		}
   255  
   256  		_, err := host.VersionConstraints("thingy.v1", "terraform")
   257  		if _, ok := err.(*ErrVersionNotSupported); !ok {
   258  			t.Fatalf("expected service not provided error, got: %v", err)
   259  		}
   260  	})
   261  }
   262  
   263  func testVersionsServer(h func(w http.ResponseWriter, r *http.Request)) (portStr string, close func()) {
   264  	server := httptest.NewTLSServer(http.HandlerFunc(
   265  		func(w http.ResponseWriter, r *http.Request) {
   266  			// Test server always returns 404 if the URL isn't what we expect
   267  			if !strings.HasPrefix(r.URL.Path, "/v1/versions/") {
   268  				w.WriteHeader(404)
   269  				w.Write([]byte("not found"))
   270  				return
   271  			}
   272  
   273  			// If the URL is correct then the given hander decides the response
   274  			h(w, r)
   275  		},
   276  	))
   277  
   278  	serverURL, _ := url.Parse(server.URL)
   279  
   280  	portStr = serverURL.Port()
   281  	if portStr != "" {
   282  		portStr = ":" + portStr
   283  	}
   284  
   285  	close = func() {
   286  		server.Close()
   287  	}
   288  
   289  	return portStr, close
   290  }