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  }