github.com/influxdata/influxdb/v2@v2.7.6/replications/transport/http_test.go (about) 1 package transport 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "net/http" 7 "net/http/httptest" 8 "testing" 9 10 "github.com/golang/mock/gomock" 11 "github.com/influxdata/influxdb/v2" 12 "github.com/influxdata/influxdb/v2/kit/platform" 13 "github.com/influxdata/influxdb/v2/replications/mock" 14 "github.com/stretchr/testify/assert" 15 tmock "github.com/stretchr/testify/mock" 16 "github.com/stretchr/testify/require" 17 "go.uber.org/zap/zaptest" 18 ) 19 20 //go:generate go run github.com/golang/mock/mockgen -package mock -destination ../mock/service.go github.com/influxdata/influxdb/v2/replications/transport ReplicationService 21 22 var ( 23 orgStr = "1234123412341234" 24 orgID, _ = platform.IDFromString(orgStr) 25 remoteStr = "9876987698769876" 26 remoteID, _ = platform.IDFromString(remoteStr) 27 idStr = "4321432143214321" 28 id, _ = platform.IDFromString(idStr) 29 localBucketStr = "1111111111111111" 30 localBucketId, _ = platform.IDFromString(localBucketStr) 31 remoteBucketStr = "1234567887654321" 32 remoteBucketID, _ = platform.IDFromString(remoteBucketStr) 33 testReplication = influxdb.Replication{ 34 ID: *id, 35 OrgID: *orgID, 36 RemoteID: *remoteID, 37 LocalBucketID: *localBucketId, 38 RemoteBucketID: remoteBucketID, 39 Name: "example", 40 MaxQueueSizeBytes: influxdb.DefaultReplicationMaxQueueSizeBytes, 41 } 42 ) 43 44 func TestReplicationHandler(t *testing.T) { 45 t.Run("get replications happy path", func(t *testing.T) { 46 ts, svc := newTestServer(t) 47 defer ts.Close() 48 49 req := newTestRequest(t, "GET", ts.URL, nil) 50 51 q := req.URL.Query() 52 q.Add("orgID", orgStr) 53 q.Add("name", testReplication.Name) 54 q.Add("remoteID", remoteStr) 55 q.Add("localBucketID", localBucketStr) 56 req.URL.RawQuery = q.Encode() 57 58 expected := influxdb.Replications{Replications: []influxdb.Replication{testReplication}} 59 60 svc.EXPECT(). 61 ListReplications(gomock.Any(), tmock.MatchedBy(func(in influxdb.ReplicationListFilter) bool { 62 return assert.Equal(t, *orgID, in.OrgID) && 63 assert.Equal(t, testReplication.Name, *in.Name) && 64 assert.Equal(t, testReplication.RemoteID, *in.RemoteID) && 65 assert.Equal(t, testReplication.LocalBucketID, *in.LocalBucketID) 66 })).Return(&expected, nil) 67 68 res := doTestRequest(t, req, http.StatusOK, true) 69 70 var got influxdb.Replications 71 require.NoError(t, json.NewDecoder(res.Body).Decode(&got)) 72 require.Equal(t, expected, got) 73 }) 74 75 t.Run("create replication happy path", func(t *testing.T) { 76 77 body := influxdb.CreateReplicationRequest{ 78 OrgID: testReplication.OrgID, 79 Name: testReplication.Name, 80 RemoteID: testReplication.RemoteID, 81 LocalBucketID: testReplication.LocalBucketID, 82 RemoteBucketID: *testReplication.RemoteBucketID, 83 } 84 85 t.Run("with explicit queue size", func(t *testing.T) { 86 ts, svc := newTestServer(t) 87 defer ts.Close() 88 89 body := body 90 body.MaxQueueSizeBytes = 2 * influxdb.DefaultReplicationMaxQueueSizeBytes 91 92 req := newTestRequest(t, "POST", ts.URL, &body) 93 94 svc.EXPECT().CreateReplication(gomock.Any(), body).Return(&testReplication, nil) 95 96 res := doTestRequest(t, req, http.StatusCreated, true) 97 98 var got influxdb.Replication 99 require.NoError(t, json.NewDecoder(res.Body).Decode(&got)) 100 require.Equal(t, testReplication, got) 101 }) 102 103 t.Run("with default queue size", func(t *testing.T) { 104 ts, svc := newTestServer(t) 105 defer ts.Close() 106 107 req := newTestRequest(t, "POST", ts.URL, &body) 108 109 expectedBody := body 110 expectedBody.MaxQueueSizeBytes = influxdb.DefaultReplicationMaxQueueSizeBytes 111 112 svc.EXPECT().CreateReplication(gomock.Any(), expectedBody).Return(&testReplication, nil) 113 114 res := doTestRequest(t, req, http.StatusCreated, true) 115 116 var got influxdb.Replication 117 require.NoError(t, json.NewDecoder(res.Body).Decode(&got)) 118 require.Equal(t, testReplication, got) 119 }) 120 }) 121 122 t.Run("dry-run create happy path", func(t *testing.T) { 123 124 body := influxdb.CreateReplicationRequest{ 125 OrgID: testReplication.OrgID, 126 Name: testReplication.Name, 127 RemoteID: testReplication.RemoteID, 128 LocalBucketID: testReplication.LocalBucketID, 129 RemoteBucketID: *testReplication.RemoteBucketID, 130 } 131 132 t.Run("with explicit queue size", func(t *testing.T) { 133 ts, svc := newTestServer(t) 134 defer ts.Close() 135 136 body := body 137 body.MaxQueueSizeBytes = 2 * influxdb.DefaultReplicationMaxQueueSizeBytes 138 139 req := newTestRequest(t, "POST", ts.URL, &body) 140 q := req.URL.Query() 141 q.Add("validate", "true") 142 req.URL.RawQuery = q.Encode() 143 144 svc.EXPECT().ValidateNewReplication(gomock.Any(), body).Return(nil) 145 146 doTestRequest(t, req, http.StatusNoContent, false) 147 }) 148 149 t.Run("with default queue size", func(t *testing.T) { 150 ts, svc := newTestServer(t) 151 defer ts.Close() 152 153 req := newTestRequest(t, "POST", ts.URL, &body) 154 q := req.URL.Query() 155 q.Add("validate", "true") 156 req.URL.RawQuery = q.Encode() 157 158 expectedBody := body 159 expectedBody.MaxQueueSizeBytes = influxdb.DefaultReplicationMaxQueueSizeBytes 160 161 svc.EXPECT().ValidateNewReplication(gomock.Any(), expectedBody).Return(nil) 162 163 doTestRequest(t, req, http.StatusNoContent, false) 164 }) 165 }) 166 167 t.Run("get replication happy path", func(t *testing.T) { 168 ts, svc := newTestServer(t) 169 defer ts.Close() 170 171 req := newTestRequest(t, "GET", ts.URL+"/"+id.String(), nil) 172 173 svc.EXPECT().GetReplication(gomock.Any(), *id).Return(&testReplication, nil) 174 175 res := doTestRequest(t, req, http.StatusOK, true) 176 177 var got influxdb.Replication 178 require.NoError(t, json.NewDecoder(res.Body).Decode(&got)) 179 require.Equal(t, testReplication, got) 180 }) 181 182 t.Run("delete replication happy path", func(t *testing.T) { 183 ts, svc := newTestServer(t) 184 defer ts.Close() 185 186 req := newTestRequest(t, "DELETE", ts.URL+"/"+id.String(), nil) 187 188 svc.EXPECT().DeleteReplication(gomock.Any(), *id).Return(nil) 189 190 doTestRequest(t, req, http.StatusNoContent, false) 191 }) 192 193 t.Run("update replication happy path", func(t *testing.T) { 194 ts, svc := newTestServer(t) 195 defer ts.Close() 196 197 newDescription := "my cool replication" 198 newQueueSize := 3 * influxdb.DefaultReplicationMaxQueueSizeBytes 199 body := influxdb.UpdateReplicationRequest{Description: &newDescription, MaxQueueSizeBytes: &newQueueSize} 200 201 req := newTestRequest(t, "PATCH", ts.URL+"/"+id.String(), body) 202 203 svc.EXPECT().UpdateReplication(gomock.Any(), *id, body).Return(&testReplication, nil) 204 205 res := doTestRequest(t, req, http.StatusOK, true) 206 207 var got influxdb.Replication 208 require.NoError(t, json.NewDecoder(res.Body).Decode(&got)) 209 require.Equal(t, testReplication, got) 210 }) 211 212 t.Run("dry-run update happy path", func(t *testing.T) { 213 ts, svc := newTestServer(t) 214 defer ts.Close() 215 216 newDescription := "my cool replication" 217 newQueueSize := 3 * influxdb.DefaultReplicationMaxQueueSizeBytes 218 body := influxdb.UpdateReplicationRequest{Description: &newDescription, MaxQueueSizeBytes: &newQueueSize} 219 220 req := newTestRequest(t, "PATCH", ts.URL+"/"+id.String(), body) 221 q := req.URL.Query() 222 q.Add("validate", "true") 223 req.URL.RawQuery = q.Encode() 224 225 svc.EXPECT().ValidateUpdatedReplication(gomock.Any(), *id, body).Return(nil) 226 227 doTestRequest(t, req, http.StatusNoContent, false) 228 }) 229 230 t.Run("validate replication happy path", func(t *testing.T) { 231 ts, svc := newTestServer(t) 232 defer ts.Close() 233 234 req := newTestRequest(t, "POST", ts.URL+"/"+id.String()+"/validate", nil) 235 236 svc.EXPECT().ValidateReplication(gomock.Any(), *id).Return(nil) 237 238 doTestRequest(t, req, http.StatusNoContent, false) 239 }) 240 241 t.Run("invalid replication IDs return 400", func(t *testing.T) { 242 ts, _ := newTestServer(t) 243 defer ts.Close() 244 245 req1 := newTestRequest(t, "GET", ts.URL+"/foo", nil) 246 req2 := newTestRequest(t, "PATCH", ts.URL+"/foo", &influxdb.UpdateReplicationRequest{}) 247 req3 := newTestRequest(t, "DELETE", ts.URL+"/foo", nil) 248 249 for _, req := range []*http.Request{req1, req2, req3} { 250 t.Run(req.Method, func(t *testing.T) { 251 doTestRequest(t, req, http.StatusBadRequest, true) 252 }) 253 } 254 }) 255 256 t.Run("invalid org ID to GET /replications returns 400", func(t *testing.T) { 257 ts, _ := newTestServer(t) 258 defer ts.Close() 259 260 req := newTestRequest(t, "GET", ts.URL, nil) 261 q := req.URL.Query() 262 q.Add("orgID", "foo") 263 req.URL.RawQuery = q.Encode() 264 265 doTestRequest(t, req, http.StatusBadRequest, true) 266 }) 267 268 t.Run("invalid request bodies return 400", func(t *testing.T) { 269 ts, _ := newTestServer(t) 270 defer ts.Close() 271 272 body := "o no not an object" 273 req1 := newTestRequest(t, "POST", ts.URL, &body) 274 req2 := newTestRequest(t, "PATCH", ts.URL+"/"+id.String(), &body) 275 276 for _, req := range []*http.Request{req1, req2} { 277 t.Run(req.Method, func(t *testing.T) { 278 doTestRequest(t, req, http.StatusBadRequest, true) 279 }) 280 } 281 }) 282 283 t.Run("too-small queue size on create is rejected", func(t *testing.T) { 284 ts, _ := newTestServer(t) 285 defer ts.Close() 286 287 body := influxdb.CreateReplicationRequest{ 288 OrgID: testReplication.OrgID, 289 Name: testReplication.Name, 290 RemoteID: testReplication.RemoteID, 291 LocalBucketID: testReplication.LocalBucketID, 292 RemoteBucketID: *testReplication.RemoteBucketID, 293 MaxQueueSizeBytes: influxdb.MinReplicationMaxQueueSizeBytes / 2, 294 } 295 296 req := newTestRequest(t, "POST", ts.URL, &body) 297 298 doTestRequest(t, req, http.StatusBadRequest, true) 299 }) 300 301 t.Run("too-small queue size on update is rejected", func(t *testing.T) { 302 ts, _ := newTestServer(t) 303 defer ts.Close() 304 305 newSize := influxdb.MinReplicationMaxQueueSizeBytes / 2 306 body := influxdb.UpdateReplicationRequest{MaxQueueSizeBytes: &newSize} 307 308 req := newTestRequest(t, "PATCH", ts.URL+"/"+id.String(), &body) 309 310 doTestRequest(t, req, http.StatusBadRequest, true) 311 }) 312 } 313 314 func newTestServer(t *testing.T) (*httptest.Server, *mock.MockReplicationService) { 315 ctrl := gomock.NewController(t) 316 svc := mock.NewMockReplicationService(ctrl) 317 server := newReplicationHandler(zaptest.NewLogger(t), svc) 318 return httptest.NewServer(server), svc 319 } 320 321 func newTestRequest(t *testing.T, method, path string, body interface{}) *http.Request { 322 dat, err := json.Marshal(body) 323 require.NoError(t, err) 324 325 req, err := http.NewRequest(method, path, bytes.NewBuffer(dat)) 326 require.NoError(t, err) 327 328 req.Header.Add("Content-Type", "application/json") 329 330 return req 331 } 332 333 func doTestRequest(t *testing.T, req *http.Request, wantCode int, needJSON bool) *http.Response { 334 res, err := http.DefaultClient.Do(req) 335 require.NoError(t, err) 336 require.Equal(t, wantCode, res.StatusCode) 337 if needJSON { 338 require.Equal(t, "application/json; charset=utf-8", res.Header.Get("Content-Type")) 339 } 340 return res 341 }