1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package main_test
19
20 import (
21 "context"
22 "crypto/tls"
23 "crypto/x509"
24 "errors"
25 "fmt"
26 "io"
27 "net"
28 "net/http"
29 "net/url"
30 "os"
31 "os/exec"
32 "strings"
33 "testing"
34 "time"
35
36 "cloud.google.com/go/internal/testutil"
37 "cloud.google.com/go/storage"
38 "golang.org/x/oauth2"
39 "google.golang.org/api/option"
40 )
41
42 const initial = "initial state"
43
44 func TestIntegration_HTTPR(t *testing.T) {
45 if testing.Short() {
46 t.Skip("Integration tests skipped in short mode")
47 }
48 if testutil.ProjID() == "" {
49 t.Fatal("set GCLOUD_TESTS_GOLANG_PROJECT_ID and GCLOUD_TESTS_GOLANG_KEY")
50 }
51
52 f, err := os.CreateTemp("", "httpreplay")
53 if err != nil {
54 t.Fatal(err)
55 }
56 replayFilename := f.Name()
57 if err := f.Close(); err != nil {
58 t.Fatal(err)
59 }
60 defer os.Remove(replayFilename)
61
62 if err := exec.Command("go", "build").Run(); err != nil {
63 t.Fatalf("running 'go build': %v", err)
64 }
65 defer os.Remove("./httpr")
66 want := runRecord(t, replayFilename)
67 got := runReplay(t, replayFilename)
68 if got != want {
69 t.Fatalf("got %q, want %q", got, want)
70 }
71 }
72
73 func runRecord(t *testing.T, filename string) string {
74 cmd, tr, cport, err := start(t, "-record", filename)
75 if err != nil {
76 t.Fatal(err)
77 }
78 defer stop(t, cmd)
79
80 ctx := context.Background()
81 hc := &http.Client{
82 Transport: &oauth2.Transport{
83 Base: tr,
84 Source: testutil.TokenSource(ctx, storage.ScopeFullControl),
85 },
86 }
87 res, err := http.Post(
88 fmt.Sprintf("http://localhost:%s/initial", cport),
89 "text/plain",
90 strings.NewReader(initial))
91 if err != nil {
92 t.Fatal(err)
93 }
94 if res.StatusCode != 200 {
95 t.Fatalf("from POST: %s", res.Status)
96 }
97 info, err := getBucketInfo(ctx, hc)
98 if err != nil {
99 t.Fatal(err)
100 }
101 return info
102 }
103
104 func runReplay(t *testing.T, filename string) string {
105 cmd, tr, cport, err := start(t, "-replay", filename)
106 if err != nil {
107 t.Fatal(err)
108 }
109 defer stop(t, cmd)
110
111 hc := &http.Client{Transport: tr}
112 res, err := http.Get(fmt.Sprintf("http://localhost:%s/initial", cport))
113 if err != nil {
114 t.Fatal(err)
115 }
116 if res.StatusCode != 200 {
117 t.Fatalf("from GET: %s", res.Status)
118 }
119 bytes, err := io.ReadAll(res.Body)
120 res.Body.Close()
121 if err != nil {
122 t.Fatal(err)
123 }
124 if got, want := string(bytes), initial; got != want {
125 t.Errorf("initial: got %q, want %q", got, want)
126 }
127 info, err := getBucketInfo(context.Background(), hc)
128 if err != nil {
129 t.Fatal(err)
130 }
131 return info
132 }
133
134
135
136
137 func start(t *testing.T, modeFlag, filename string) (*exec.Cmd, *http.Transport, string, error) {
138 pport, err := pickPort()
139 if err != nil {
140 return nil, nil, "", err
141 }
142 cport, err := pickPort()
143 if err != nil {
144 return nil, nil, "", err
145 }
146 cmd := exec.Command("./httpr",
147 "-port", pport,
148 "-control-port", cport,
149 modeFlag,
150 filename,
151 "-debug-headers",
152 )
153 if err := cmd.Start(); err != nil {
154 return nil, nil, "", err
155 }
156
157 serverUp := false
158 for i := 0; i < 10; i++ {
159 if conn, err := net.Dial("tcp", "localhost:"+cport); err == nil {
160 conn.Close()
161 serverUp = true
162 break
163 }
164 time.Sleep(time.Second)
165 }
166 if !serverUp {
167 return nil, nil, "", errors.New("server never came up")
168 }
169 tr, err := proxyTransport(t, pport, cport)
170 if err != nil {
171 return nil, nil, "", err
172 }
173 return cmd, tr, cport, nil
174 }
175
176 func stop(t *testing.T, cmd *exec.Cmd) {
177 if err := cmd.Process.Signal(os.Interrupt); err != nil {
178 t.Fatal(err)
179 }
180 }
181
182
183 func pickPort() (string, error) {
184 l, err := net.Listen("tcp", ":0")
185 if err != nil {
186 return "", err
187 }
188 addr := l.Addr().String()
189 _, port, err := net.SplitHostPort(addr)
190 if err != nil {
191 return "", err
192 }
193 l.Close()
194 return port, nil
195 }
196
197 func proxyTransport(t *testing.T, pport, cport string) (*http.Transport, error) {
198 var caCert []byte
199 var err error
200
201 testutil.Retry(t, 10, 1*time.Second, func(r *testutil.R) {
202 if caCert, err = getBody(fmt.Sprintf("http://localhost:%s/authority.cer", cport)); err != nil {
203 r.Errorf("proxyTransport Retry: %v", err)
204 }
205 })
206 if err != nil {
207 return nil, err
208 }
209 caCertPool := x509.NewCertPool()
210 if !caCertPool.AppendCertsFromPEM([]byte(caCert)) {
211 return nil, errors.New("bad CA Cert")
212 }
213 return &http.Transport{
214 Proxy: http.ProxyURL(&url.URL{Host: "localhost:" + pport}),
215 TLSClientConfig: &tls.Config{RootCAs: caCertPool},
216 }, nil
217 }
218
219 func getBucketInfo(ctx context.Context, hc *http.Client) (string, error) {
220 ctx, cc := context.WithTimeout(ctx, 10*time.Second)
221 defer cc()
222
223 client, err := storage.NewClient(ctx, option.WithHTTPClient(hc))
224 if err != nil {
225 return "", err
226 }
227 defer client.Close()
228 b := client.Bucket(testutil.ProjID())
229 attrs, err := b.Attrs(ctx)
230 if err != nil {
231 return "", err
232 }
233 return fmt.Sprintf("name:%s reqpays:%v location:%s sclass:%s",
234 attrs.Name, attrs.RequesterPays, attrs.Location, attrs.StorageClass), nil
235 }
236
237 func getBody(url string) ([]byte, error) {
238 res, err := http.Get(url)
239 if err != nil {
240 return nil, err
241 }
242 if res.StatusCode != 200 {
243 return nil, fmt.Errorf("response: %s", res.Status)
244 }
245 defer res.Body.Close()
246 return io.ReadAll(res.Body)
247 }
248
View as plain text