github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/aria2/monitor/monitor_test.go (about) 1 package monitor 2 3 import ( 4 "database/sql" 5 "errors" 6 "testing" 7 8 "github.com/DATA-DOG/go-sqlmock" 9 model "github.com/cloudreve/Cloudreve/v3/models" 10 "github.com/cloudreve/Cloudreve/v3/pkg/aria2/common" 11 "github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc" 12 "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" 13 "github.com/cloudreve/Cloudreve/v3/pkg/mocks" 14 "github.com/cloudreve/Cloudreve/v3/pkg/mq" 15 "github.com/jinzhu/gorm" 16 "github.com/stretchr/testify/assert" 17 testMock "github.com/stretchr/testify/mock" 18 ) 19 20 var mock sqlmock.Sqlmock 21 22 // TestMain 初始化数据库Mock 23 func TestMain(m *testing.M) { 24 var db *sql.DB 25 var err error 26 db, mock, err = sqlmock.New() 27 if err != nil { 28 panic("An error was not expected when opening a stub database connection") 29 } 30 model.DB, _ = gorm.Open("mysql", db) 31 defer db.Close() 32 m.Run() 33 } 34 35 func TestNewMonitor(t *testing.T) { 36 a := assert.New(t) 37 mockMQ := mq.NewMQ() 38 39 // node not available 40 { 41 mockPool := &mocks.NodePoolMock{} 42 mockPool.On("GetNodeByID", uint(1)).Return(nil) 43 mock.ExpectBegin() 44 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 45 mock.ExpectCommit() 46 47 task := &model.Download{ 48 Model: gorm.Model{ID: 1}, 49 } 50 NewMonitor(task, mockPool, mockMQ) 51 mockPool.AssertExpectations(t) 52 a.NoError(mock.ExpectationsWereMet()) 53 a.NotEmpty(task.Error) 54 } 55 56 // success 57 { 58 mockNode := &mocks.NodeMock{} 59 mockNode.On("GetAria2Instance").Return(&common.DummyAria2{}) 60 mockPool := &mocks.NodePoolMock{} 61 mockPool.On("GetNodeByID", uint(1)).Return(mockNode) 62 63 task := &model.Download{ 64 Model: gorm.Model{ID: 1}, 65 } 66 NewMonitor(task, mockPool, mockMQ) 67 mockNode.AssertExpectations(t) 68 mockPool.AssertExpectations(t) 69 } 70 71 } 72 73 func TestMonitor_Loop(t *testing.T) { 74 a := assert.New(t) 75 mockMQ := mq.NewMQ() 76 mockNode := &mocks.NodeMock{} 77 mockNode.On("GetAria2Instance").Return(&common.DummyAria2{}) 78 m := &Monitor{ 79 retried: MAX_RETRY, 80 node: mockNode, 81 Task: &model.Download{Model: gorm.Model{ID: 1}}, 82 notifier: mockMQ.Subscribe("test", 1), 83 } 84 85 // into interval loop 86 { 87 mock.ExpectBegin() 88 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 89 mock.ExpectCommit() 90 m.Loop(mockMQ) 91 a.NoError(mock.ExpectationsWereMet()) 92 a.NotEmpty(m.Task.Error) 93 } 94 95 // into notifier loop 96 { 97 m.Task.Error = "" 98 mockMQ.Publish("test", mq.Message{}) 99 mock.ExpectBegin() 100 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 101 mock.ExpectCommit() 102 m.Loop(mockMQ) 103 a.NoError(mock.ExpectationsWereMet()) 104 a.NotEmpty(m.Task.Error) 105 } 106 } 107 108 func TestMonitor_UpdateFailedAfterRetry(t *testing.T) { 109 a := assert.New(t) 110 mockNode := &mocks.NodeMock{} 111 mockNode.On("GetAria2Instance").Return(&common.DummyAria2{}) 112 m := &Monitor{ 113 node: mockNode, 114 Task: &model.Download{Model: gorm.Model{ID: 1}}, 115 } 116 mock.ExpectBegin() 117 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 118 mock.ExpectCommit() 119 120 for i := 0; i < MAX_RETRY; i++ { 121 a.False(m.Update()) 122 } 123 124 mockNode.AssertExpectations(t) 125 a.True(m.Update()) 126 a.NoError(mock.ExpectationsWereMet()) 127 a.NotEmpty(m.Task.Error) 128 } 129 130 func TestMonitor_UpdateMagentoFollow(t *testing.T) { 131 a := assert.New(t) 132 mockAria2 := &mocks.Aria2Mock{} 133 mockAria2.On("Status", testMock.Anything).Return(rpc.StatusInfo{ 134 FollowedBy: []string{"next"}, 135 }, nil) 136 mockNode := &mocks.NodeMock{} 137 mockNode.On("GetAria2Instance").Return(mockAria2) 138 m := &Monitor{ 139 node: mockNode, 140 Task: &model.Download{Model: gorm.Model{ID: 1}}, 141 } 142 mock.ExpectBegin() 143 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 144 mock.ExpectCommit() 145 146 a.False(m.Update()) 147 a.NoError(mock.ExpectationsWereMet()) 148 a.Equal("next", m.Task.GID) 149 mockAria2.AssertExpectations(t) 150 } 151 152 func TestMonitor_UpdateFailedToUpdateInfo(t *testing.T) { 153 a := assert.New(t) 154 mockAria2 := &mocks.Aria2Mock{} 155 mockAria2.On("Status", testMock.Anything).Return(rpc.StatusInfo{}, nil) 156 mockAria2.On("DeleteTempFile", testMock.Anything).Return(nil) 157 mockNode := &mocks.NodeMock{} 158 mockNode.On("GetAria2Instance").Return(mockAria2) 159 m := &Monitor{ 160 node: mockNode, 161 Task: &model.Download{Model: gorm.Model{ID: 1}}, 162 } 163 mock.ExpectBegin() 164 mock.ExpectExec("UPDATE(.+)").WillReturnError(errors.New("error")) 165 mock.ExpectRollback() 166 mock.ExpectBegin() 167 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 168 mock.ExpectCommit() 169 170 a.True(m.Update()) 171 a.NoError(mock.ExpectationsWereMet()) 172 mockAria2.AssertExpectations(t) 173 mockNode.AssertExpectations(t) 174 a.NotEmpty(m.Task.Error) 175 } 176 177 func TestMonitor_UpdateCompleted(t *testing.T) { 178 a := assert.New(t) 179 mockAria2 := &mocks.Aria2Mock{} 180 mockAria2.On("Status", testMock.Anything).Return(rpc.StatusInfo{ 181 Status: "complete", 182 }, nil) 183 mockAria2.On("DeleteTempFile", testMock.Anything).Return(nil) 184 mockNode := &mocks.NodeMock{} 185 mockNode.On("GetAria2Instance").Return(mockAria2) 186 mockNode.On("ID").Return(uint(1)) 187 m := &Monitor{ 188 node: mockNode, 189 Task: &model.Download{Model: gorm.Model{ID: 1}}, 190 } 191 mock.ExpectBegin() 192 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 193 mock.ExpectCommit() 194 mock.ExpectQuery("SELECT(.+)users(.+)").WillReturnError(errors.New("error")) 195 mock.ExpectBegin() 196 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 197 mock.ExpectCommit() 198 199 a.True(m.Update()) 200 a.NoError(mock.ExpectationsWereMet()) 201 mockAria2.AssertExpectations(t) 202 mockNode.AssertExpectations(t) 203 a.NotEmpty(m.Task.Error) 204 } 205 206 func TestMonitor_UpdateError(t *testing.T) { 207 a := assert.New(t) 208 mockAria2 := &mocks.Aria2Mock{} 209 mockAria2.On("Status", testMock.Anything).Return(rpc.StatusInfo{ 210 Status: "error", 211 ErrorMessage: "error", 212 }, nil) 213 mockAria2.On("DeleteTempFile", testMock.Anything).Return(nil) 214 mockNode := &mocks.NodeMock{} 215 mockNode.On("GetAria2Instance").Return(mockAria2) 216 m := &Monitor{ 217 node: mockNode, 218 Task: &model.Download{Model: gorm.Model{ID: 1}}, 219 } 220 mock.ExpectBegin() 221 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 222 mock.ExpectCommit() 223 mock.ExpectBegin() 224 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 225 mock.ExpectCommit() 226 227 a.True(m.Update()) 228 a.NoError(mock.ExpectationsWereMet()) 229 mockAria2.AssertExpectations(t) 230 mockNode.AssertExpectations(t) 231 a.NotEmpty(m.Task.Error) 232 } 233 234 func TestMonitor_UpdateActive(t *testing.T) { 235 a := assert.New(t) 236 mockAria2 := &mocks.Aria2Mock{} 237 mockAria2.On("Status", testMock.Anything).Return(rpc.StatusInfo{ 238 Status: "active", 239 }, nil) 240 mockNode := &mocks.NodeMock{} 241 mockNode.On("GetAria2Instance").Return(mockAria2) 242 m := &Monitor{ 243 node: mockNode, 244 Task: &model.Download{Model: gorm.Model{ID: 1}}, 245 } 246 mock.ExpectBegin() 247 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 248 mock.ExpectCommit() 249 250 a.False(m.Update()) 251 a.NoError(mock.ExpectationsWereMet()) 252 mockAria2.AssertExpectations(t) 253 mockNode.AssertExpectations(t) 254 } 255 256 func TestMonitor_UpdateRemoved(t *testing.T) { 257 a := assert.New(t) 258 mockAria2 := &mocks.Aria2Mock{} 259 mockAria2.On("Status", testMock.Anything).Return(rpc.StatusInfo{ 260 Status: "removed", 261 }, nil) 262 mockAria2.On("DeleteTempFile", testMock.Anything).Return(nil) 263 mockNode := &mocks.NodeMock{} 264 mockNode.On("GetAria2Instance").Return(mockAria2) 265 m := &Monitor{ 266 node: mockNode, 267 Task: &model.Download{Model: gorm.Model{ID: 1}}, 268 } 269 mock.ExpectBegin() 270 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 271 mock.ExpectCommit() 272 273 mock.ExpectBegin() 274 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 275 mock.ExpectCommit() 276 277 a.True(m.Update()) 278 a.Equal(common.Canceled, m.Task.Status) 279 a.NoError(mock.ExpectationsWereMet()) 280 mockAria2.AssertExpectations(t) 281 mockNode.AssertExpectations(t) 282 } 283 284 func TestMonitor_UpdateUnknown(t *testing.T) { 285 a := assert.New(t) 286 mockAria2 := &mocks.Aria2Mock{} 287 mockAria2.On("Status", testMock.Anything).Return(rpc.StatusInfo{ 288 Status: "unknown", 289 }, nil) 290 mockNode := &mocks.NodeMock{} 291 mockNode.On("GetAria2Instance").Return(mockAria2) 292 m := &Monitor{ 293 node: mockNode, 294 Task: &model.Download{Model: gorm.Model{ID: 1}}, 295 } 296 mock.ExpectBegin() 297 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 298 mock.ExpectCommit() 299 300 a.True(m.Update()) 301 a.NoError(mock.ExpectationsWereMet()) 302 mockAria2.AssertExpectations(t) 303 mockNode.AssertExpectations(t) 304 } 305 306 func TestMonitor_UpdateTaskInfoValidateFailed(t *testing.T) { 307 a := assert.New(t) 308 status := rpc.StatusInfo{ 309 Status: "completed", 310 TotalLength: "100", 311 CompletedLength: "50", 312 DownloadSpeed: "20", 313 } 314 mockNode := &mocks.NodeMock{} 315 mockNode.On("GetAria2Instance").Return(&common.DummyAria2{}) 316 m := &Monitor{ 317 node: mockNode, 318 Task: &model.Download{Model: gorm.Model{ID: 1}}, 319 } 320 321 mock.ExpectBegin() 322 mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1)) 323 mock.ExpectCommit() 324 325 err := m.UpdateTaskInfo(status) 326 a.Error(err) 327 a.NoError(mock.ExpectationsWereMet()) 328 mockNode.AssertExpectations(t) 329 } 330 331 func TestMonitor_ValidateFile(t *testing.T) { 332 a := assert.New(t) 333 m := &Monitor{ 334 Task: &model.Download{ 335 Model: gorm.Model{ID: 1}, 336 TotalSize: 100, 337 }, 338 } 339 340 // failed to create filesystem 341 { 342 m.Task.User = &model.User{ 343 Policy: model.Policy{ 344 Type: "random", 345 }, 346 } 347 a.Equal(filesystem.ErrUnknownPolicyType, m.ValidateFile()) 348 } 349 350 // User capacity not enough 351 { 352 m.Task.User = &model.User{ 353 Group: model.Group{ 354 MaxStorage: 99, 355 }, 356 Policy: model.Policy{ 357 Type: "local", 358 }, 359 } 360 a.Equal(filesystem.ErrInsufficientCapacity, m.ValidateFile()) 361 } 362 363 // single file too big 364 { 365 m.Task.StatusInfo.Files = []rpc.FileInfo{ 366 { 367 Length: "100", 368 Selected: "true", 369 }, 370 } 371 m.Task.User = &model.User{ 372 Group: model.Group{ 373 MaxStorage: 100, 374 }, 375 Policy: model.Policy{ 376 Type: "local", 377 MaxSize: 99, 378 }, 379 } 380 a.Equal(filesystem.ErrFileSizeTooBig, m.ValidateFile()) 381 } 382 383 // all pass 384 { 385 m.Task.StatusInfo.Files = []rpc.FileInfo{ 386 { 387 Length: "100", 388 Selected: "true", 389 }, 390 } 391 m.Task.User = &model.User{ 392 Group: model.Group{ 393 MaxStorage: 100, 394 }, 395 Policy: model.Policy{ 396 Type: "local", 397 MaxSize: 100, 398 }, 399 } 400 a.NoError(m.ValidateFile()) 401 } 402 } 403 404 func TestMonitor_Complete(t *testing.T) { 405 a := assert.New(t) 406 mockNode := &mocks.NodeMock{} 407 mockNode.On("ID").Return(uint(1)) 408 mockPool := &mocks.TaskPoolMock{} 409 mockPool.On("Submit", testMock.Anything) 410 m := &Monitor{ 411 node: mockNode, 412 Task: &model.Download{ 413 Model: gorm.Model{ID: 1}, 414 TotalSize: 100, 415 UserID: 9414, 416 }, 417 } 418 m.Task.StatusInfo.Files = []rpc.FileInfo{ 419 { 420 Length: "100", 421 Selected: "true", 422 }, 423 } 424 425 mock.ExpectQuery("SELECT(.+)users").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(9414)) 426 427 mock.ExpectBegin() 428 mock.ExpectExec("INSERT(.+)tasks").WillReturnResult(sqlmock.NewResult(1, 1)) 429 mock.ExpectCommit() 430 431 mock.ExpectBegin() 432 mock.ExpectExec("UPDATE(.+)downloads").WillReturnResult(sqlmock.NewResult(1, 1)) 433 mock.ExpectCommit() 434 435 mock.ExpectQuery("SELECT(.+)tasks").WillReturnRows(sqlmock.NewRows([]string{"id", "type", "status"}).AddRow(1, 2, 4)) 436 mock.ExpectQuery("SELECT(.+)users").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(9414)) 437 mock.ExpectBegin() 438 mock.ExpectExec("INSERT(.+)tasks").WillReturnResult(sqlmock.NewResult(2, 1)) 439 mock.ExpectCommit() 440 441 a.False(m.Complete(mockPool)) 442 m.Task.StatusInfo.Status = "complete" 443 a.True(m.Complete(mockPool)) 444 a.NoError(mock.ExpectationsWereMet()) 445 mockNode.AssertExpectations(t) 446 mockPool.AssertExpectations(t) 447 }