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 }