github.com/release-engineering/exodus-rsync@v1.11.2/internal/gw/client_publish_errors_test.go (about) 1 package gw 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "strings" 9 "testing" 10 11 "github.com/release-engineering/exodus-rsync/internal/args" 12 "github.com/release-engineering/exodus-rsync/internal/log" 13 ) 14 15 type ( 16 mockReadCloser struct { 17 expectedData []byte 18 expectedErr error 19 } 20 ) 21 22 func (mrc *mockReadCloser) Read(p []byte) (n int, err error) { 23 copy(p, mrc.expectedData) 24 return 0, mrc.expectedErr 25 } 26 func (mrc *mockReadCloser) Close() error { return nil } 27 28 func TestClientPublishErrors(t *testing.T) { 29 cfg := testConfig(t) 30 31 clientIface, err := Package.NewClient(context.Background(), cfg) 32 if clientIface == nil { 33 t.Errorf("failed to create client, err = %v", err) 34 } 35 36 ctx := context.Background() 37 ctx = log.NewContext(ctx, log.Package.NewLogger(args.Config{})) 38 39 gw := newFakeGw(t, clientIface.(*client)) 40 41 t.Run("unable to prepare request", func(t *testing.T) { 42 // Passing a nil context is used here as a way to make http.NewRequestWithContext fail. 43 _, err := clientIface.NewPublish(nil) 44 45 if err == nil { 46 t.Error("Unexpectedly failed to return an error") 47 } 48 }) 49 50 t.Run("low-level error from request", func(t *testing.T) { 51 gw.nextHTTPError = fmt.Errorf("simulated error") 52 53 _, err := clientIface.NewPublish(ctx) 54 55 if err == nil { 56 t.Error("Unexpectedly failed to return an error") 57 } 58 if !strings.Contains(err.Error(), "simulated error") { 59 t.Errorf("Did not get expected error, got: %v", err) 60 } 61 if !strings.Contains(err.Error(), "https://exodus-gw.example.com/env/publish") { 62 t.Errorf("Error did not include URL, got: %v", err) 63 } 64 }) 65 66 t.Run("unsuccessful HTTP response", func(t *testing.T) { 67 gw.nextHTTPResponse = &http.Response{ 68 Status: "418 I'm a teapot", 69 StatusCode: 418, 70 Body: io.NopCloser(&mockReadCloser{ 71 expectedData: []byte(""), 72 expectedErr: fmt.Errorf("Failed to read"), 73 }), 74 } 75 76 _, err := clientIface.NewPublish(ctx) 77 78 if err == nil { 79 t.Error("Unexpectedly failed to return an error") 80 } 81 if !strings.Contains(err.Error(), "I'm a teapot") { 82 t.Errorf("Did not get expected error, got: %v", err) 83 } 84 if !strings.Contains(err.Error(), "https://exodus-gw.example.com/env/publish") { 85 t.Errorf("Error did not include URL, got: %v", err) 86 } 87 }) 88 89 t.Run("invalid JSON in response", func(t *testing.T) { 90 gw.nextHTTPResponse = &http.Response{ 91 Status: "200 OK", 92 StatusCode: 200, 93 Body: io.NopCloser(strings.NewReader("['oops', this is not valid JSON")), 94 } 95 96 _, err := clientIface.NewPublish(ctx) 97 98 if err == nil { 99 t.Error("Unexpectedly failed to return an error") 100 } 101 if !strings.Contains(err.Error(), "invalid character") { 102 t.Errorf("Did not get expected error, got: %v", err) 103 } 104 if !strings.Contains(err.Error(), "https://exodus-gw.example.com/env/publish") { 105 t.Errorf("Error did not include URL, got: %v", err) 106 } 107 }) 108 109 t.Run("get request fails", func(t *testing.T) { 110 gw.nextHTTPResponse = &http.Response{ 111 Status: "404 Publish not found", 112 StatusCode: 404, 113 Body: io.NopCloser(strings.NewReader("'oops', publish not found")), 114 } 115 116 _, err := clientIface.GetPublish(ctx, "3e0a4539-be4a-437e-a45f-6d72f7192f17") 117 if err == nil { 118 t.Error("Unexpectedly failed to return an error") 119 } 120 }) 121 122 t.Run("can recover by retrying", func(t *testing.T) { 123 // Force an EOF, then a 500 error, and finally a successful 124 // response. The caller should only see the success. 125 gw.nextHTTPError = io.EOF 126 gw.nextHTTPResponse = &http.Response{ 127 Status: "500 Internal Server Error", 128 StatusCode: 500, 129 Body: io.NopCloser(strings.NewReader("some error")), 130 } 131 132 gw.publishes["some-id"] = &fakePublish{id: "some-id"} 133 134 p, err := clientIface.GetPublish(ctx, "some-id") 135 if err != nil { 136 t.Errorf("failed to get publish, err = %v", err) 137 } 138 id := p.ID() 139 if id != "some-id" { 140 t.Errorf("got unexpected id %s", id) 141 } 142 }) 143 144 t.Run("missing link for commit", func(t *testing.T) { 145 // Create a publish object directly without filling in any Links. 146 publish := publish{client: clientIface.(*client)} 147 148 err := publish.Commit(ctx, "") 149 150 if err == nil { 151 t.Error("Unexpectedly failed to return an error") 152 } 153 if !strings.Contains(err.Error(), "not eligible for commit") { 154 t.Errorf("Did not get expected error, got: %v", err) 155 } 156 }) 157 158 t.Run("missing link for update", func(t *testing.T) { 159 publish := publish{client: clientIface.(*client)} 160 161 err := publish.AddItems(ctx, []ItemInput{}) 162 163 if err == nil { 164 t.Error("Unexpectedly failed to return an error") 165 } 166 if !strings.Contains(err.Error(), "missing 'self'") { 167 t.Errorf("Did not get expected error, got: %v", err) 168 } 169 }) 170 171 t.Run("HTTP error during AddItems", func(t *testing.T) { 172 publish := publish{client: clientIface.(*client)} 173 publish.raw.Links = make(map[string]string) 174 publish.raw.Links["self"] = "/publish/1234" 175 176 gw.nextHTTPResponse = &http.Response{ 177 Status: "409 Conflict", 178 StatusCode: 409, 179 Body: io.NopCloser(strings.NewReader( 180 "{\"detail\": \"Publish in unexpected state\"}", 181 )), 182 } 183 184 err := publish.AddItems(ctx, []ItemInput{{"/some/uri", "abc123", "mime/type", ""}}) 185 186 if err == nil { 187 t.Error("Unexpectedly failed to return an error") 188 } 189 if !strings.Contains(err.Error(), "Publish in unexpected state") { 190 t.Errorf("Did not get expected error, got: %v", err) 191 } 192 }) 193 194 t.Run("commit request fails", func(t *testing.T) { 195 publish := publish{client: clientIface.(*client)} 196 197 // Putting an incorrect 'commit' URL is enough to make the GW fake 198 // return a 404 error. 199 publish.raw.Links = make(map[string]string) 200 publish.raw.Links["commit"] = "/some/invalid/url" 201 202 err := publish.Commit(ctx, "") 203 204 if err == nil { 205 t.Error("Unexpectedly failed to return an error") 206 } 207 if !strings.Contains(err.Error(), "404 Not Found") { 208 t.Errorf("Did not get expected error, got: %v", err) 209 } 210 }) 211 212 }