github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/http/client_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 http
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"net/url"
    15  	"reflect"
    16  	"testing"
    17  
    18  	"github.com/hashicorp/go-retryablehttp"
    19  	"github.com/opentofu/opentofu/internal/states/remote"
    20  )
    21  
    22  func TestHTTPClient_impl(t *testing.T) {
    23  	var _ remote.Client = new(httpClient)
    24  	var _ remote.ClientLocker = new(httpClient)
    25  }
    26  
    27  func TestHTTPClient(t *testing.T) {
    28  	handler := new(testHTTPHandler)
    29  	ts := httptest.NewServer(http.HandlerFunc(handler.Handle))
    30  	defer ts.Close()
    31  
    32  	url, err := url.Parse(ts.URL)
    33  	if err != nil {
    34  		t.Fatalf("Parse: %s", err)
    35  	}
    36  
    37  	// Test basic get/update
    38  	client := &httpClient{URL: url, Client: retryablehttp.NewClient()}
    39  	remote.TestClient(t, client)
    40  
    41  	// test just a single PUT
    42  	p := &httpClient{
    43  		URL:          url,
    44  		UpdateMethod: "PUT",
    45  		Client:       retryablehttp.NewClient(),
    46  	}
    47  	remote.TestClient(t, p)
    48  
    49  	// Test headers
    50  	c := retryablehttp.NewClient()
    51  	c.RequestLogHook = func(_ retryablehttp.Logger, req *http.Request, _ int) {
    52  		// Test user defined header is part of the request
    53  		v := req.Header.Get("user-defined")
    54  		if v != "test" {
    55  			t.Fatalf("Expected header \"user-defined\" with value \"test\", got \"%s\"", v)
    56  		}
    57  
    58  		// Test the content-type header was not overridden
    59  		v = req.Header.Get("content-type")
    60  		if req.Method == "PUT" && v != "application/json" {
    61  			t.Fatalf("Expected header \"content-type\" with value \"application/json\", got \"%s\"", v)
    62  		}
    63  	}
    64  
    65  	p = &httpClient{
    66  		URL:          url,
    67  		UpdateMethod: "PUT",
    68  		Headers: map[string]string{
    69  			"user-defined": "test",
    70  			"content-type": "application/xml",
    71  		},
    72  		Client: c,
    73  	}
    74  
    75  	remote.TestClient(t, p)
    76  
    77  	// Test locking and alternative UpdateMethod
    78  	a := &httpClient{
    79  		URL:          url,
    80  		UpdateMethod: "PUT",
    81  		LockURL:      url,
    82  		LockMethod:   "LOCK",
    83  		UnlockURL:    url,
    84  		UnlockMethod: "UNLOCK",
    85  		Client:       retryablehttp.NewClient(),
    86  	}
    87  	b := &httpClient{
    88  		URL:          url,
    89  		UpdateMethod: "PUT",
    90  		LockURL:      url,
    91  		LockMethod:   "LOCK",
    92  		UnlockURL:    url,
    93  		UnlockMethod: "UNLOCK",
    94  		Client:       retryablehttp.NewClient(),
    95  	}
    96  	remote.TestRemoteLocks(t, a, b)
    97  
    98  	// test a WebDAV-ish backend
    99  	davhandler := new(testHTTPHandler)
   100  	ts = httptest.NewServer(http.HandlerFunc(davhandler.HandleWebDAV))
   101  	defer ts.Close()
   102  
   103  	url, err = url.Parse(ts.URL)
   104  	client = &httpClient{
   105  		URL:          url,
   106  		UpdateMethod: "PUT",
   107  		Client:       retryablehttp.NewClient(),
   108  	}
   109  	if err != nil {
   110  		t.Fatalf("Parse: %s", err)
   111  	}
   112  
   113  	remote.TestClient(t, client) // first time through: 201
   114  	remote.TestClient(t, client) // second time, with identical data: 204
   115  
   116  	// test a broken backend
   117  	brokenHandler := new(testBrokenHTTPHandler)
   118  	brokenHandler.handler = new(testHTTPHandler)
   119  	ts = httptest.NewServer(http.HandlerFunc(brokenHandler.Handle))
   120  	defer ts.Close()
   121  
   122  	url, err = url.Parse(ts.URL)
   123  	if err != nil {
   124  		t.Fatalf("Parse: %s", err)
   125  	}
   126  	client = &httpClient{URL: url, Client: retryablehttp.NewClient()}
   127  	remote.TestClient(t, client)
   128  }
   129  
   130  type testHTTPHandler struct {
   131  	Data   []byte
   132  	Locked bool
   133  }
   134  
   135  func (h *testHTTPHandler) Handle(w http.ResponseWriter, r *http.Request) {
   136  	switch r.Method {
   137  	case "GET":
   138  		w.Write(h.Data)
   139  	case "PUT":
   140  		buf := new(bytes.Buffer)
   141  		if _, err := io.Copy(buf, r.Body); err != nil {
   142  			w.WriteHeader(500)
   143  		}
   144  		w.WriteHeader(201)
   145  		h.Data = buf.Bytes()
   146  	case "POST":
   147  		buf := new(bytes.Buffer)
   148  		if _, err := io.Copy(buf, r.Body); err != nil {
   149  			w.WriteHeader(500)
   150  		}
   151  		h.Data = buf.Bytes()
   152  	case "LOCK":
   153  		if h.Locked {
   154  			w.WriteHeader(423)
   155  		} else {
   156  			h.Locked = true
   157  		}
   158  	case "UNLOCK":
   159  		h.Locked = false
   160  	case "DELETE":
   161  		h.Data = nil
   162  		w.WriteHeader(200)
   163  	default:
   164  		w.WriteHeader(500)
   165  		w.Write([]byte(fmt.Sprintf("Unknown method: %s", r.Method)))
   166  	}
   167  }
   168  
   169  // mod_dav-ish behavior
   170  func (h *testHTTPHandler) HandleWebDAV(w http.ResponseWriter, r *http.Request) {
   171  	switch r.Method {
   172  	case "GET":
   173  		w.Write(h.Data)
   174  	case "PUT":
   175  		buf := new(bytes.Buffer)
   176  		if _, err := io.Copy(buf, r.Body); err != nil {
   177  			w.WriteHeader(500)
   178  		}
   179  		if reflect.DeepEqual(h.Data, buf.Bytes()) {
   180  			h.Data = buf.Bytes()
   181  			w.WriteHeader(204)
   182  		} else {
   183  			h.Data = buf.Bytes()
   184  			w.WriteHeader(201)
   185  		}
   186  	case "DELETE":
   187  		h.Data = nil
   188  		w.WriteHeader(200)
   189  	default:
   190  		w.WriteHeader(500)
   191  		w.Write([]byte(fmt.Sprintf("Unknown method: %s", r.Method)))
   192  	}
   193  }
   194  
   195  type testBrokenHTTPHandler struct {
   196  	lastRequestWasBroken bool
   197  	handler              *testHTTPHandler
   198  }
   199  
   200  func (h *testBrokenHTTPHandler) Handle(w http.ResponseWriter, r *http.Request) {
   201  	if h.lastRequestWasBroken {
   202  		h.lastRequestWasBroken = false
   203  		h.handler.Handle(w, r)
   204  	} else {
   205  		h.lastRequestWasBroken = true
   206  		w.WriteHeader(500)
   207  	}
   208  }