github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/v2/processor_test.go (about) 1 // Copyright 2023 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package v2 15 16 import ( 17 "context" 18 "encoding/json" 19 "fmt" 20 "net/http" 21 "net/http/httptest" 22 "testing" 23 24 "github.com/golang/mock/gomock" 25 mock_capture "github.com/pingcap/tiflow/cdc/capture/mock" 26 mock_controller "github.com/pingcap/tiflow/cdc/controller/mock" 27 "github.com/pingcap/tiflow/cdc/model" 28 mock_owner "github.com/pingcap/tiflow/cdc/owner/mock" 29 "github.com/stretchr/testify/require" 30 ) 31 32 const ( 33 captureID = "test-capture" 34 ) 35 36 func TestGetProcessor(t *testing.T) { 37 t.Parallel() 38 39 // case 1: invalid changefeed id. 40 { 41 ctl := gomock.NewController(t) 42 cp := mock_capture.NewMockCapture(ctl) 43 cp.EXPECT().IsReady().Return(true).AnyTimes() 44 statusProvider := mock_owner.NewMockStatusProvider(ctl) 45 statusProvider.EXPECT().IsChangefeedOwner(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes() 46 cp.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 47 cp.EXPECT().IsController().Return(true).AnyTimes() 48 49 apiV2 := NewOpenAPIV2ForTest(cp, APIV2HelpersImpl{}) 50 router := newRouter(apiV2) 51 metainfo := testCase{ 52 url: "/api/v2/processors/%s/%s", 53 method: "GET", 54 } 55 invalidID := "@^Invalid" 56 w := httptest.NewRecorder() 57 req, _ := http.NewRequestWithContext( 58 context.Background(), 59 metainfo.method, 60 fmt.Sprintf(metainfo.url, invalidID, captureID), 61 nil, 62 ) 63 router.ServeHTTP(w, req) 64 respErr := model.HTTPError{} 65 err := json.NewDecoder(w.Body).Decode(&respErr) 66 require.Nil(t, err) 67 require.Contains(t, respErr.Code, "ErrAPIInvalidParam") 68 require.Equal(t, http.StatusBadRequest, w.Code) 69 } 70 71 // case 2: invalid capture id. 72 { 73 ctl := gomock.NewController(t) 74 cp := mock_capture.NewMockCapture(ctl) 75 cp.EXPECT().IsReady().Return(true).AnyTimes() 76 statusProvider := mock_owner.NewMockStatusProvider(ctl) 77 statusProvider.EXPECT().IsChangefeedOwner(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes() 78 cp.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 79 cp.EXPECT().IsController().Return(true).AnyTimes() 80 81 apiV2 := NewOpenAPIV2ForTest(cp, APIV2HelpersImpl{}) 82 router := newRouter(apiV2) 83 metainfo := testCase{ 84 url: "/api/v2/processors/%s/%s", 85 method: "GET", 86 } 87 invalidID := "@^Invalid" 88 w := httptest.NewRecorder() 89 req, _ := http.NewRequestWithContext( 90 context.Background(), 91 metainfo.method, 92 fmt.Sprintf(metainfo.url, changeFeedID.ID, invalidID), 93 nil, 94 ) 95 router.ServeHTTP(w, req) 96 respErr := model.HTTPError{} 97 err := json.NewDecoder(w.Body).Decode(&respErr) 98 require.Nil(t, err) 99 require.Contains(t, respErr.Code, "ErrAPIInvalidParam") 100 require.Equal(t, http.StatusBadRequest, w.Code) 101 } 102 103 // case 3: abnormal changefeed state. 104 { 105 provider := &mockStatusProvider{ 106 changefeedInfo: &model.ChangeFeedInfo{ 107 State: model.StatePending, 108 }, 109 } 110 cp := mock_capture.NewMockCapture(gomock.NewController(t)) 111 cp.EXPECT().IsReady().Return(true).AnyTimes() 112 cp.EXPECT().IsController().Return(true).AnyTimes() 113 114 apiV2 := NewOpenAPIV2ForTest(cp, APIV2HelpersImpl{}) 115 router := newRouter(apiV2) 116 metainfo := testCase{ 117 url: "/api/v2/processors/%s/%s", 118 method: "GET", 119 } 120 cp.EXPECT().StatusProvider().Return(provider).AnyTimes() 121 122 w := httptest.NewRecorder() 123 req, _ := http.NewRequestWithContext( 124 context.Background(), 125 metainfo.method, 126 fmt.Sprintf(metainfo.url, changeFeedID.ID, captureID), 127 nil, 128 ) 129 router.ServeHTTP(w, req) 130 respErr := model.HTTPError{} 131 err := json.NewDecoder(w.Body).Decode(&respErr) 132 require.Nil(t, err) 133 require.Contains(t, respErr.Code, "ErrAPIInvalidParam") 134 require.Contains(t, respErr.Error, "changefeed in abnormal state") 135 require.Equal(t, http.StatusBadRequest, w.Code) 136 } 137 138 // case 4: captureID not exist. 139 { 140 provider := &mockStatusProvider{ 141 changefeedInfo: &model.ChangeFeedInfo{ 142 State: model.StateNormal, 143 }, 144 processors: []*model.ProcInfoSnap{ 145 { 146 CaptureID: "nonexist-capture", 147 }, 148 }, 149 } 150 cp := mock_capture.NewMockCapture(gomock.NewController(t)) 151 cp.EXPECT().StatusProvider().Return(provider).AnyTimes() 152 cp.EXPECT().IsReady().Return(true).AnyTimes() 153 cp.EXPECT().IsController().Return(true).AnyTimes() 154 155 apiV2 := NewOpenAPIV2ForTest(cp, APIV2HelpersImpl{}) 156 router := newRouter(apiV2) 157 metainfo := testCase{ 158 url: "/api/v2/processors/%s/%s", 159 method: "GET", 160 } 161 162 w := httptest.NewRecorder() 163 req, _ := http.NewRequestWithContext( 164 context.Background(), 165 metainfo.method, 166 fmt.Sprintf(metainfo.url, changeFeedID.ID, captureID), 167 nil, 168 ) 169 router.ServeHTTP(w, req) 170 respErr := model.HTTPError{} 171 err := json.NewDecoder(w.Body).Decode(&respErr) 172 require.Nil(t, err) 173 require.Contains(t, respErr.Code, "ErrCaptureNotExist") 174 require.Equal(t, http.StatusBadRequest, w.Code) 175 } 176 177 // case 5: corresponding task status not found. 178 { 179 provider := &mockStatusProvider{ 180 changefeedInfo: &model.ChangeFeedInfo{ 181 State: model.StateNormal, 182 }, 183 processors: []*model.ProcInfoSnap{ 184 { 185 CaptureID: captureID, 186 }, 187 }, 188 taskStatus: map[model.CaptureID]*model.TaskStatus{}, 189 } 190 cp := mock_capture.NewMockCapture(gomock.NewController(t)) 191 cp.EXPECT().StatusProvider().Return(provider).AnyTimes() 192 cp.EXPECT().IsReady().Return(true).AnyTimes() 193 cp.EXPECT().IsController().Return(true).AnyTimes() 194 195 apiV2 := NewOpenAPIV2ForTest(cp, APIV2HelpersImpl{}) 196 router := newRouter(apiV2) 197 metainfo := testCase{ 198 url: "/api/v2/processors/%s/%s", 199 method: "GET", 200 } 201 w := httptest.NewRecorder() 202 req, _ := http.NewRequestWithContext( 203 context.Background(), 204 metainfo.method, 205 fmt.Sprintf(metainfo.url, changeFeedID.ID, captureID), 206 nil, 207 ) 208 209 router.ServeHTTP(w, req) 210 resp := model.ProcessorDetail{} 211 err := json.NewDecoder(w.Body).Decode(&resp) 212 require.Nil(t, err) 213 require.Equal(t, http.StatusOK, w.Code) 214 // if task status not exist, return an empty processor detail 215 require.Equal(t, model.ProcessorDetail{}, resp) 216 } 217 218 // case 6: success get the desired processor info. 219 { 220 provider := &mockStatusProvider{ 221 changefeedInfo: &model.ChangeFeedInfo{ 222 State: model.StateNormal, 223 }, 224 processors: []*model.ProcInfoSnap{ 225 { 226 CaptureID: captureID, 227 }, 228 }, 229 taskStatus: map[model.CaptureID]*model.TaskStatus{ 230 model.CaptureID(captureID): { 231 Tables: map[model.TableID]*model.TableReplicaInfo{ 232 model.TableID(0): {}, 233 model.TableID(1): {}, 234 }, 235 }, 236 }, 237 } 238 cp := mock_capture.NewMockCapture(gomock.NewController(t)) 239 cp.EXPECT().StatusProvider().Return(provider).AnyTimes() 240 cp.EXPECT().IsReady().Return(true).AnyTimes() 241 cp.EXPECT().IsController().Return(true).AnyTimes() 242 apiV2 := NewOpenAPIV2ForTest(cp, APIV2HelpersImpl{}) 243 router := newRouter(apiV2) 244 245 metainfo := testCase{ 246 url: "/api/v2/processors/%s/%s", 247 method: "GET", 248 } 249 w := httptest.NewRecorder() 250 req, _ := http.NewRequestWithContext( 251 context.Background(), 252 metainfo.method, 253 fmt.Sprintf(metainfo.url, changeFeedID.ID, captureID), 254 nil, 255 ) 256 257 router.ServeHTTP(w, req) 258 resp1 := model.ProcessorDetail{} 259 err := json.NewDecoder(w.Body).Decode(&resp1) 260 require.Nil(t, err) 261 require.Equal(t, http.StatusOK, w.Code) 262 require.Equal(t, 2, len(resp1.Tables)) 263 } 264 } 265 266 func TestListProcessors(t *testing.T) { 267 cp := mock_capture.NewMockCapture(gomock.NewController(t)) 268 controller := mock_controller.NewMockController(gomock.NewController(t)) 269 cp.EXPECT().GetController().Return(controller, nil).AnyTimes() 270 cp.EXPECT().IsReady().Return(true).AnyTimes() 271 cp.EXPECT().IsController().Return(true).AnyTimes() 272 apiV2 := NewOpenAPIV2ForTest(cp, APIV2HelpersImpl{}) 273 router := newRouter(apiV2) 274 controller.EXPECT().GetProcessors(gomock.Any()).Return( 275 []*model.ProcInfoSnap{ 276 { 277 CfID: model.ChangeFeedID{ 278 Namespace: "default", 279 ID: "test", 280 }, 281 CaptureID: "c1", 282 }, 283 { 284 CfID: model.ChangeFeedID{ 285 Namespace: "default", 286 ID: "test", 287 }, 288 CaptureID: "c2", 289 }, 290 }, nil, 291 ) 292 293 metainfo := testCase{ 294 url: "/api/v2/processors", 295 method: "GET", 296 } 297 w := httptest.NewRecorder() 298 req, _ := http.NewRequestWithContext( 299 context.Background(), 300 metainfo.method, 301 metainfo.url, 302 nil, 303 ) 304 305 router.ServeHTTP(w, req) 306 resp1 := ListResponse[ProcessorCommonInfo]{} 307 err := json.NewDecoder(w.Body).Decode(&resp1) 308 require.Nil(t, err) 309 require.Equal(t, http.StatusOK, w.Code) 310 require.Equal(t, 2, len(resp1.Items)) 311 require.Equal(t, "c1", resp1.Items[0].CaptureID) 312 }