github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/orm/client_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 orm
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"database/sql/driver"
    20  	"fmt"
    21  	"reflect"
    22  	"regexp"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/DATA-DOG/go-sqlmock"
    27  	"github.com/go-sql-driver/mysql"
    28  	"github.com/pingcap/failpoint"
    29  	frameModel "github.com/pingcap/tiflow/engine/framework/model"
    30  	engineModel "github.com/pingcap/tiflow/engine/model"
    31  	resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model"
    32  	metaMock "github.com/pingcap/tiflow/engine/pkg/meta/mock"
    33  	metaModel "github.com/pingcap/tiflow/engine/pkg/meta/model"
    34  	"github.com/pingcap/tiflow/engine/pkg/orm/model"
    35  	"github.com/pingcap/tiflow/pkg/errors"
    36  	"github.com/pingcap/tiflow/pkg/label"
    37  	"github.com/stretchr/testify/require"
    38  )
    39  
    40  const (
    41  	defaultTestStoreType = metaModel.StoreTypeMySQL
    42  )
    43  
    44  type tCase struct {
    45  	fn     string        // function name
    46  	inputs []interface{} // function args
    47  
    48  	output interface{} // function output
    49  	err    error       // function error
    50  
    51  	mockExpectResFn func(mock sqlmock.Sqlmock) // sqlmock expectation
    52  }
    53  
    54  func mockGetDBConn(t *testing.T) (*sql.DB, sqlmock.Sqlmock) {
    55  	db, mock, err := sqlmock.New()
    56  	require.Nil(t, err)
    57  	// common execution for orm
    58  	mock.ExpectQuery("SELECT VERSION()").
    59  		WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).AddRow("5.7.35-log"))
    60  	return db, mock
    61  }
    62  
    63  type anyTime struct{}
    64  
    65  func (a anyTime) Match(v driver.Value) bool {
    66  	_, ok := v.(time.Time)
    67  	return ok
    68  }
    69  
    70  func TestNewMetaOpsClient(t *testing.T) {
    71  	t.Parallel()
    72  
    73  	var store metaModel.StoreConfig
    74  	store.SetEndpoints("127.0.0.1:3306")
    75  	_, err := NewClient(nil)
    76  	require.Error(t, err)
    77  
    78  	sqlDB, mock := mockGetDBConn(t)
    79  	defer sqlDB.Close()
    80  	defer mock.ExpectClose()
    81  	_, err = newClient(sqlDB, defaultTestStoreType)
    82  	require.Nil(t, err)
    83  }
    84  
    85  func TestProject(t *testing.T) {
    86  	t.Parallel()
    87  
    88  	sqlDB, mock := mockGetDBConn(t)
    89  	defer sqlDB.Close()
    90  	defer mock.ExpectClose()
    91  	cli, err := newClient(sqlDB, defaultTestStoreType)
    92  	require.Nil(t, err)
    93  	require.NotNil(t, cli)
    94  
    95  	tm := time.Now()
    96  	createdAt := tm.Add(time.Duration(1))
    97  	updatedAt := tm.Add(time.Duration(1))
    98  
    99  	testCases := []tCase{
   100  		{
   101  			fn: "CreateProject",
   102  			inputs: []interface{}{
   103  				&model.ProjectInfo{
   104  					Model: model.Model{
   105  						CreatedAt: createdAt,
   106  						UpdatedAt: updatedAt,
   107  					},
   108  					ID:   "p111",
   109  					Name: "tenant1",
   110  				},
   111  			},
   112  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   113  				mock.ExpectExec("INSERT INTO `project_infos` [(]`created_at`,`updated_at`,`id`,"+
   114  					"`name`[)]").WithArgs(createdAt, updatedAt, "p111", "tenant1").WillReturnResult(sqlmock.NewResult(1, 1))
   115  			},
   116  		},
   117  		{
   118  			fn: "CreateProject",
   119  			inputs: []interface{}{
   120  				&model.ProjectInfo{
   121  					Model: model.Model{
   122  						SeqID:     1,
   123  						CreatedAt: createdAt,
   124  						UpdatedAt: updatedAt,
   125  					},
   126  					ID:   "p111",
   127  					Name: "tenant2",
   128  				},
   129  			},
   130  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   131  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   132  				mock.ExpectExec("INSERT INTO `project_infos` [(]`created_at`,`updated_at`,`id`,"+
   133  					"`name`,`seq_id`[)]").WithArgs(createdAt, updatedAt, "p111", "tenant2", 1).WillReturnError(errors.New("projectID is duplicated"))
   134  			},
   135  		},
   136  		{
   137  			fn: "DeleteProject",
   138  			inputs: []interface{}{
   139  				"p111",
   140  			},
   141  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   142  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   143  				mock.ExpectExec("DELETE FROM `project_infos` WHERE id").WithArgs("p111").WillReturnError(errors.New("DeleteProject error"))
   144  			},
   145  		},
   146  		{
   147  			fn: "DeleteProject",
   148  			inputs: []interface{}{
   149  				"p111",
   150  			},
   151  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   152  				mock.ExpectExec("DELETE FROM `project_infos` WHERE id").WithArgs("p111").WillReturnResult(sqlmock.NewResult(0, 1))
   153  			},
   154  		},
   155  		{
   156  			fn:     "QueryProjects",
   157  			inputs: []interface{}{},
   158  			output: []*model.ProjectInfo{
   159  				{
   160  					Model: model.Model{
   161  						SeqID:     1,
   162  						CreatedAt: createdAt,
   163  						UpdatedAt: updatedAt,
   164  					},
   165  					ID:   "p111",
   166  					Name: "tenant1",
   167  				},
   168  				{
   169  					Model: model.Model{
   170  						SeqID:     2,
   171  						CreatedAt: createdAt,
   172  						UpdatedAt: updatedAt,
   173  					},
   174  					ID:   "p111",
   175  					Name: "tenant2",
   176  				},
   177  			},
   178  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   179  				mock.ExpectQuery("SELECT [*] FROM `project_infos`").WillReturnRows(sqlmock.NewRows([]string{
   180  					"created_at", "updated_at", "id", "name",
   181  					"seq_id",
   182  				}).AddRow(createdAt, updatedAt, "p111", "tenant1", 1).AddRow(createdAt, updatedAt, "p111", "tenant2", 2))
   183  			},
   184  		},
   185  		{
   186  			fn:     "QueryProjects",
   187  			inputs: []interface{}{},
   188  			err:    errors.ErrMetaOpFail.GenWithStackByArgs(),
   189  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   190  				mock.ExpectQuery("SELECT [*] FROM `project_infos`").WillReturnError(errors.New("QueryProjects error"))
   191  			},
   192  		},
   193  		{
   194  			// SELECT * FROM `project_infos` WHERE project_id = '111-222-333' ORDER BY `project_infos`.`id` LIMIT 1
   195  			fn: "GetProjectByID",
   196  			inputs: []interface{}{
   197  				"111-222-333",
   198  			},
   199  			output: &model.ProjectInfo{
   200  				Model: model.Model{
   201  					SeqID:     2,
   202  					CreatedAt: createdAt,
   203  					UpdatedAt: updatedAt,
   204  				},
   205  				ID:   "p111",
   206  				Name: "tenant1",
   207  			},
   208  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   209  				mock.ExpectQuery("SELECT [*] FROM `project_infos` WHERE id").WithArgs("111-222-333").WillReturnRows(
   210  					sqlmock.NewRows([]string{
   211  						"created_at", "updated_at", "id", "name",
   212  						"seq_id",
   213  					}).AddRow(createdAt, updatedAt, "p111", "tenant1", 2))
   214  			},
   215  		},
   216  		{
   217  			fn: "GetProjectByID",
   218  			inputs: []interface{}{
   219  				"p111",
   220  			},
   221  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   222  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   223  				mock.ExpectQuery("SELECT [*] FROM `project_infos` WHERE id").WithArgs("p111").WillReturnError(
   224  					errors.New("GetProjectByID error"))
   225  			},
   226  		},
   227  	}
   228  
   229  	for _, tc := range testCases {
   230  		testInner(t, mock, cli, tc)
   231  	}
   232  }
   233  
   234  func TestProjectOperation(t *testing.T) {
   235  	t.Parallel()
   236  
   237  	sqlDB, mock := mockGetDBConn(t)
   238  	defer sqlDB.Close() //nolint: staticcheck
   239  	defer mock.ExpectClose()
   240  	cli, err := newClient(sqlDB, defaultTestStoreType)
   241  	require.Nil(t, err)
   242  	require.NotNil(t, cli)
   243  
   244  	tm := time.Now()
   245  	tm1 := tm.Add(time.Duration(1))
   246  
   247  	testCases := []tCase{
   248  		{
   249  			// SELECT * FROM `project_operations` WHERE project_id = '111'
   250  			fn: "QueryProjectOperations",
   251  			inputs: []interface{}{
   252  				"p111",
   253  			},
   254  			output: []*model.ProjectOperation{
   255  				{
   256  					SeqID:     1,
   257  					ProjectID: "p111",
   258  					Operation: "Submit",
   259  					JobID:     "j222",
   260  					CreatedAt: tm,
   261  				},
   262  				{
   263  					SeqID:     2,
   264  					ProjectID: "p112",
   265  					Operation: "Drop",
   266  					JobID:     "j222",
   267  					CreatedAt: tm1,
   268  				},
   269  			},
   270  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   271  				mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111").WillReturnRows(
   272  					sqlmock.NewRows([]string{"seq_id", "project_id", "operation", "job_id", "created_at"}).AddRow(
   273  						1, "p111", "Submit", "j222", tm).AddRow(
   274  						2, "p112", "Drop", "j222", tm1))
   275  			},
   276  		},
   277  		{
   278  			fn: "QueryProjectOperations",
   279  			inputs: []interface{}{
   280  				"p111",
   281  			},
   282  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   283  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   284  				mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111").WillReturnError(errors.New("QueryProjectOperations error"))
   285  			},
   286  		},
   287  		{
   288  			// SELECT * FROM `project_operations` WHERE project_id = '111' AND created_at >= '2022-04-13 23:51:42.46' AND created_at <= '2022-04-13 23:51:42.46'
   289  			fn: "QueryProjectOperationsByTimeRange",
   290  			inputs: []interface{}{
   291  				"p111",
   292  				TimeRange{
   293  					start: tm,
   294  					end:   tm1,
   295  				},
   296  			},
   297  			output: []*model.ProjectOperation{
   298  				{
   299  					SeqID:     1,
   300  					ProjectID: "p111",
   301  					Operation: "Submit",
   302  					JobID:     "j222",
   303  					CreatedAt: tm,
   304  				},
   305  				{
   306  					SeqID:     2,
   307  					ProjectID: "p112",
   308  					Operation: "Drop",
   309  					JobID:     "j222",
   310  					CreatedAt: tm1,
   311  				},
   312  			},
   313  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   314  				mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111", tm, tm1).WillReturnRows(
   315  					sqlmock.NewRows([]string{"seq_id", "project_id", "operation", "job_id", "created_at"}).AddRow(
   316  						1, "p111", "Submit", "j222", tm).AddRow(
   317  						2, "p112", "Drop", "j222", tm1))
   318  			},
   319  		},
   320  		{
   321  			fn: "QueryProjectOperationsByTimeRange",
   322  			inputs: []interface{}{
   323  				"p111",
   324  				TimeRange{
   325  					start: tm,
   326  					end:   tm1,
   327  				},
   328  			},
   329  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   330  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   331  				mock.ExpectQuery("SELECT [*] FROM `project_operations` WHERE project_id").WithArgs("p111", tm, tm1).WillReturnError(
   332  					errors.New("QueryProjectOperationsByTimeRange error"))
   333  			},
   334  		},
   335  	}
   336  
   337  	for _, tc := range testCases {
   338  		testInner(t, mock, cli, tc)
   339  	}
   340  }
   341  
   342  func TestJob(t *testing.T) {
   343  	t.Parallel()
   344  
   345  	sqlDB, mock := mockGetDBConn(t)
   346  	defer sqlDB.Close()
   347  	defer mock.ExpectClose()
   348  	cli, err := newClient(sqlDB, defaultTestStoreType)
   349  	require.Nil(t, err)
   350  	require.NotNil(t, cli)
   351  
   352  	tm := time.Now()
   353  	createdAt := tm.Add(time.Duration(1))
   354  	updatedAt := tm.Add(time.Duration(1))
   355  
   356  	extForTest := frameModel.MasterMetaExt{
   357  		Selectors: []*label.Selector{
   358  			{
   359  				Key:    "test",
   360  				Target: "test-val",
   361  				Op:     label.OpEq,
   362  			},
   363  		},
   364  	}
   365  	extJSONForTest := `{"selectors":[{"label":"test","target":"test-val","op":"eq"}]}`
   366  
   367  	testCases := []tCase{
   368  		{
   369  			fn: "InsertJob",
   370  			inputs: []interface{}{
   371  				&frameModel.MasterMeta{
   372  					Model: model.Model{
   373  						CreatedAt: createdAt,
   374  						UpdatedAt: updatedAt,
   375  					},
   376  					ProjectID: "p111",
   377  					ID:        "j111",
   378  					Type:      1,
   379  					NodeID:    "n111",
   380  					Epoch:     1,
   381  					State:     1,
   382  					Addr:      "127.0.0.1",
   383  					Config:    []byte{0x11, 0x22},
   384  					Ext:       extForTest,
   385  				},
   386  			},
   387  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   388  				mock.ExpectExec("INSERT INTO `master_meta` [(]`created_at`,"+
   389  					"`updated_at`,`project_id`,`id`,`type`,`state`,`node_id`,"+
   390  					"`address`,`epoch`,`config`,`error_message`,`detail`,"+
   391  					"`ext`,`deleted`[)]").
   392  					WithArgs(createdAt, updatedAt, "p111", "j111", 1, 1, "n111",
   393  						"127.0.0.1", 1, []byte{0x11, 0x22}, sqlmock.AnyArg(),
   394  						sqlmock.AnyArg(), extForTest, nil).
   395  					WillReturnResult(sqlmock.NewResult(1, 1))
   396  			},
   397  		},
   398  		{
   399  			fn: "InsertJob",
   400  			inputs: []interface{}{
   401  				&frameModel.MasterMeta{
   402  					Model: model.Model{
   403  						CreatedAt: createdAt,
   404  						UpdatedAt: updatedAt,
   405  					},
   406  					ProjectID: "p111",
   407  					ID:        "j111",
   408  					Type:      1,
   409  					NodeID:    "n111",
   410  					Epoch:     1,
   411  					State:     1,
   412  					Addr:      "127.0.0.1",
   413  					Config:    []byte{0x11, 0x22},
   414  					Ext:       extForTest,
   415  				},
   416  			},
   417  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   418  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   419  				mock.ExpectExec("INSERT INTO `master_meta` [(]`created_at`," +
   420  					"`updated_at`,`project_id`,`id`,`type`,`state`,`node_id`," +
   421  					"`address`,`epoch`,`config`,`error_message`,`detail`," +
   422  					"`ext`,`deleted`[)]").
   423  					WillReturnError(&mysql.MySQLError{Number: 1062, Message: "Duplicate entry '123456' for key 'uidx_mid'"})
   424  			},
   425  		},
   426  		{
   427  			fn: "UpsertJob",
   428  			inputs: []interface{}{
   429  				&frameModel.MasterMeta{
   430  					ProjectID: "p111",
   431  					ID:        "j111",
   432  					Type:      1,
   433  					NodeID:    "n111",
   434  					Epoch:     1,
   435  					State:     1,
   436  					Addr:      "127.0.0.1",
   437  					Config:    []byte{0x11, 0x22},
   438  					Ext:       extForTest,
   439  				},
   440  			},
   441  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   442  				mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 1))
   443  			},
   444  		},
   445  		{
   446  			fn: "DeleteJob",
   447  			inputs: []interface{}{
   448  				"j111",
   449  			},
   450  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   451  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   452  				expectedSQL := "UPDATE `master_meta` SET `deleted`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL"
   453  				mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).WithArgs(
   454  					anyTime{}, "j111").WillReturnError(errors.New("DeleteJob error"))
   455  			},
   456  		},
   457  		{
   458  			// DELETE FROM `master_meta` WHERE project_id = '111-222-334' AND job_id = '111'
   459  			fn: "DeleteJob",
   460  			inputs: []interface{}{
   461  				"j112",
   462  			},
   463  			output: &ormResult{
   464  				rowsAffected: 1,
   465  			},
   466  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   467  				expectedSQL := "UPDATE `master_meta` SET `deleted`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL"
   468  				mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).WithArgs(
   469  					anyTime{}, "j112").WillReturnResult(sqlmock.NewResult(0, 1))
   470  			},
   471  		},
   472  		{
   473  			fn: "UpdateJob",
   474  			inputs: []interface{}{
   475  				"j111",
   476  				(&frameModel.MasterMeta{
   477  					ProjectID: "p111",
   478  					ID:        "j111",
   479  					Type:      1,
   480  					NodeID:    "n111",
   481  					Epoch:     1,
   482  					State:     1,
   483  					Addr:      "127.0.0.1",
   484  					Config:    []byte{0x11, 0x22},
   485  				}).RefreshValues(),
   486  			},
   487  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   488  				mock.ExpectExec(regexp.QuoteMeta(
   489  					"UPDATE `master_meta` SET `address`=?,`epoch`=?,`node_id`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")).
   490  					WillReturnResult(sqlmock.NewResult(0, 1))
   491  			},
   492  		},
   493  		{
   494  			fn: "UpdateJob",
   495  			inputs: []interface{}{
   496  				"j111",
   497  				(&frameModel.MasterMeta{
   498  					ProjectID: "p111",
   499  					ID:        "j111",
   500  					Type:      1,
   501  					NodeID:    "n111",
   502  					Epoch:     1,
   503  					State:     1,
   504  					Addr:      "127.0.0.1",
   505  					Config:    []byte{0x11, 0x22},
   506  				}).UpdateStateValues(),
   507  			},
   508  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   509  				mock.ExpectExec(regexp.QuoteMeta(
   510  					"UPDATE `master_meta` SET `state`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")).
   511  					WillReturnResult(sqlmock.NewResult(0, 1))
   512  			},
   513  		},
   514  		{
   515  			fn: "UpdateJob",
   516  			inputs: []interface{}{
   517  				"j111",
   518  				(&frameModel.MasterMeta{
   519  					ProjectID: "p111",
   520  					ID:        "j111",
   521  					Type:      1,
   522  					NodeID:    "n111",
   523  					Epoch:     1,
   524  					State:     1,
   525  					Addr:      "127.0.0.1",
   526  					Config:    []byte{0x11, 0x22},
   527  					ErrorMsg:  "error message",
   528  					Detail:    []byte("job detail"),
   529  				}).UpdateErrorValues(),
   530  			},
   531  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   532  				mock.ExpectExec(regexp.QuoteMeta(
   533  					"UPDATE `master_meta` SET `error_message`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")).
   534  					WillReturnResult(sqlmock.NewResult(0, 1))
   535  			},
   536  		},
   537  		{
   538  			fn: "UpdateJob",
   539  			inputs: []interface{}{
   540  				"j111",
   541  				(&frameModel.MasterMeta{
   542  					ProjectID: "p111",
   543  					ID:        "j111",
   544  					Type:      1,
   545  					NodeID:    "n111",
   546  					Epoch:     1,
   547  					State:     1,
   548  					Addr:      "127.0.0.1",
   549  					Config:    []byte{0x11, 0x22},
   550  					ErrorMsg:  "error message",
   551  					Detail:    []byte("job detail"),
   552  				}).ExitValues(),
   553  			},
   554  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   555  				mock.ExpectExec(regexp.QuoteMeta(
   556  					"UPDATE `master_meta` SET `detail`=?,`error_message`=?,`state`=?,`updated_at`=? WHERE id = ? AND `master_meta`.`deleted` IS NULL")).
   557  					WillReturnResult(sqlmock.NewResult(0, 1))
   558  			},
   559  		},
   560  		{
   561  			// SELECT * FROM `master_meta` WHERE project_id = '111-222-333' AND job_id = '111' ORDER BY `master_meta`.`id` LIMIT 1
   562  			fn: "GetJobByID",
   563  			inputs: []interface{}{
   564  				"j111",
   565  			},
   566  			output: &frameModel.MasterMeta{
   567  				Model: model.Model{
   568  					SeqID:     1,
   569  					CreatedAt: createdAt,
   570  					UpdatedAt: updatedAt,
   571  				},
   572  				ProjectID: "p111",
   573  				ID:        "j111",
   574  				Type:      1,
   575  				NodeID:    "n111",
   576  				Epoch:     1,
   577  				State:     1,
   578  				Addr:      "127.0.0.1",
   579  				Config:    []byte{0x11, 0x22},
   580  				Ext:       extForTest,
   581  			},
   582  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   583  				expectedSQL := "SELECT * FROM `master_meta` WHERE id = ? AND `master_meta`.`deleted` IS NULL"
   584  				mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).WithArgs("j111").WillReturnRows(
   585  					sqlmock.NewRows([]string{
   586  						"created_at", "updated_at", "project_id", "id",
   587  						"type", "state", "node_id", "address", "epoch", "config", "seq_id", "ext",
   588  					}).AddRow(
   589  						createdAt, updatedAt, "p111", "j111", 1, 1, "n111", "127.0.0.1", 1, []byte{0x11, 0x22}, 1,
   590  						extJSONForTest))
   591  			},
   592  		},
   593  		{
   594  			fn: "GetJobByID",
   595  			inputs: []interface{}{
   596  				"j111",
   597  			},
   598  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   599  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   600  				mock.ExpectQuery("SELECT [*] FROM `master_meta` WHERE id").WithArgs("j111").WillReturnError(
   601  					errors.New("GetJobByID error"))
   602  			},
   603  		},
   604  		{
   605  			// SELECT * FROM `master_meta` WHERE project_id = '111-222-333'
   606  			fn: "QueryJobsByProjectID",
   607  			inputs: []interface{}{
   608  				"p111",
   609  			},
   610  			output: []*frameModel.MasterMeta{
   611  				{
   612  					Model: model.Model{
   613  						SeqID:     1,
   614  						CreatedAt: createdAt,
   615  						UpdatedAt: updatedAt,
   616  					},
   617  					ProjectID: "p111",
   618  					ID:        "j111",
   619  					Type:      1,
   620  					NodeID:    "n111",
   621  					Epoch:     1,
   622  					State:     1,
   623  					Addr:      "1.1.1.1",
   624  					Config:    []byte{0x11, 0x22},
   625  					Ext:       extForTest,
   626  				},
   627  			},
   628  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   629  				mock.ExpectQuery("SELECT [*] FROM `master_meta` WHERE project_id").WithArgs("p111").WillReturnRows(
   630  					sqlmock.NewRows([]string{
   631  						"created_at", "updated_at", "project_id", "id",
   632  						"type", "state", "node_id", "address", "epoch", "config", "seq_id", "ext",
   633  					}).AddRow(
   634  						createdAt, updatedAt, "p111", "j111", 1, 1, "n111", "1.1.1.1", 1, []byte{0x11, 0x22}, 1, extJSONForTest))
   635  			},
   636  		},
   637  		{
   638  			fn: "QueryJobsByProjectID",
   639  			inputs: []interface{}{
   640  				"p111",
   641  			},
   642  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   643  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   644  				mock.ExpectQuery("SELECT [*] FROM `master_meta` WHERE project_id").WithArgs("p111").WillReturnError(
   645  					errors.New("QueryJobsByProjectID error"))
   646  			},
   647  		},
   648  		{
   649  			//  SELECT * FROM `master_meta` WHERE project_id = '111-222-333' AND job_status = 1
   650  			fn: "QueryJobsByState",
   651  			inputs: []interface{}{
   652  				"p111",
   653  				1,
   654  			},
   655  			output: []*frameModel.MasterMeta{
   656  				{
   657  					Model: model.Model{
   658  						SeqID:     1,
   659  						CreatedAt: createdAt,
   660  						UpdatedAt: updatedAt,
   661  					},
   662  					ProjectID: "p111",
   663  					ID:        "j111",
   664  					Type:      1,
   665  					NodeID:    "n111",
   666  					Epoch:     1,
   667  					State:     1,
   668  					Addr:      "127.0.0.1",
   669  					Config:    []byte{0x11, 0x22},
   670  					Ext:       extForTest,
   671  				},
   672  			},
   673  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   674  				expectedSQL := "SELECT * FROM `master_meta` WHERE (project_id = ? AND state = ?) AND `master_meta`.`deleted` IS NULL"
   675  				mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).WithArgs("p111", 1).WillReturnRows(
   676  					sqlmock.NewRows([]string{
   677  						"created_at", "updated_at", "project_id", "id",
   678  						"type", "state", "node_id", "address", "epoch", "config", "seq_id", "ext",
   679  					}).AddRow(
   680  						createdAt, updatedAt, "p111", "j111", 1, 1, "n111", "127.0.0.1", 1, []byte{0x11, 0x22}, 1, extJSONForTest))
   681  			},
   682  		},
   683  		{
   684  			fn: "QueryJobsByState",
   685  			inputs: []interface{}{
   686  				"p111",
   687  				1,
   688  			},
   689  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   690  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   691  				expectedSQL := "SELECT * FROM `master_meta` WHERE (project_id = ? AND state = ?) AND `master_meta`.`deleted` IS NULL"
   692  				mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).WithArgs("p111", 1).WillReturnError(
   693  					errors.New("QueryJobsByState error"))
   694  			},
   695  		},
   696  	}
   697  
   698  	for _, tc := range testCases {
   699  		testInner(t, mock, cli, tc)
   700  	}
   701  }
   702  
   703  func TestWorker(t *testing.T) {
   704  	t.Parallel()
   705  
   706  	sqlDB, mock := mockGetDBConn(t)
   707  	defer sqlDB.Close()
   708  	defer mock.ExpectClose()
   709  	cli, err := newClient(sqlDB, defaultTestStoreType)
   710  	require.Nil(t, err)
   711  	require.NotNil(t, cli)
   712  
   713  	tm := time.Now()
   714  	createdAt := tm.Add(time.Duration(1))
   715  	updatedAt := tm.Add(time.Duration(1))
   716  
   717  	testCases := []tCase{
   718  		{
   719  			// INSERT INTO `worker_statuses` (`created_at`,`updated_at`,`project_id`,`job_id`,`id`,`type`,`state`,`epoch`,`error_message`,`extend_bytes`)
   720  			// VALUES ('2022-04-29 18:49:40.932','2022-04-29 18:49:40.932','p111','j111','w222',1,'1',10,'error','<binary>') ON DUPLICATE KEY
   721  			// UPDATE `updated_at`=VALUES(`updated_at`),`project_id`=VALUES(`project_id`),`job_id`=VALUES(`job_id`),`id`=VALUES(`id`),
   722  			// `type`=VALUES(`type`),`state`=VALUES(`state`),`epoch`=VALUES(`epoch`),`error_message`=VALUES(`error_message`),`extend_bytes`=VALUES(`extend_bytes`)
   723  			fn: "UpsertWorker",
   724  			inputs: []interface{}{
   725  				&frameModel.WorkerStatus{
   726  					Model: model.Model{
   727  						CreatedAt: createdAt,
   728  						UpdatedAt: updatedAt,
   729  					},
   730  					ProjectID: "p111",
   731  					JobID:     "j111",
   732  					ID:        "w222",
   733  					Type:      1,
   734  					State:     1,
   735  					Epoch:     10,
   736  					ErrorMsg:  "error",
   737  					ExtBytes:  []byte{0x11, 0x22},
   738  				},
   739  			},
   740  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   741  				mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 1))
   742  			},
   743  		},
   744  		{
   745  			fn: "UpsertWorker",
   746  			inputs: []interface{}{
   747  				&frameModel.WorkerStatus{
   748  					Model: model.Model{
   749  						SeqID:     1,
   750  						CreatedAt: createdAt,
   751  						UpdatedAt: updatedAt,
   752  					},
   753  					ProjectID: "p111",
   754  					JobID:     "j111",
   755  					ID:        "w222",
   756  					Type:      1,
   757  					State:     1,
   758  					Epoch:     10,
   759  					ErrorMsg:  "error",
   760  					ExtBytes:  []byte{0x11, 0x22},
   761  				},
   762  			},
   763  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   764  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   765  				mock.ExpectExec("INSERT INTO `worker_statuses` [(]`created_at`,`updated_at`,`project_id`,`job_id`," +
   766  					"`id`,`type`,`state`,`epoch`,`error_message`,`extend_bytes`,`seq_id`[)]").WillReturnError(&mysql.MySQLError{Number: 1062, Message: "error"})
   767  			},
   768  		},
   769  		{
   770  			fn: "DeleteWorker",
   771  			inputs: []interface{}{
   772  				"j111",
   773  				"w222",
   774  			},
   775  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   776  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   777  				mock.ExpectExec("DELETE FROM `worker_statuses` WHERE job_id").WithArgs(
   778  					"j111", "w222").WillReturnError(errors.New("DeleteWorker error"))
   779  			},
   780  		},
   781  		{
   782  			// DELETE FROM `worker_statuses` WHERE project_id = '111-222-334' AND job_id = '111' AND worker_id = '222'
   783  			fn: "DeleteWorker",
   784  			inputs: []interface{}{
   785  				"j112",
   786  				"w223",
   787  			},
   788  			output: &ormResult{
   789  				rowsAffected: 1,
   790  			},
   791  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   792  				mock.ExpectExec("DELETE FROM `worker_statuses` WHERE job_id").WithArgs(
   793  					"j112", "w223").WillReturnResult(sqlmock.NewResult(0, 1))
   794  			},
   795  		},
   796  		{
   797  			// 'UPDATE `worker_statuses` SET `epoch`=?,`error-message`=?,`extend-bytes`=?,`id`=?,`job_id`=?,`project_id`=?,`status`=?,`type`=?,`updated_at`=? WHERE job_id = ? && id = ?'
   798  			fn: "UpdateWorker",
   799  			inputs: []interface{}{
   800  				&frameModel.WorkerStatus{
   801  					ProjectID: "p111",
   802  					JobID:     "j111",
   803  					ID:        "w111",
   804  					Type:      1,
   805  					State:     1,
   806  					Epoch:     10,
   807  					ErrorMsg:  "error",
   808  					ExtBytes:  []byte{0x11, 0x22},
   809  				},
   810  			},
   811  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   812  				mock.ExpectExec("UPDATE `worker_statuses` SET").WillReturnResult(sqlmock.NewResult(0, 1))
   813  			},
   814  		},
   815  		{
   816  			// SELECT * FROM `worker_statuses` WHERE project_id = '111-222-333' AND job_id = '111' AND
   817  			// worker_id = '222' ORDER BY `worker_statuses`.`id` LIMIT 1
   818  			fn: "GetWorkerByID",
   819  			inputs: []interface{}{
   820  				"j111",
   821  				"w222",
   822  			},
   823  			output: &frameModel.WorkerStatus{
   824  				Model: model.Model{
   825  					SeqID:     1,
   826  					CreatedAt: createdAt,
   827  					UpdatedAt: updatedAt,
   828  				},
   829  				ProjectID: "p111",
   830  				JobID:     "j111",
   831  				ID:        "w222",
   832  				Type:      1,
   833  				State:     1,
   834  				Epoch:     10,
   835  				ErrorMsg:  "error",
   836  				ExtBytes:  []byte{0x11, 0x22},
   837  			},
   838  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   839  				mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", "w222").WillReturnRows(
   840  					sqlmock.NewRows([]string{
   841  						"created_at", "updated_at", "project_id", "job_id",
   842  						"id", "type", "state", "epoch", "error_message", "extend_bytes", "seq_id",
   843  					}).AddRow(
   844  						createdAt, updatedAt, "p111", "j111", "w222", 1, 1, 10, "error", []byte{0x11, 0x22}, 1))
   845  			},
   846  		},
   847  		{
   848  			fn: "GetWorkerByID",
   849  			inputs: []interface{}{
   850  				"j111",
   851  				"w222",
   852  			},
   853  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   854  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   855  				mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", "w222").WillReturnError(
   856  					errors.New("GetWorkerByID error"))
   857  			},
   858  		},
   859  		{
   860  			// SELECT * FROM `worker_statuses` WHERE project_id = '111-222-333' AND job_id = '111'
   861  			fn: "QueryWorkersByMasterID",
   862  			inputs: []interface{}{
   863  				"j111",
   864  			},
   865  			output: []*frameModel.WorkerStatus{
   866  				{
   867  					Model: model.Model{
   868  						SeqID:     1,
   869  						CreatedAt: createdAt,
   870  						UpdatedAt: updatedAt,
   871  					},
   872  					ProjectID: "p111",
   873  					JobID:     "j111",
   874  					ID:        "w222",
   875  					Type:      1,
   876  					State:     1,
   877  					Epoch:     10,
   878  					ErrorMsg:  "error",
   879  					ExtBytes:  []byte{0x11, 0x22},
   880  				},
   881  			},
   882  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   883  				mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111").WillReturnRows(
   884  					sqlmock.NewRows([]string{
   885  						"created_at", "updated_at", "project_id", "job_id",
   886  						"id", "type", "state", "epoch", "error_message", "extend_bytes", "seq_id",
   887  					}).AddRow(
   888  						createdAt, updatedAt, "p111", "j111", "w222", 1, 1, 10, "error", []byte{0x11, 0x22}, 1))
   889  			},
   890  		},
   891  		{
   892  			fn: "QueryWorkersByMasterID",
   893  			inputs: []interface{}{
   894  				"j111",
   895  			},
   896  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   897  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   898  				mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111").WillReturnError(
   899  					errors.New("QueryWorkersByMasterID error"))
   900  			},
   901  		},
   902  		{
   903  			// SELECT * FROM `worker_statuses` WHERE project_id = '111-222-333' AND job_id = '111' AND worker_statuses = 1
   904  			fn: "QueryWorkersByState",
   905  			inputs: []interface{}{
   906  				"j111",
   907  				1,
   908  			},
   909  			output: []*frameModel.WorkerStatus{
   910  				{
   911  					Model: model.Model{
   912  						SeqID:     1,
   913  						CreatedAt: createdAt,
   914  						UpdatedAt: updatedAt,
   915  					},
   916  					ProjectID: "p111",
   917  					JobID:     "j111",
   918  					ID:        "w222",
   919  					Type:      1,
   920  					State:     1,
   921  					Epoch:     10,
   922  					ErrorMsg:  "error",
   923  					ExtBytes:  []byte{0x11, 0x22},
   924  				},
   925  			},
   926  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   927  				mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", 1).WillReturnRows(
   928  					sqlmock.NewRows([]string{
   929  						"created_at", "updated_at", "project_id", "job_id",
   930  						"id", "type", "state", "epoch", "error_message", "extend_bytes", "seq_id",
   931  					}).AddRow(
   932  						createdAt, updatedAt, "p111", "j111", "w222", 1, 1, 10, "error", []byte{0x11, 0x22}, 1))
   933  			},
   934  		},
   935  		{
   936  			fn: "QueryWorkersByState",
   937  			inputs: []interface{}{
   938  				"j111",
   939  				1,
   940  			},
   941  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
   942  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   943  				mock.ExpectQuery("SELECT [*] FROM `worker_statuses` WHERE job_id").WithArgs("j111", 1).WillReturnError(
   944  					errors.New("QueryWorkersByState error"))
   945  			},
   946  		},
   947  	}
   948  
   949  	for _, tc := range testCases {
   950  		testInner(t, mock, cli, tc)
   951  	}
   952  }
   953  
   954  func TestResource(t *testing.T) {
   955  	t.Parallel()
   956  
   957  	sqlDB, mock := mockGetDBConn(t)
   958  	defer sqlDB.Close()
   959  	defer mock.ExpectClose()
   960  	cli, err := newClient(sqlDB, defaultTestStoreType)
   961  	require.Nil(t, err)
   962  	require.NotNil(t, cli)
   963  
   964  	tm := time.Now()
   965  	createdAt := tm.Add(time.Duration(1))
   966  	updatedAt := tm.Add(time.Duration(1))
   967  
   968  	testCases := []tCase{
   969  		{
   970  			fn: "CreateResource",
   971  			inputs: []interface{}{
   972  				&resModel.ResourceMeta{
   973  					Model: model.Model{
   974  						SeqID:     1,
   975  						CreatedAt: createdAt,
   976  						UpdatedAt: updatedAt,
   977  					},
   978  					ID:        "r333",
   979  					ProjectID: "111-222-333",
   980  					TenantID:  "111-222-333",
   981  					Job:       "j111",
   982  					Worker:    "w222",
   983  					Executor:  "e444",
   984  					Deleted:   false,
   985  				},
   986  			},
   987  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   988  				mock.ExpectBegin()
   989  				mock.ExpectQuery(regexp.QuoteMeta("SELECT count(*) FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r333").WillReturnRows(
   990  					sqlmock.NewRows([]string{
   991  						"count(*)",
   992  					}).AddRow(0))
   993  				mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `resource_meta` (`created_at`,`updated_at`,`project_id`,`tenant_id`,`id`,`job_id`,"+
   994  					"`worker_id`,`executor_id`,`gc_pending`,`deleted`,`seq_id`)")).WithArgs(
   995  					createdAt, updatedAt, "111-222-333", "111-222-333", "r333", "j111", "w222", "e444", false, false, 1).
   996  					WillReturnResult(sqlmock.NewResult(1, 1))
   997  				mock.ExpectCommit()
   998  			},
   999  		},
  1000  		{
  1001  			fn: "CreateResource",
  1002  			inputs: []interface{}{
  1003  				&resModel.ResourceMeta{
  1004  					Model: model.Model{
  1005  						SeqID:     1,
  1006  						CreatedAt: createdAt,
  1007  						UpdatedAt: updatedAt,
  1008  					},
  1009  					ID:        "r333",
  1010  					ProjectID: "111-222-333",
  1011  					TenantID:  "111-222-333",
  1012  					Job:       "j111",
  1013  					Worker:    "w222",
  1014  					Executor:  "e444",
  1015  					Deleted:   false,
  1016  				},
  1017  			},
  1018  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1019  				mock.ExpectBegin()
  1020  				mock.ExpectQuery(regexp.QuoteMeta("SELECT count(*) FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r333").WillReturnRows(
  1021  					sqlmock.NewRows([]string{
  1022  						"count(1)",
  1023  					}).AddRow(1))
  1024  				mock.ExpectRollback()
  1025  			},
  1026  			err: errors.ErrDuplicateResourceID.GenWithStackByArgs("r333"),
  1027  		},
  1028  		{
  1029  			fn: "UpsertResource",
  1030  			inputs: []interface{}{
  1031  				&resModel.ResourceMeta{
  1032  					Model: model.Model{
  1033  						SeqID:     1,
  1034  						CreatedAt: createdAt,
  1035  						UpdatedAt: updatedAt,
  1036  					},
  1037  					ID:        "r333",
  1038  					ProjectID: "111-222-333",
  1039  					Job:       "j111",
  1040  					Worker:    "w222",
  1041  					Executor:  "e445",
  1042  					Deleted:   true,
  1043  				},
  1044  			},
  1045  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1046  				mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 1))
  1047  			},
  1048  		},
  1049  		{
  1050  			fn: "UpsertResource",
  1051  			inputs: []interface{}{
  1052  				&resModel.ResourceMeta{
  1053  					Model: model.Model{
  1054  						SeqID:     1,
  1055  						CreatedAt: createdAt,
  1056  						UpdatedAt: updatedAt,
  1057  					},
  1058  					ID:        "r333",
  1059  					ProjectID: "111-222-333",
  1060  					TenantID:  "",
  1061  					Job:       "j111",
  1062  					Worker:    "w222",
  1063  					Executor:  "e444",
  1064  					Deleted:   true,
  1065  				},
  1066  			},
  1067  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
  1068  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1069  				mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `resource_meta` (`created_at`,`updated_at`,`project_id`,`tenant_id`,`id`,`job_id`,"+
  1070  					"`worker_id`,`executor_id`,`gc_pending`,`deleted`,`seq_id`)")).WithArgs(
  1071  					createdAt, updatedAt, "111-222-333", "", "r333", "j111", "w222", "e444", false, true, 1).WillReturnError(&mysql.MySQLError{Number: 1062, Message: "error"})
  1072  			},
  1073  		},
  1074  		{
  1075  			fn: "DeleteResource",
  1076  			inputs: []interface{}{
  1077  				ResourceKey{
  1078  					JobID: "j111",
  1079  					ID:    "r222",
  1080  				},
  1081  			},
  1082  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
  1083  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1084  				mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs(
  1085  					"j111", "r222").WillReturnError(errors.New("DeleteReource error"))
  1086  			},
  1087  		},
  1088  		{
  1089  			fn: "DeleteResource",
  1090  			inputs: []interface{}{
  1091  				ResourceKey{
  1092  					JobID: "j111",
  1093  					ID:    "r223",
  1094  				},
  1095  			},
  1096  			output: &ormResult{
  1097  				rowsAffected: 1,
  1098  			},
  1099  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1100  				mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs(
  1101  					"j111", "r223").WillReturnResult(sqlmock.NewResult(0, 1))
  1102  			},
  1103  		},
  1104  		{
  1105  			// 'UPDATE `resource_meta` SET `deleted`=?,`executor_id`=?,`id`=?,`job_id`=?,`project_id`=?,`worker_id`=?,`updated_at`=? WHERE id = ?'
  1106  			fn: "UpdateResource",
  1107  			inputs: []interface{}{
  1108  				&resModel.ResourceMeta{
  1109  					ProjectID: "p111",
  1110  					ID:        "w111",
  1111  					Job:       "j111",
  1112  					Worker:    "w111",
  1113  					Executor:  "e111",
  1114  					Deleted:   true,
  1115  				},
  1116  			},
  1117  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1118  				mock.ExpectExec(regexp.QuoteMeta("UPDATE `resource_meta` SET")).WillReturnResult(sqlmock.NewResult(0, 1))
  1119  			},
  1120  		},
  1121  		{
  1122  			fn: "GetResourceByID",
  1123  			inputs: []interface{}{
  1124  				ResourceKey{
  1125  					JobID: "j111",
  1126  					ID:    "r222",
  1127  				},
  1128  			},
  1129  			output: &resModel.ResourceMeta{
  1130  				Model: model.Model{
  1131  					SeqID:     1,
  1132  					CreatedAt: createdAt,
  1133  					UpdatedAt: updatedAt,
  1134  				},
  1135  				ID:        "r333",
  1136  				ProjectID: "111-222-333",
  1137  				Job:       "j111",
  1138  				Worker:    "w222",
  1139  				Executor:  "e444",
  1140  				Deleted:   true,
  1141  			},
  1142  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1143  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r222").WillReturnRows(
  1144  					sqlmock.NewRows([]string{
  1145  						"created_at", "updated_at", "project_id", "id", "job_id",
  1146  						"worker_id", "executor_id", "deleted", "seq_id",
  1147  					}).AddRow(
  1148  						createdAt, updatedAt, "111-222-333", "r333", "j111", "w222", "e444", true, 1))
  1149  			},
  1150  		},
  1151  		{
  1152  			fn: "GetResourceByID",
  1153  			inputs: []interface{}{
  1154  				ResourceKey{
  1155  					JobID: "j111",
  1156  					ID:    "r222",
  1157  				},
  1158  			},
  1159  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
  1160  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1161  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id = ? AND id = ?")).WithArgs("j111", "r222").WillReturnError(
  1162  					errors.New("GetResourceByID error"))
  1163  			},
  1164  		},
  1165  		{
  1166  			fn: "QueryResourcesByJobID",
  1167  			inputs: []interface{}{
  1168  				"j111",
  1169  			},
  1170  			output: []*resModel.ResourceMeta{
  1171  				{
  1172  					Model: model.Model{
  1173  						SeqID:     1,
  1174  						CreatedAt: createdAt,
  1175  						UpdatedAt: updatedAt,
  1176  					},
  1177  					ID:        "r333",
  1178  					ProjectID: "111-222-333",
  1179  					Job:       "j111",
  1180  					Worker:    "w222",
  1181  					Executor:  "e444",
  1182  					Deleted:   true,
  1183  				},
  1184  			},
  1185  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1186  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id = ?")).WithArgs("j111").WillReturnRows(
  1187  					sqlmock.NewRows([]string{
  1188  						"created_at", "updated_at", "project_id", "tenant_id", "id", "job_id",
  1189  						"worker_id", "executor_id", "deleted", "seq_id",
  1190  					}).AddRow(
  1191  						createdAt, updatedAt, "111-222-333", "", "r333", "j111", "w222", "e444", true, 1))
  1192  			},
  1193  		},
  1194  		{
  1195  			fn: "QueryResourcesByJobID",
  1196  			inputs: []interface{}{
  1197  				"j111",
  1198  			},
  1199  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
  1200  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1201  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE job_id")).WithArgs("j111").WillReturnError(
  1202  					errors.New("QueryResourcesByJobID error"))
  1203  			},
  1204  		},
  1205  		{
  1206  			fn: "QueryResourcesByExecutorIDs",
  1207  			inputs: []interface{}{
  1208  				engineModel.ExecutorID("e444"),
  1209  			},
  1210  			output: []*resModel.ResourceMeta{
  1211  				{
  1212  					Model: model.Model{
  1213  						SeqID:     1,
  1214  						CreatedAt: createdAt,
  1215  						UpdatedAt: updatedAt,
  1216  					},
  1217  					ID:        "r333",
  1218  					ProjectID: "111-222-333",
  1219  					TenantID:  "333-222-111",
  1220  					Job:       "j111",
  1221  					Worker:    "w222",
  1222  					Executor:  "e444",
  1223  					Deleted:   true,
  1224  				},
  1225  			},
  1226  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1227  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE executor_id in")).WithArgs("e444").WillReturnRows(
  1228  					sqlmock.NewRows([]string{
  1229  						"created_at", "updated_at", "project_id", "tenant_id", "id", "job_id",
  1230  						"worker_id", "executor_id", "deleted", "seq_id",
  1231  					}).AddRow(createdAt, updatedAt, "111-222-333", "333-222-111", "r333", "j111", "w222", "e444", true, 1))
  1232  			},
  1233  		},
  1234  		{
  1235  			fn: "QueryResourcesByExecutorIDs",
  1236  			inputs: []interface{}{
  1237  				engineModel.ExecutorID("e444"),
  1238  				engineModel.ExecutorID("e555"),
  1239  			},
  1240  			output: []*resModel.ResourceMeta{
  1241  				{
  1242  					Model: model.Model{
  1243  						SeqID:     1,
  1244  						CreatedAt: createdAt,
  1245  						UpdatedAt: updatedAt,
  1246  					},
  1247  					ID:        "r333",
  1248  					ProjectID: "111-222-333",
  1249  					TenantID:  "333-222-111",
  1250  					Job:       "j111",
  1251  					Worker:    "w222",
  1252  					Executor:  "e444",
  1253  					Deleted:   true,
  1254  				},
  1255  				{
  1256  					Model: model.Model{
  1257  						SeqID:     1,
  1258  						CreatedAt: createdAt,
  1259  						UpdatedAt: updatedAt,
  1260  					},
  1261  					ID:        "r333",
  1262  					ProjectID: "111-222-333",
  1263  					TenantID:  "333-222-111",
  1264  					Job:       "j111",
  1265  					Worker:    "w222",
  1266  					Executor:  "e555",
  1267  					Deleted:   true,
  1268  				},
  1269  			},
  1270  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1271  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE executor_id in")).
  1272  					WithArgs("e444", "e555").WillReturnRows(
  1273  					sqlmock.NewRows([]string{
  1274  						"created_at", "updated_at", "project_id", "tenant_id", "id", "job_id",
  1275  						"worker_id", "executor_id", "deleted", "seq_id",
  1276  					}).AddRow(createdAt, updatedAt, "111-222-333", "333-222-111", "r333", "j111", "w222", "e444", true, 1).
  1277  						AddRow(createdAt, updatedAt, "111-222-333", "333-222-111", "r333", "j111", "w222", "e555", true, 1),
  1278  				)
  1279  			},
  1280  		},
  1281  		{
  1282  			fn: "QueryResourcesByExecutorIDs",
  1283  			inputs: []interface{}{
  1284  				engineModel.ExecutorID("e444"),
  1285  			},
  1286  			err: errors.ErrMetaOpFail.GenWithStackByArgs(),
  1287  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1288  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `resource_meta` WHERE executor_id in")).WithArgs("e444").WillReturnError(
  1289  					errors.New("QueryResourcesByExecutorIDs error"))
  1290  			},
  1291  		},
  1292  		{
  1293  			fn: "SetGCPendingByJobs",
  1294  			inputs: []interface{}{
  1295  				"job-1",
  1296  				"job-2",
  1297  				"job-3",
  1298  			},
  1299  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1300  				expectedSQL := "UPDATE `resource_meta` SET `gc_pending`=?,`updated_at`=? WHERE job_id in"
  1301  				mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).
  1302  					WithArgs(
  1303  						true,
  1304  						anyTime{},
  1305  						"job-1",
  1306  						"job-2",
  1307  						"job-3").
  1308  					WillReturnResult(driver.RowsAffected(1))
  1309  			},
  1310  		},
  1311  		{
  1312  			fn: "DeleteResourcesByTypeAndExecutorIDs",
  1313  			inputs: []interface{}{
  1314  				resModel.ResourceTypeLocalFile,
  1315  				engineModel.ExecutorID("executor-1"),
  1316  			},
  1317  			output: &ormResult{rowsAffected: 1},
  1318  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1319  				expectedSQL := "DELETE FROM `resource_meta` WHERE executor_id = ? and id like"
  1320  				mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).
  1321  					WithArgs("executor-1", "/local%").
  1322  					WillReturnResult(driver.RowsAffected(1))
  1323  			},
  1324  		},
  1325  		{
  1326  			fn: "DeleteResourcesByTypeAndExecutorIDs",
  1327  			inputs: []interface{}{
  1328  				resModel.ResourceTypeLocalFile,
  1329  				engineModel.ExecutorID("executor-1"),
  1330  				engineModel.ExecutorID("executor-2"),
  1331  			},
  1332  			output: &ormResult{rowsAffected: 2},
  1333  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1334  				expectedSQL := "DELETE FROM `resource_meta` WHERE executor_id in (?,?) and id like"
  1335  				mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).
  1336  					WithArgs("executor-1", "executor-2", "/local%").
  1337  					WillReturnResult(driver.RowsAffected(2))
  1338  			},
  1339  		},
  1340  		{
  1341  			fn: "GetOneResourceForGC",
  1342  			output: &resModel.ResourceMeta{
  1343  				Model: model.Model{
  1344  					SeqID:     1,
  1345  					CreatedAt: createdAt,
  1346  					UpdatedAt: updatedAt,
  1347  				},
  1348  				ID:        "resource-1",
  1349  				Job:       "job-1",
  1350  				Worker:    "worker-1",
  1351  				Executor:  "executor-1",
  1352  				GCPending: true,
  1353  			},
  1354  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1355  				expectedSQL := "SELECT * FROM `resource_meta` WHERE gc_pending = true ORDER BY"
  1356  				mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).
  1357  					WillReturnRows(
  1358  						sqlmock.NewRows([]string{
  1359  							"created_at", "updated_at", "project_id", "id", "job_id",
  1360  							"worker_id", "executor_id", "deleted", "gc_pending", "seq_id",
  1361  						}).AddRow(createdAt, updatedAt, "", "resource-1", "job-1", "worker-1", "executor-1", false, true, 1))
  1362  			},
  1363  		},
  1364  	}
  1365  
  1366  	for _, tc := range testCases {
  1367  		fmt.Println("test case", tc.fn)
  1368  		testInner(t, mock, cli, tc)
  1369  	}
  1370  }
  1371  
  1372  func TestError(t *testing.T) {
  1373  	t.Parallel()
  1374  
  1375  	sqlDB, mock := mockGetDBConn(t)
  1376  	defer sqlDB.Close()
  1377  	defer mock.ExpectClose()
  1378  	cli, err := newClient(sqlDB, defaultTestStoreType)
  1379  	require.Nil(t, err)
  1380  	require.NotNil(t, cli)
  1381  
  1382  	mock.ExpectQuery("SELECT [*] FROM `project_infos`").WillReturnRows(sqlmock.NewRows([]string{
  1383  		"created_at", "updated_at", "id", "name",
  1384  		"seq_id",
  1385  	}))
  1386  	res, err := cli.QueryProjects(context.TODO())
  1387  	require.Nil(t, err)
  1388  	require.Len(t, res, 0)
  1389  
  1390  	mock.ExpectQuery("SELECT [*] FROM `project_infos` WHERE id").WithArgs("p111").WillReturnRows(
  1391  		sqlmock.NewRows([]string{
  1392  			"created_at", "updated_at", "id", "name",
  1393  			"seq_id",
  1394  		}))
  1395  	res2, err := cli.GetProjectByID(context.TODO(), "p111")
  1396  	require.Nil(t, res2)
  1397  	require.Error(t, err)
  1398  	require.True(t, errors.Is(err, errors.ErrMetaEntryNotFound))
  1399  }
  1400  
  1401  func TestContext(t *testing.T) {
  1402  	t.Parallel()
  1403  
  1404  	ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
  1405  	defer cancel()
  1406  
  1407  	db, mock := mockGetDBConn(t)
  1408  	defer db.Close()
  1409  	defer mock.ExpectClose()
  1410  
  1411  	conn := metaMock.NewClientConnWithDB(db)
  1412  	require.NotNil(t, conn)
  1413  	defer conn.Close()
  1414  
  1415  	// test normal function
  1416  	err := failpoint.Enable("github.com/pingcap/tiflow/engine/pkg/orm/initializedDelay", "sleep(2000)")
  1417  	require.NoError(t, err)
  1418  	ctx = failpoint.WithHook(ctx, func(ctx context.Context, fpname string) bool {
  1419  		return ctx.Value(fpname) != nil
  1420  	})
  1421  	ctx2 := context.WithValue(ctx, "github.com/pingcap/tiflow/engine/pkg/orm/initializedDelay", struct{}{})
  1422  
  1423  	// NEED enable failpoint here, or you will meet sql mock NOT MATCH error
  1424  	err = InitAllFrameworkModels(ctx2, conn)
  1425  	require.Error(t, err)
  1426  	require.Regexp(t, "context deadline exceed", err.Error())
  1427  	failpoint.Disable("github.com/pingcap/tiflow/engine/pkg/orm/initializedDelay")
  1428  }
  1429  
  1430  func TestJobOp(t *testing.T) {
  1431  	t.Parallel()
  1432  
  1433  	sqlDB, mock := mockGetDBConn(t)
  1434  	defer sqlDB.Close()
  1435  	defer mock.ExpectClose()
  1436  	cli, err := newClient(sqlDB, defaultTestStoreType)
  1437  	require.Nil(t, err)
  1438  	require.NotNil(t, cli)
  1439  
  1440  	tm := time.Now()
  1441  	createdAt := tm.Add(time.Duration(1))
  1442  	updatedAt := tm.Add(time.Duration(1))
  1443  
  1444  	testCases := []tCase{
  1445  		// SetJobCanceling successfully
  1446  		{
  1447  			fn: "SetJobCanceling",
  1448  			inputs: []interface{}{
  1449  				"job-111",
  1450  			},
  1451  			output: &ormResult{
  1452  				rowsAffected: 1,
  1453  			},
  1454  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1455  				mock.ExpectBegin()
  1456  				mock.ExpectQuery(regexp.QuoteMeta(
  1457  					"SELECT count(*) FROM `job_ops` WHERE job_id = ?")).
  1458  					WithArgs("job-111").WillReturnRows(
  1459  					sqlmock.NewRows([]string{
  1460  						"count(0)",
  1461  					}).AddRow(0))
  1462  				mock.ExpectExec(regexp.QuoteMeta(
  1463  					"INSERT INTO `job_ops` (`created_at`,`updated_at`,`op`,`job_id`")).
  1464  					WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), model.JobOpStatusCanceling, "job-111").
  1465  					WillReturnResult(sqlmock.NewResult(1, 1))
  1466  				mock.ExpectCommit()
  1467  			},
  1468  		},
  1469  		// SetJobCanceling does nothing because cancelling op exists
  1470  		{
  1471  			fn: "SetJobCanceling",
  1472  			inputs: []interface{}{
  1473  				"job-111",
  1474  			},
  1475  			output: &ormResult{
  1476  				rowsAffected: 0,
  1477  			},
  1478  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1479  				mock.ExpectBegin()
  1480  				mock.ExpectQuery(regexp.QuoteMeta(
  1481  					"SELECT count(*) FROM `job_ops` WHERE job_id = ?")).
  1482  					WithArgs("job-111").WillReturnRows(
  1483  					sqlmock.NewRows([]string{
  1484  						"count(1)",
  1485  					}).AddRow(1))
  1486  				mock.ExpectQuery(
  1487  					"SELECT [*] FROM `job_ops` WHERE job_id = ?").
  1488  					WithArgs("job-111").WillReturnRows(
  1489  					sqlmock.NewRows([]string{
  1490  						"created_at", "updated_at", "op", "job_id", "seq_id",
  1491  					}).AddRow(createdAt, updatedAt, model.JobOpStatusCanceling, "job-111", 1))
  1492  				mock.ExpectCommit()
  1493  			},
  1494  		},
  1495  		// SetJobCanceling returns error if job is already cancelled
  1496  		{
  1497  			fn: "SetJobCanceling",
  1498  			inputs: []interface{}{
  1499  				"job-111",
  1500  			},
  1501  			output: &ormResult{
  1502  				rowsAffected: 0,
  1503  			},
  1504  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1505  				mock.ExpectBegin()
  1506  				mock.ExpectQuery(regexp.QuoteMeta(
  1507  					"SELECT count(*) FROM `job_ops` WHERE job_id = ?")).
  1508  					WithArgs("job-111").WillReturnRows(
  1509  					sqlmock.NewRows([]string{
  1510  						"count(1)",
  1511  					}).AddRow(1))
  1512  				mock.ExpectQuery(
  1513  					"SELECT [*] FROM `job_ops` WHERE job_id = ?").
  1514  					WithArgs("job-111").WillReturnRows(
  1515  					sqlmock.NewRows([]string{
  1516  						"created_at", "updated_at", "op", "job_id", "seq_id",
  1517  					}).AddRow(createdAt, updatedAt, model.JobOpStatusCanceled, "job-111", 1))
  1518  				mock.ExpectRollback()
  1519  			},
  1520  			err: errors.ErrJobAlreadyCanceled.GenWithStackByArgs("job-111"),
  1521  		},
  1522  		// SetJobCanceling updates job operation to canceling if exists a noop job operation
  1523  		{
  1524  			fn: "SetJobCanceling",
  1525  			inputs: []interface{}{
  1526  				"job-111",
  1527  			},
  1528  			output: &ormResult{
  1529  				rowsAffected: 2,
  1530  			},
  1531  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1532  				mock.ExpectBegin()
  1533  				mock.ExpectQuery(regexp.QuoteMeta(
  1534  					"SELECT count(*) FROM `job_ops` WHERE job_id = ?")).
  1535  					WithArgs("job-111").WillReturnRows(
  1536  					sqlmock.NewRows([]string{
  1537  						"count(1)",
  1538  					}).AddRow(1))
  1539  				mock.ExpectQuery(
  1540  					"SELECT [*] FROM `job_ops` WHERE job_id = ?").
  1541  					WithArgs("job-111").WillReturnRows(
  1542  					sqlmock.NewRows([]string{
  1543  						"created_at", "updated_at", "op", "job_id", "seq_id",
  1544  					}).AddRow(createdAt, updatedAt, model.JobOpStatusNoop, "job-111", 1))
  1545  				mock.ExpectExec("ON DUPLICATE KEY UPDATE").WillReturnResult(sqlmock.NewResult(1, 2))
  1546  				mock.ExpectCommit()
  1547  			},
  1548  		},
  1549  		// SetJobCanceled
  1550  		{
  1551  			fn: "SetJobCanceled",
  1552  			inputs: []interface{}{
  1553  				"job-111",
  1554  			},
  1555  			output: &ormResult{
  1556  				rowsAffected: 1,
  1557  			},
  1558  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1559  				expectedSQL := "UPDATE `job_ops` SET `op`=?,`updated_at`=? WHERE job_id = ? AND op = ?"
  1560  				mock.ExpectExec(regexp.QuoteMeta(expectedSQL)).
  1561  					WithArgs(model.JobOpStatusCanceled, anyTime{}, "job-111", model.JobOpStatusCanceling).
  1562  					WillReturnResult(sqlmock.NewResult(0, 1))
  1563  			},
  1564  		},
  1565  		// QueryJobOp
  1566  		{
  1567  			fn: "QueryJobOp",
  1568  			inputs: []interface{}{
  1569  				"job-1",
  1570  			},
  1571  			output: &model.JobOp{
  1572  				Model: model.Model{
  1573  					SeqID:     1,
  1574  					CreatedAt: createdAt,
  1575  					UpdatedAt: updatedAt,
  1576  				},
  1577  				JobID: "job-1",
  1578  				Op:    model.JobOpStatusCanceling,
  1579  			},
  1580  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1581  				expectedSQL := "SELECT * FROM `job_ops` WHERE job_id = ?"
  1582  				mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).
  1583  					WillReturnRows(
  1584  						sqlmock.NewRows([]string{"created_at", "updated_at", "op", "job_id", "seq_id"}).
  1585  							AddRow(createdAt, updatedAt, model.JobOpStatusCanceling, "job-1", 1))
  1586  			},
  1587  		},
  1588  		// QueryJobOpsByStatus
  1589  		{
  1590  			fn: "QueryJobOpsByStatus",
  1591  			inputs: []interface{}{
  1592  				model.JobOpStatusCanceling,
  1593  			},
  1594  			output: []*model.JobOp{
  1595  				{
  1596  					Model: model.Model{
  1597  						SeqID:     1,
  1598  						CreatedAt: createdAt,
  1599  						UpdatedAt: updatedAt,
  1600  					},
  1601  					JobID: "job-1",
  1602  					Op:    model.JobOpStatusCanceling,
  1603  				},
  1604  			},
  1605  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1606  				expectedSQL := "SELECT * FROM `job_ops` WHERE op = ?"
  1607  				mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).
  1608  					WithArgs(model.JobOpStatusCanceling).
  1609  					WillReturnRows(
  1610  						sqlmock.NewRows([]string{"created_at", "updated_at", "op", "job_id", "seq_id"}).
  1611  							AddRow(createdAt, updatedAt, model.JobOpStatusCanceling, "job-1", 1))
  1612  			},
  1613  		},
  1614  	}
  1615  
  1616  	for _, tc := range testCases {
  1617  		testInner(t, mock, cli, tc)
  1618  	}
  1619  }
  1620  
  1621  func TestExecutorClient(t *testing.T) {
  1622  	t.Parallel()
  1623  
  1624  	sqlDB, mock := mockGetDBConn(t)
  1625  	defer sqlDB.Close()
  1626  	defer mock.ExpectClose()
  1627  	cli, err := newClient(sqlDB, defaultTestStoreType)
  1628  	require.Nil(t, err)
  1629  	require.NotNil(t, cli)
  1630  
  1631  	tm := time.Now()
  1632  	createdAt := tm.Add(time.Duration(1))
  1633  	updatedAt := tm.Add(time.Duration(1))
  1634  
  1635  	executor := &model.Executor{
  1636  		Model: model.Model{
  1637  			CreatedAt: createdAt,
  1638  			UpdatedAt: updatedAt,
  1639  		},
  1640  		ID:      "executor-0-1234",
  1641  		Name:    "executor-0",
  1642  		Address: "127.0.0.1:1234",
  1643  		Labels: map[label.Key]label.Value{
  1644  			"key1": "val1",
  1645  			"key2": "val2",
  1646  		},
  1647  	}
  1648  
  1649  	testCases := []tCase{
  1650  		{
  1651  			fn: "CreateExecutor",
  1652  			inputs: []interface{}{
  1653  				executor,
  1654  			},
  1655  			output: &ormResult{
  1656  				rowsAffected: 1,
  1657  			},
  1658  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1659  				mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `executors` (`created_at`,`updated_at`,`id`,`name`,`address`,`labels`) VALUES (?,?,?,?,?,?)")).
  1660  					WithArgs(createdAt, updatedAt, executor.ID, executor.Name, executor.Address, "{\"key1\":\"val1\",\"key2\":\"val2\"}").
  1661  					WillReturnResult(sqlmock.NewResult(1, 1))
  1662  			},
  1663  		},
  1664  		{
  1665  			fn: "UpdateExecutor",
  1666  			inputs: []interface{}{
  1667  				executor,
  1668  			},
  1669  			output: &ormResult{
  1670  				rowsAffected: 1,
  1671  			},
  1672  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1673  				mock.ExpectExec(regexp.QuoteMeta("UPDATE `executors` SET `address`=?,`id`=?,`labels`=?,`name`=?,`updated_at`=? WHERE id = ?")).
  1674  					WithArgs(executor.Address, executor.ID, "{\"key1\":\"val1\",\"key2\":\"val2\"}", executor.Name, sqlmock.AnyArg(), executor.ID).
  1675  					WillReturnResult(sqlmock.NewResult(0, 1))
  1676  			},
  1677  		},
  1678  		{
  1679  			fn: "DeleteExecutor",
  1680  			inputs: []interface{}{
  1681  				executor.ID,
  1682  			},
  1683  			output: &ormResult{
  1684  				rowsAffected: 1,
  1685  			},
  1686  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1687  				mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `executors` WHERE id = ?")).
  1688  					WithArgs(executor.ID).WillReturnResult(sqlmock.NewResult(0, 1))
  1689  			},
  1690  		},
  1691  		{
  1692  			fn:     "QueryExecutors",
  1693  			inputs: []interface{}{},
  1694  			output: []*model.Executor{executor},
  1695  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
  1696  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `executors`")).
  1697  					WillReturnRows(sqlmock.NewRows([]string{
  1698  						"seq_id", "created_at", "updated_at", "id", "name", "address", "labels",
  1699  					}).AddRow(1, createdAt, updatedAt, executor.ID, executor.Name,
  1700  						executor.Address, "{\"key1\":\"val1\",\"key2\":\"val2\"}"))
  1701  			},
  1702  		},
  1703  	}
  1704  
  1705  	for _, tc := range testCases {
  1706  		testInner(t, mock, cli, tc)
  1707  	}
  1708  }
  1709  
  1710  func testInner(t *testing.T, m sqlmock.Sqlmock, cli Client, c tCase) {
  1711  	// set the mock expectation
  1712  	c.mockExpectResFn(m)
  1713  
  1714  	var args []reflect.Value
  1715  	args = append(args, reflect.ValueOf(context.Background()))
  1716  	for _, ip := range c.inputs {
  1717  		args = append(args, reflect.ValueOf(ip))
  1718  	}
  1719  	result := reflect.ValueOf(cli).MethodByName(c.fn).Call(args)
  1720  	// only error
  1721  	if len(result) == 1 {
  1722  		if c.err == nil {
  1723  			require.Nil(t, result[0].Interface())
  1724  		} else {
  1725  			require.NotNil(t, result[0].Interface())
  1726  			require.Error(t, result[0].Interface().(error))
  1727  		}
  1728  	} else if len(result) == 2 {
  1729  		// result and error
  1730  		if c.err != nil {
  1731  			require.NotNil(t, result[1].Interface())
  1732  			require.Error(t, result[1].Interface().(error))
  1733  		} else {
  1734  			require.Equal(t, c.output, result[0].Interface())
  1735  		}
  1736  	}
  1737  }