github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/jobmaster/dm/openapi_test.go (about) 1 // Copyright 2022 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 dm 15 16 import ( 17 "bytes" 18 "context" 19 "encoding/json" 20 "net/http" 21 "net/http/httptest" 22 "os" 23 "testing" 24 25 "github.com/DATA-DOG/go-sqlmock" 26 "github.com/gin-gonic/gin" 27 "github.com/pingcap/log" 28 "github.com/pingcap/tiflow/dm/checker" 29 dmconfig "github.com/pingcap/tiflow/dm/config" 30 dmmaster "github.com/pingcap/tiflow/dm/master" 31 "github.com/pingcap/tiflow/dm/pkg/conn" 32 "github.com/pingcap/tiflow/dm/pkg/terror" 33 "github.com/pingcap/tiflow/engine/jobmaster/dm/config" 34 "github.com/pingcap/tiflow/engine/jobmaster/dm/metadata" 35 "github.com/pingcap/tiflow/engine/jobmaster/dm/openapi" 36 dmpkg "github.com/pingcap/tiflow/engine/pkg/dm" 37 resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model" 38 "github.com/pingcap/tiflow/engine/pkg/meta/mock" 39 engineOpenAPI "github.com/pingcap/tiflow/engine/pkg/openapi" 40 "github.com/pingcap/tiflow/engine/pkg/promutil" 41 "github.com/pingcap/tiflow/pkg/errors" 42 tmock "github.com/stretchr/testify/mock" 43 "github.com/stretchr/testify/require" 44 "github.com/stretchr/testify/suite" 45 ) 46 47 const ( 48 baseURL = "/api/v1/jobs/job-id/" 49 ) 50 51 func TestDMOpenAPISuite(t *testing.T) { 52 suite.Run(t, new(testDMOpenAPISuite)) 53 } 54 55 type testDMOpenAPISuite struct { 56 suite.Suite 57 jm *JobMaster 58 engine *gin.Engine 59 messageAgent *dmpkg.MockMessageAgent 60 checkpointAnget *MockCheckpointAgent 61 funcBackup func(ctx context.Context, cfg *dmconfig.SourceConfig) error 62 } 63 64 func (t *testDMOpenAPISuite) SetupSuite() { 65 var ( 66 mockBaseJobmaster = &MockBaseJobmaster{t: t.T()} 67 mockMessageAgent = &dmpkg.MockMessageAgent{} 68 mockCheckpointAgent = &MockCheckpointAgent{} 69 jm = &JobMaster{ 70 BaseJobMaster: mockBaseJobmaster, 71 metadata: metadata.NewMetaData(mock.NewMetaMock(), log.L()), 72 messageAgent: mockMessageAgent, 73 checkpointAgent: mockCheckpointAgent, 74 } 75 ) 76 jm.taskManager = NewTaskManager("test-job", nil, jm.metadata.JobStore(), jm.messageAgent, jm.Logger(), promutil.NewFactory4Test(t.T().TempDir())) 77 jm.workerManager = NewWorkerManager(mockBaseJobmaster.ID(), nil, jm.metadata.JobStore(), jm.metadata.UnitStateStore(), nil, jm.messageAgent, nil, jm.Logger(), 78 resModel.ResourceTypeLocalFile) 79 jm.initialized.Store(true) 80 81 engine := gin.New() 82 apiGroup := engine.Group(baseURL) 83 jm.initOpenAPI(apiGroup) 84 85 t.funcBackup = dmmaster.CheckAndAdjustSourceConfigFunc 86 t.jm = jm 87 t.engine = engine 88 t.messageAgent = mockMessageAgent 89 t.checkpointAnget = mockCheckpointAgent 90 dmmaster.CheckAndAdjustSourceConfigFunc = checkAndNoAdjustSourceConfigMock 91 } 92 93 func (t *testDMOpenAPISuite) TearDownSuite() { 94 dmmaster.CheckAndAdjustSourceConfigFunc = t.funcBackup 95 } 96 97 func (t *testDMOpenAPISuite) TestDMAPIGetJobConfig() { 98 w := httptest.NewRecorder() 99 r := httptest.NewRequest("GET", "/api/v1/jobs/job-not-exist/config", nil) 100 t.engine.ServeHTTP(w, r) 101 require.Equal(t.T(), http.StatusNotFound, w.Code) 102 103 w = httptest.NewRecorder() 104 r = httptest.NewRequest("GET", baseURL+"config", nil) 105 t.engine.ServeHTTP(w, r) 106 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 107 equalError(t.T(), "state not found", w.Body) 108 109 jobCfg := &config.JobCfg{} 110 require.NoError(t.T(), jobCfg.DecodeFile(jobTemplatePath)) 111 require.NoError(t.T(), t.jm.taskManager.OperateTask(context.Background(), dmpkg.Create, jobCfg, nil)) 112 w = httptest.NewRecorder() 113 r = httptest.NewRequest("GET", baseURL+"config", nil) 114 t.engine.ServeHTTP(w, r) 115 require.Equal(t.T(), http.StatusOK, w.Code) 116 117 var cfgStr string 118 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &cfgStr)) 119 bs, err := jobCfg.Yaml() 120 require.NoError(t.T(), err) 121 require.Equal(t.T(), sortString(string(bs)), sortString(cfgStr)) 122 123 require.NoError(t.T(), t.jm.taskManager.OperateTask(context.Background(), dmpkg.Delete, nil, nil)) 124 } 125 126 func (t *testDMOpenAPISuite) TestDMAPIUpdateJobCfg() { 127 w := httptest.NewRecorder() 128 r := httptest.NewRequest("PUT", "/api/v1/jobs/job-not-exist/config", nil) 129 t.engine.ServeHTTP(w, r) 130 require.Equal(t.T(), http.StatusNotFound, w.Code) 131 132 req := openapi.UpdateJobConfigRequest{} 133 bs, err := json.Marshal(req) 134 require.NoError(t.T(), err) 135 w = httptest.NewRecorder() 136 r = httptest.NewRequest("PUT", baseURL+"config", bytes.NewReader(bs)) 137 r.Header.Set("Content-Type", "application/json") 138 t.engine.ServeHTTP(w, r) 139 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 140 141 cfgBytes, err := os.ReadFile(jobTemplatePath) 142 require.NoError(t.T(), err) 143 req = openapi.UpdateJobConfigRequest{Config: string(cfgBytes)} 144 bs, err = json.Marshal(req) 145 require.NoError(t.T(), err) 146 w = httptest.NewRecorder() 147 r = httptest.NewRequest("PUT", baseURL+"config", bytes.NewReader(bs)) 148 r.Header.Set("Content-Type", "application/json") 149 t.engine.ServeHTTP(w, r) 150 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 151 152 require.NoError(t.T(), t.jm.taskManager.OperateTask(context.Background(), dmpkg.Create, &config.JobCfg{}, nil)) 153 t.checkpointAnget.On("Update").Return(nil) 154 verDB := conn.InitVersionDB() 155 verDB.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). 156 AddRow("version", "5.7.25-TiDB-v6.1.0")) 157 checker.CheckSyncConfigFunc = func(_ context.Context, _ []*dmconfig.SubTaskConfig, _, _ int64) (string, error) { 158 return "check pass", nil 159 } 160 w = httptest.NewRecorder() 161 r = httptest.NewRequest("PUT", baseURL+"config", bytes.NewReader(bs)) 162 r.Header.Set("Content-Type", "application/json") 163 t.engine.ServeHTTP(w, r) 164 require.Equal(t.T(), http.StatusOK, w.Code) 165 } 166 167 func (t *testDMOpenAPISuite) TestDMAPIGetJobStatus() { 168 w := httptest.NewRecorder() 169 r := httptest.NewRequest("GET", "/api/v1/jobs/job-not-exist/status", nil) 170 t.engine.ServeHTTP(w, r) 171 require.Equal(t.T(), http.StatusNotFound, w.Code) 172 173 w = httptest.NewRecorder() 174 r = httptest.NewRequest("GET", baseURL+"status", nil) 175 t.engine.ServeHTTP(w, r) 176 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 177 equalError(t.T(), "state not found", w.Body) 178 179 w = httptest.NewRecorder() 180 r = httptest.NewRequest("GET", baseURL+"status"+"?tasks=task", nil) 181 t.engine.ServeHTTP(w, r) 182 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 183 equalError(t.T(), "state not found", w.Body) 184 185 require.NoError(t.T(), t.jm.taskManager.OperateTask(context.Background(), dmpkg.Create, &config.JobCfg{}, nil)) 186 w = httptest.NewRecorder() 187 r = httptest.NewRequest("GET", baseURL+"status", nil) 188 t.engine.ServeHTTP(w, r) 189 require.Equal(t.T(), http.StatusOK, w.Code) 190 191 jobStatus := JobStatus{ 192 JobID: "dm-jobmaster-id", 193 TaskStatus: map[string]TaskStatus{}, 194 } 195 var jobStatus2 JobStatus 196 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &jobStatus2)) 197 require.Equal(t.T(), jobStatus, jobStatus2) 198 199 w = httptest.NewRecorder() 200 r = httptest.NewRequest("GET", baseURL+"status"+"?tasks=task1&tasks=task2", nil) 201 t.engine.ServeHTTP(w, r) 202 require.Equal(t.T(), http.StatusOK, w.Code) 203 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &jobStatus2)) 204 require.Equal(t.T(), "task task1 for job not found", jobStatus2.TaskStatus["task1"].Status.ErrorMsg) 205 require.Equal(t.T(), "task task2 for job not found", jobStatus2.TaskStatus["task2"].Status.ErrorMsg) 206 207 require.NoError(t.T(), t.jm.taskManager.OperateTask(context.Background(), dmpkg.Delete, nil, nil)) 208 } 209 210 func (t *testDMOpenAPISuite) TestDMAPIOperateJob() { 211 w := httptest.NewRecorder() 212 r := httptest.NewRequest("PUT", "/api/v1/jobs/job-not-exist/status", nil) 213 t.engine.ServeHTTP(w, r) 214 require.Equal(t.T(), http.StatusNotFound, w.Code) 215 216 w = httptest.NewRecorder() 217 r = httptest.NewRequest("PUT", baseURL+"status", nil) 218 t.engine.ServeHTTP(w, r) 219 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 220 equalError(t.T(), "unsupported op type '' for operate task", w.Body) 221 222 tasks := []string{"task"} 223 req := openapi.OperateJobRequest{ 224 Op: openapi.OperateJobRequestOpPause, 225 Tasks: &tasks, 226 } 227 bs, err := json.Marshal(req) 228 require.NoError(t.T(), err) 229 w = httptest.NewRecorder() 230 r = httptest.NewRequest("PUT", baseURL+"status", bytes.NewReader(bs)) 231 r.Header.Set("Content-Type", "application/json") 232 t.engine.ServeHTTP(w, r) 233 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 234 equalError(t.T(), "state not found", w.Body) 235 236 jobCfg := &config.JobCfg{} 237 require.NoError(t.T(), jobCfg.DecodeFile(jobTemplatePath)) 238 require.NoError(t.T(), t.jm.taskManager.OperateTask(context.Background(), dmpkg.Create, jobCfg, nil)) 239 req.Op = openapi.OperateJobRequestOpResume 240 req.Tasks = nil 241 bs, err = json.Marshal(req) 242 require.NoError(t.T(), err) 243 w = httptest.NewRecorder() 244 r = httptest.NewRequest("PUT", baseURL+"status", bytes.NewReader(bs)) 245 r.Header.Set("Content-Type", "application/json") 246 t.engine.ServeHTTP(w, r) 247 require.Equal(t.T(), http.StatusOK, w.Code) 248 249 require.NoError(t.T(), t.jm.taskManager.OperateTask(context.Background(), dmpkg.Delete, nil, nil)) 250 } 251 252 func (t *testDMOpenAPISuite) TestDMAPIGetBinlogOperator() { 253 w := httptest.NewRecorder() 254 r := httptest.NewRequest("GET", "/api/v1/jobs/job-not-exist/binlog", nil) 255 t.engine.ServeHTTP(w, r) 256 require.Equal(t.T(), http.StatusNotFound, w.Code) 257 258 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(nil, errors.New("binlog operator not found")).Once() 259 w = httptest.NewRecorder() 260 r = httptest.NewRequest("GET", baseURL+"binlog/tasks/"+"task1"+"?binlog_pos='mysql-bin.000001,4'", nil) 261 t.engine.ServeHTTP(w, r) 262 require.Equal(t.T(), http.StatusOK, w.Code) 263 var binlogResp dmpkg.BinlogResponse 264 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogResp)) 265 require.Equal(t.T(), "", binlogResp.ErrorMsg) 266 require.Equal(t.T(), "binlog operator not found", binlogResp.Results["task1"].ErrorMsg) 267 } 268 269 func (t *testDMOpenAPISuite) TestDMAPISetBinlogOperator() { 270 w := httptest.NewRecorder() 271 r := httptest.NewRequest("POST", "/api/v1/jobs/job-not-exist/binlog", nil) 272 t.engine.ServeHTTP(w, r) 273 require.Equal(t.T(), http.StatusNotFound, w.Code) 274 275 req := openapi.SetBinlogOperatorRequest{ 276 Op: "wrong-op", 277 } 278 bs, err := json.Marshal(req) 279 require.NoError(t.T(), err) 280 w = httptest.NewRecorder() 281 r = httptest.NewRequest("POST", baseURL+"binlog/tasks/"+"task1", bytes.NewReader(bs)) 282 r.Header.Set("Content-Type", "application/json") 283 t.engine.ServeHTTP(w, r) 284 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 285 equalError(t.T(), "unsupported op type 'wrong-op' for set binlog operator", w.Body) 286 287 pos := "mysql-bin.000001,4" 288 sqls := []string{"ALTER TABLE tb ADD COLUMN c int(11) UNIQUE;"} 289 req = openapi.SetBinlogOperatorRequest{ 290 BinlogPos: &pos, 291 Op: openapi.SetBinlogOperatorRequestOpReplace, 292 Sqls: &sqls, 293 } 294 bs, err = json.Marshal(req) 295 require.NoError(t.T(), err) 296 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 297 Msg: "binlog operator set success", 298 }, nil).Once() 299 w = httptest.NewRecorder() 300 r = httptest.NewRequest("POST", baseURL+"binlog/tasks/"+"task1", bytes.NewReader(bs)) 301 r.Header.Set("Content-Type", "application/json") 302 t.engine.ServeHTTP(w, r) 303 require.Equal(t.T(), http.StatusCreated, w.Code) 304 var binlogResp dmpkg.BinlogResponse 305 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogResp)) 306 require.Equal(t.T(), "", binlogResp.ErrorMsg) 307 require.Equal(t.T(), "binlog operator set success", binlogResp.Results["task1"].Msg) 308 309 req = openapi.SetBinlogOperatorRequest{ 310 Op: openapi.SetBinlogOperatorRequestOpSkip, 311 } 312 bs, err = json.Marshal(req) 313 require.NoError(t.T(), err) 314 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 315 ErrorMsg: "no binlog error", 316 }, nil).Once() 317 w = httptest.NewRecorder() 318 r = httptest.NewRequest("POST", baseURL+"binlog/tasks/"+"task1", bytes.NewReader(bs)) 319 r.Header.Set("Content-Type", "application/json") 320 t.engine.ServeHTTP(w, r) 321 require.Equal(t.T(), http.StatusCreated, w.Code) 322 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogResp)) 323 require.Equal(t.T(), "", binlogResp.ErrorMsg) 324 require.Equal(t.T(), "no binlog error", binlogResp.Results["task1"].ErrorMsg) 325 326 req = openapi.SetBinlogOperatorRequest{ 327 Op: openapi.SetBinlogOperatorRequestOpInject, 328 } 329 bs, err = json.Marshal(req) 330 require.NoError(t.T(), err) 331 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 332 ErrorMsg: "no binlog error", 333 }, nil).Once() 334 w = httptest.NewRecorder() 335 r = httptest.NewRequest("POST", baseURL+"binlog/tasks/"+"task1", bytes.NewReader(bs)) 336 r.Header.Set("Content-Type", "application/json") 337 t.engine.ServeHTTP(w, r) 338 require.Equal(t.T(), http.StatusCreated, w.Code) 339 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogResp)) 340 require.Equal(t.T(), "", binlogResp.ErrorMsg) 341 require.Equal(t.T(), "no binlog error", binlogResp.Results["task1"].ErrorMsg) 342 } 343 344 func (t *testDMOpenAPISuite) TestDMAPIDeleteBinlogOperator() { 345 w := httptest.NewRecorder() 346 r := httptest.NewRequest("DELETE", "/api/v1/jobs/job-not-exist/binlog", nil) 347 t.engine.ServeHTTP(w, r) 348 require.Equal(t.T(), http.StatusNotFound, w.Code) 349 350 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(nil, errors.New("binlog operator not found")).Once() 351 w = httptest.NewRecorder() 352 r = httptest.NewRequest("DELETE", baseURL+"binlog/tasks/"+"task1"+"?binlog_pos='mysql-bin.000001,4'", nil) 353 t.engine.ServeHTTP(w, r) 354 require.Equal(t.T(), http.StatusOK, w.Code) 355 var binlogResp dmpkg.BinlogResponse 356 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogResp)) 357 require.Equal(t.T(), "", binlogResp.ErrorMsg) 358 require.Equal(t.T(), "binlog operator not found", binlogResp.Results["task1"].ErrorMsg) 359 360 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{}, nil).Once() 361 w = httptest.NewRecorder() 362 r = httptest.NewRequest("DELETE", baseURL+"binlog/tasks/"+"task1"+"?binlog_pos='mysql-bin.000001,4'", nil) 363 t.engine.ServeHTTP(w, r) 364 require.Equal(t.T(), http.StatusNoContent, w.Code) 365 } 366 367 func (t *testDMOpenAPISuite) TestDMAPIGetSchema() { 368 w := httptest.NewRecorder() 369 r := httptest.NewRequest("GET", "/api/v1/jobs/job-not-exist/schema", nil) 370 t.engine.ServeHTTP(w, r) 371 require.Equal(t.T(), http.StatusNotFound, w.Code) 372 373 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 374 Msg: "targets", 375 }, nil).Once() 376 w = httptest.NewRecorder() 377 r = httptest.NewRequest("GET", baseURL+"schema/tasks/"+"task1"+"?target=true", nil) 378 t.engine.ServeHTTP(w, r) 379 require.Equal(t.T(), http.StatusOK, w.Code) 380 var binlogSchemaResp dmpkg.BinlogSchemaResponse 381 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogSchemaResp)) 382 require.Equal(t.T(), "", binlogSchemaResp.ErrorMsg) 383 require.Equal(t.T(), "targets", binlogSchemaResp.Results["task1"].Msg) 384 385 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 386 Msg: "tables", 387 }, nil).Once() 388 w = httptest.NewRecorder() 389 r = httptest.NewRequest("GET", baseURL+"schema/tasks/"+"task1"+"?database='db'", nil) 390 t.engine.ServeHTTP(w, r) 391 require.Equal(t.T(), http.StatusOK, w.Code) 392 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogSchemaResp)) 393 require.Equal(t.T(), "", binlogSchemaResp.ErrorMsg) 394 require.Equal(t.T(), "tables", binlogSchemaResp.Results["task1"].Msg) 395 396 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 397 Msg: "table", 398 }, nil).Once() 399 w = httptest.NewRecorder() 400 r = httptest.NewRequest("GET", baseURL+"schema/tasks/"+"task1"+"?database='db'&table='tb'", nil) 401 t.engine.ServeHTTP(w, r) 402 require.Equal(t.T(), http.StatusOK, w.Code) 403 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogSchemaResp)) 404 require.Equal(t.T(), "", binlogSchemaResp.ErrorMsg) 405 require.Equal(t.T(), "table", binlogSchemaResp.Results["task1"].Msg) 406 407 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 408 Msg: "databases", 409 }, nil).Once() 410 w = httptest.NewRecorder() 411 r = httptest.NewRequest("GET", baseURL+"schema/tasks/"+"task1", nil) 412 t.engine.ServeHTTP(w, r) 413 require.Equal(t.T(), http.StatusOK, w.Code) 414 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogSchemaResp)) 415 require.Equal(t.T(), "", binlogSchemaResp.ErrorMsg) 416 require.Equal(t.T(), "databases", binlogSchemaResp.Results["task1"].Msg) 417 418 w = httptest.NewRecorder() 419 r = httptest.NewRequest("GET", baseURL+"schema/tasks/"+"task1"+"?table='tb'", nil) 420 t.engine.ServeHTTP(w, r) 421 require.Equal(t.T(), http.StatusInternalServerError, w.Code) 422 equalError(t.T(), "invalid query params for get schema", w.Body) 423 } 424 425 func (t *testDMOpenAPISuite) TestDMAPISetSchema() { 426 w := httptest.NewRecorder() 427 r := httptest.NewRequest("PUT", "/api/v1/jobs/job-not-exist/schema", nil) 428 t.engine.ServeHTTP(w, r) 429 require.Equal(t.T(), http.StatusNotFound, w.Code) 430 431 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(nil, errors.New("task not paused")).Once() 432 from := true 433 req := openapi.SetBinlogSchemaRequest{Database: "db", Table: "tb", FromSource: &from} 434 bs, err := json.Marshal(req) 435 require.NoError(t.T(), err) 436 w = httptest.NewRecorder() 437 r = httptest.NewRequest("PUT", baseURL+"schema/tasks/"+"task1", bytes.NewReader(bs)) 438 r.Header.Set("Content-Type", "application/json") 439 t.engine.ServeHTTP(w, r) 440 require.Equal(t.T(), http.StatusOK, w.Code) 441 var binlogSchemaResp dmpkg.BinlogSchemaResponse 442 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogSchemaResp)) 443 require.Equal(t.T(), "", binlogSchemaResp.ErrorMsg) 444 require.Equal(t.T(), "task not paused", binlogSchemaResp.Results["task1"].ErrorMsg) 445 446 t.messageAgent.On("SendRequest", tmock.Anything, tmock.Anything, tmock.Anything, tmock.Anything).Return(&dmpkg.CommonTaskResponse{ 447 Msg: "success", 448 }, nil).Once() 449 req = openapi.SetBinlogSchemaRequest{Database: "db", Table: "tb", FromTarget: &from} 450 bs, err = json.Marshal(req) 451 require.NoError(t.T(), err) 452 w = httptest.NewRecorder() 453 r = httptest.NewRequest("PUT", baseURL+"schema/tasks/"+"task1", bytes.NewReader(bs)) 454 r.Header.Set("Content-Type", "application/json") 455 t.engine.ServeHTTP(w, r) 456 require.Equal(t.T(), http.StatusOK, w.Code) 457 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &binlogSchemaResp)) 458 require.Equal(t.T(), "", binlogSchemaResp.ErrorMsg) 459 require.Equal(t.T(), "", binlogSchemaResp.Results["task1"].ErrorMsg) 460 require.Equal(t.T(), "success", binlogSchemaResp.Results["task1"].Msg) 461 } 462 463 func (t *testDMOpenAPISuite) TestJobMasterNotInitialized() { 464 t.jm.initialized.Store(false) 465 defer t.jm.initialized.Store(true) 466 467 w := httptest.NewRecorder() 468 r := httptest.NewRequest("GET", baseURL+"config", nil) 469 t.engine.ServeHTTP(w, r) 470 require.Equal(t.T(), errors.HTTPStatusCode(errors.ErrJobNotRunning), w.Code) 471 var httpErr engineOpenAPI.HTTPError 472 require.NoError(t.T(), json.Unmarshal(w.Body.Bytes(), &httpErr)) 473 require.Equal(t.T(), string(errors.ErrJobNotRunning.RFCCode()), httpErr.Code) 474 } 475 476 func equalError(t *testing.T, expected string, body *bytes.Buffer) { 477 var httpErr engineOpenAPI.HTTPError 478 json.Unmarshal(body.Bytes(), &httpErr) 479 require.Equal(t, expected, httpErr.Message) 480 } 481 482 func TestHTTPErrorHandler(t *testing.T) { 483 t.Parallel() 484 485 testCases := []struct { 486 err error 487 code string 488 message string 489 }{ 490 { 491 errors.New("unknown error"), 492 string(errors.ErrUnknown.RFCCode()), 493 "unknown error", 494 }, 495 { 496 errors.ErrDeserializeConfig.GenWithStackByArgs(), 497 string(errors.ErrDeserializeConfig.RFCCode()), 498 errors.ErrDeserializeConfig.GetMsg(), 499 }, 500 { 501 terror.ErrDBBadConn.Generate(), 502 "DM:ErrDBBadConn", 503 terror.ErrDBBadConn.Generate().Error(), 504 }, 505 { 506 terror.ErrDBInvalidConn.Generate(), 507 "DM:ErrDBInvalidConn", 508 terror.ErrDBInvalidConn.Generate().Error(), 509 }, 510 } 511 512 for _, tc := range testCases { 513 engine := gin.New() 514 engine.Use(httpErrorHandler()) 515 engine.GET("/test", func(c *gin.Context) { 516 c.Error(tc.err) 517 }) 518 519 w := httptest.NewRecorder() 520 r := httptest.NewRequest("GET", "/test", nil) 521 engine.ServeHTTP(w, r) 522 require.Equal(t, errors.HTTPStatusCode(tc.err), w.Code) 523 524 var httpErr engineOpenAPI.HTTPError 525 err := json.Unmarshal(w.Body.Bytes(), &httpErr) 526 require.NoError(t, err) 527 require.Equal(t, tc.code, httpErr.Code) 528 require.Equal(t, tc.message, httpErr.Message) 529 } 530 }