github.com/goravel/framework@v1.13.9/queue/application_test.go (about)

     1  package queue
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"log"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/ory/dockertest/v3"
    11  	"github.com/spf13/cast"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/suite"
    14  
    15  	configmock "github.com/goravel/framework/contracts/config/mocks"
    16  	logmock "github.com/goravel/framework/contracts/log/mocks"
    17  	"github.com/goravel/framework/contracts/queue"
    18  	"github.com/goravel/framework/support/carbon"
    19  	testingdocker "github.com/goravel/framework/support/docker"
    20  )
    21  
    22  var (
    23  	testSyncJob                = 0
    24  	testAsyncJob               = 0
    25  	testAsyncJobOfDisableDebug = 0
    26  	testDelayAsyncJob          = 0
    27  	testCustomAsyncJob         = 0
    28  	testErrorAsyncJob          = 0
    29  	testChainAsyncJob          = 0
    30  	testChainSyncJob           = 0
    31  	testChainAsyncJobError     = 0
    32  	testChainSyncJobError      = 0
    33  )
    34  
    35  type QueueTestSuite struct {
    36  	suite.Suite
    37  	app           *Application
    38  	redisResource *dockertest.Resource
    39  	mockConfig    *configmock.Config
    40  	mockLog       *logmock.Log
    41  }
    42  
    43  func TestQueueTestSuite(t *testing.T) {
    44  	if testing.Short() {
    45  		t.Skip("Skipping tests of using docker")
    46  	}
    47  
    48  	redisPool, redisResource, err := testingdocker.Redis()
    49  	if err != nil {
    50  		log.Fatalf("Get redis error: %s", err)
    51  	}
    52  
    53  	suite.Run(t, &QueueTestSuite{
    54  		redisResource: redisResource,
    55  	})
    56  
    57  	if err := redisPool.Purge(redisResource); err != nil {
    58  		log.Fatalf("Could not purge resource: %s", err)
    59  	}
    60  }
    61  
    62  func (s *QueueTestSuite) SetupTest() {
    63  	s.mockConfig = &configmock.Config{}
    64  	s.mockLog = &logmock.Log{}
    65  	s.app = NewApplication(s.mockConfig, s.mockLog)
    66  }
    67  
    68  func (s *QueueTestSuite) TestSyncQueue() {
    69  	s.mockConfig.On("GetString", "queue.default").Return("redis").Once()
    70  	s.Nil(s.app.Job(&TestSyncJob{}, []queue.Arg{
    71  		{Type: "string", Value: "TestSyncQueue"},
    72  		{Type: "int", Value: 1},
    73  	}).DispatchSync())
    74  	s.Equal(1, testSyncJob)
    75  }
    76  
    77  func (s *QueueTestSuite) TestDefaultAsyncQueue_EnableDebug() {
    78  	s.mockConfig.On("GetString", "queue.default").Return("redis").Twice()
    79  	s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4)
    80  	s.mockConfig.On("GetBool", "app.debug").Return(true).Times(2)
    81  	s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(2)
    82  	s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3)
    83  	s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice()
    84  	s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice()
    85  	s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice()
    86  	s.mockConfig.On("GetInt", "database.redis.default.port").Return(cast.ToInt(s.redisResource.GetPort("6379/tcp"))).Twice()
    87  	s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice()
    88  	s.mockLog.On("Infof", "Launching a worker with the following settings:").Once()
    89  	s.mockLog.On("Infof", "- Broker: %s", "://").Once()
    90  	s.mockLog.On("Infof", "- DefaultQueue: %s", "goravel_queues:debug").Once()
    91  	s.mockLog.On("Infof", "- ResultBackend: %s", "://").Once()
    92  	s.mockLog.On("Info", "[*] Waiting for messages. To exit press CTRL+C").Once()
    93  	s.mockLog.On("Debugf", "Received new message: %s", mock.Anything).Once()
    94  	s.mockLog.On("Debugf", "Processed task %s. Results = %s", mock.Anything, mock.Anything).Once()
    95  	s.app.jobs = []queue.Job{&TestAsyncJob{}}
    96  
    97  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    98  	defer cancel()
    99  	go func(ctx context.Context) {
   100  		s.Nil(s.app.Worker(&queue.Args{
   101  			Queue: "debug",
   102  		}).Run())
   103  
   104  		for range ctx.Done() {
   105  			return
   106  		}
   107  	}(ctx)
   108  	time.Sleep(2 * time.Second)
   109  	s.Nil(s.app.Job(&TestAsyncJob{}, []queue.Arg{
   110  		{Type: "string", Value: "TestDefaultAsyncQueue_EnableDebug"},
   111  		{Type: "int", Value: 1},
   112  	}).OnQueue("debug").Dispatch())
   113  	time.Sleep(2 * time.Second)
   114  	s.Equal(1, testAsyncJob)
   115  
   116  	s.mockConfig.AssertExpectations(s.T())
   117  	s.mockLog.AssertExpectations(s.T())
   118  }
   119  
   120  func (s *QueueTestSuite) TestDefaultAsyncQueue_DisableDebug() {
   121  	s.mockConfig.On("GetString", "queue.default").Return("redis").Twice()
   122  	s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3)
   123  	s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2)
   124  	s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(3)
   125  	s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3)
   126  	s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice()
   127  	s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice()
   128  	s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice()
   129  	s.mockConfig.On("GetInt", "database.redis.default.port").Return(cast.ToInt(s.redisResource.GetPort("6379/tcp"))).Twice()
   130  	s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice()
   131  	s.app.jobs = []queue.Job{&TestAsyncJobOfDisableDebug{}}
   132  
   133  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   134  	defer cancel()
   135  	go func(ctx context.Context) {
   136  		s.Nil(s.app.Worker(nil).Run())
   137  
   138  		for range ctx.Done() {
   139  			return
   140  		}
   141  	}(ctx)
   142  	time.Sleep(2 * time.Second)
   143  	s.Nil(s.app.Job(&TestAsyncJobOfDisableDebug{}, []queue.Arg{
   144  		{Type: "string", Value: "TestDefaultAsyncQueue_DisableDebug"},
   145  		{Type: "int", Value: 1},
   146  	}).Dispatch())
   147  	time.Sleep(2 * time.Second)
   148  	s.Equal(1, testAsyncJobOfDisableDebug)
   149  
   150  	s.mockConfig.AssertExpectations(s.T())
   151  	s.mockLog.AssertExpectations(s.T())
   152  }
   153  
   154  func (s *QueueTestSuite) TestDelayAsyncQueue() {
   155  	s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2)
   156  	s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4)
   157  	s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2)
   158  	s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice()
   159  	s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3)
   160  	s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice()
   161  	s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice()
   162  	s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice()
   163  	s.mockConfig.On("GetInt", "database.redis.default.port").Return(cast.ToInt(s.redisResource.GetPort("6379/tcp"))).Twice()
   164  	s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice()
   165  	s.app.jobs = []queue.Job{&TestDelayAsyncJob{}}
   166  
   167  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   168  	defer cancel()
   169  	go func(ctx context.Context) {
   170  		s.Nil(s.app.Worker(&queue.Args{
   171  			Queue: "delay",
   172  		}).Run())
   173  
   174  		for range ctx.Done() {
   175  			return
   176  		}
   177  	}(ctx)
   178  	time.Sleep(2 * time.Second)
   179  	s.Nil(s.app.Job(&TestDelayAsyncJob{}, []queue.Arg{
   180  		{Type: "string", Value: "TestDelayAsyncQueue"},
   181  		{Type: "int", Value: 1},
   182  	}).OnQueue("delay").Delay(carbon.Now().AddSeconds(3).ToStdTime()).Dispatch())
   183  	time.Sleep(2 * time.Second)
   184  	s.Equal(0, testDelayAsyncJob)
   185  	time.Sleep(3 * time.Second)
   186  	s.Equal(1, testDelayAsyncJob)
   187  
   188  	s.mockConfig.AssertExpectations(s.T())
   189  }
   190  
   191  func (s *QueueTestSuite) TestCustomAsyncQueue() {
   192  	s.mockConfig.On("GetString", "queue.default").Return("redis").Twice()
   193  	s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4)
   194  	s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2)
   195  	s.mockConfig.On("GetString", "queue.connections.custom.queue", "default").Return("default").Twice()
   196  	s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("redis").Times(3)
   197  	s.mockConfig.On("GetString", "queue.connections.custom.connection").Return("default").Twice()
   198  	s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice()
   199  	s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice()
   200  	s.mockConfig.On("GetInt", "database.redis.default.port").Return(cast.ToInt(s.redisResource.GetPort("6379/tcp"))).Twice()
   201  	s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice()
   202  	s.app.jobs = []queue.Job{&TestCustomAsyncJob{}}
   203  
   204  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   205  	defer cancel()
   206  	go func(ctx context.Context) {
   207  		s.Nil(s.app.Worker(&queue.Args{
   208  			Connection: "custom",
   209  			Queue:      "custom1",
   210  			Concurrent: 2,
   211  		}).Run())
   212  
   213  		for range ctx.Done() {
   214  			return
   215  		}
   216  	}(ctx)
   217  	time.Sleep(2 * time.Second)
   218  	s.Nil(s.app.Job(&TestCustomAsyncJob{}, []queue.Arg{
   219  		{Type: "string", Value: "TestCustomAsyncQueue"},
   220  		{Type: "int", Value: 1},
   221  	}).OnConnection("custom").OnQueue("custom1").Dispatch())
   222  	time.Sleep(2 * time.Second)
   223  	s.Equal(1, testCustomAsyncJob)
   224  
   225  	s.mockConfig.AssertExpectations(s.T())
   226  }
   227  
   228  func (s *QueueTestSuite) TestErrorAsyncQueue() {
   229  	s.mockConfig.On("GetString", "queue.default").Return("redis").Twice()
   230  	s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4)
   231  	s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2)
   232  	s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice()
   233  	s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3)
   234  	s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice()
   235  	s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice()
   236  	s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice()
   237  	s.mockConfig.On("GetInt", "database.redis.default.port").Return(cast.ToInt(s.redisResource.GetPort("6379/tcp"))).Twice()
   238  	s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice()
   239  	s.app.jobs = []queue.Job{&TestErrorAsyncJob{}}
   240  
   241  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   242  	defer cancel()
   243  	go func(ctx context.Context) {
   244  		s.Nil(s.app.Worker(&queue.Args{
   245  			Queue: "error",
   246  		}).Run())
   247  
   248  		for range ctx.Done() {
   249  			return
   250  		}
   251  	}(ctx)
   252  	time.Sleep(2 * time.Second)
   253  	s.Nil(s.app.Job(&TestErrorAsyncJob{}, []queue.Arg{
   254  		{Type: "string", Value: "TestErrorAsyncQueue"},
   255  		{Type: "int", Value: 1},
   256  	}).OnConnection("redis").OnQueue("error1").Dispatch())
   257  	time.Sleep(2 * time.Second)
   258  	s.Equal(0, testErrorAsyncJob)
   259  
   260  	s.mockConfig.AssertExpectations(s.T())
   261  }
   262  
   263  func (s *QueueTestSuite) TestChainAsyncQueue() {
   264  	s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2)
   265  	s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4)
   266  	s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2)
   267  	s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice()
   268  	s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3)
   269  	s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice()
   270  	s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice()
   271  	s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice()
   272  	s.mockConfig.On("GetInt", "database.redis.default.port").Return(cast.ToInt(s.redisResource.GetPort("6379/tcp"))).Twice()
   273  	s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice()
   274  	s.app.jobs = []queue.Job{&TestChainAsyncJob{}, &TestChainSyncJob{}}
   275  
   276  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   277  	defer cancel()
   278  	go func(ctx context.Context) {
   279  		s.Nil(s.app.Worker(&queue.Args{
   280  			Queue: "chain",
   281  		}).Run())
   282  
   283  		for range ctx.Done() {
   284  			return
   285  		}
   286  	}(ctx)
   287  
   288  	time.Sleep(2 * time.Second)
   289  	s.Nil(s.app.Chain([]queue.Jobs{
   290  		{
   291  			Job: &TestChainAsyncJob{},
   292  			Args: []queue.Arg{
   293  				{Type: "string", Value: "TestChainAsyncQueue"},
   294  				{Type: "int", Value: 1},
   295  			},
   296  		},
   297  		{
   298  			Job: &TestChainSyncJob{},
   299  			Args: []queue.Arg{
   300  				{Type: "string", Value: "TestChainSyncQueue"},
   301  				{Type: "int", Value: 1},
   302  			},
   303  		},
   304  	}).OnQueue("chain").Dispatch())
   305  
   306  	time.Sleep(2 * time.Second)
   307  	s.Equal(1, testChainAsyncJob)
   308  	s.Equal(1, testChainSyncJob)
   309  
   310  	s.mockConfig.AssertExpectations(s.T())
   311  }
   312  
   313  func (s *QueueTestSuite) TestChainAsyncQueue_Error() {
   314  	s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2)
   315  	s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4)
   316  	s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2)
   317  	s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice()
   318  	s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3)
   319  	s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice()
   320  	s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice()
   321  	s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice()
   322  	s.mockConfig.On("GetInt", "database.redis.default.port").Return(cast.ToInt(s.redisResource.GetPort("6379/tcp"))).Twice()
   323  	s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice()
   324  	s.mockLog.On("Errorf", "Failed processing task %s. Error = %v", mock.Anything, errors.New("error")).Once()
   325  	s.app.jobs = []queue.Job{&TestChainAsyncJob{}, &TestChainSyncJob{}}
   326  
   327  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   328  	defer cancel()
   329  	go func(ctx context.Context) {
   330  		s.Nil(s.app.Worker(&queue.Args{
   331  			Queue: "chain",
   332  		}).Run())
   333  
   334  		for range ctx.Done() {
   335  			return
   336  		}
   337  	}(ctx)
   338  
   339  	time.Sleep(2 * time.Second)
   340  	s.Nil(s.app.Chain([]queue.Jobs{
   341  		{
   342  			Job: &TestChainAsyncJob{},
   343  			Args: []queue.Arg{
   344  				{Type: "bool", Value: true},
   345  			},
   346  		},
   347  		{
   348  			Job:  &TestChainSyncJob{},
   349  			Args: []queue.Arg{},
   350  		},
   351  	}).OnQueue("chain").Dispatch())
   352  
   353  	time.Sleep(2 * time.Second)
   354  	s.Equal(1, testChainAsyncJobError)
   355  	s.Equal(0, testChainSyncJobError)
   356  
   357  	s.mockConfig.AssertExpectations(s.T())
   358  	s.mockLog.AssertExpectations(s.T())
   359  }
   360  
   361  type TestAsyncJob struct {
   362  }
   363  
   364  // Signature The name and signature of the job.
   365  func (receiver *TestAsyncJob) Signature() string {
   366  	return "test_async_job"
   367  }
   368  
   369  // Handle Execute the job.
   370  func (receiver *TestAsyncJob) Handle(args ...any) error {
   371  	testAsyncJob++
   372  
   373  	return nil
   374  }
   375  
   376  type TestAsyncJobOfDisableDebug struct {
   377  }
   378  
   379  // Signature The name and signature of the job.
   380  func (receiver *TestAsyncJobOfDisableDebug) Signature() string {
   381  	return "test_async_job_of_disable_debug"
   382  }
   383  
   384  // Handle Execute the job.
   385  func (receiver *TestAsyncJobOfDisableDebug) Handle(args ...any) error {
   386  	testAsyncJobOfDisableDebug++
   387  
   388  	return nil
   389  }
   390  
   391  type TestDelayAsyncJob struct {
   392  }
   393  
   394  // Signature The name and signature of the job.
   395  func (receiver *TestDelayAsyncJob) Signature() string {
   396  	return "test_delay_async_job"
   397  }
   398  
   399  // Handle Execute the job.
   400  func (receiver *TestDelayAsyncJob) Handle(args ...any) error {
   401  	testDelayAsyncJob++
   402  
   403  	return nil
   404  }
   405  
   406  type TestSyncJob struct {
   407  }
   408  
   409  // Signature The name and signature of the job.
   410  func (receiver *TestSyncJob) Signature() string {
   411  	return "test_sync_job"
   412  }
   413  
   414  // Handle Execute the job.
   415  func (receiver *TestSyncJob) Handle(args ...any) error {
   416  	testSyncJob++
   417  
   418  	return nil
   419  }
   420  
   421  type TestCustomAsyncJob struct {
   422  }
   423  
   424  // Signature The name and signature of the job.
   425  func (receiver *TestCustomAsyncJob) Signature() string {
   426  	return "test_async_job"
   427  }
   428  
   429  // Handle Execute the job.
   430  func (receiver *TestCustomAsyncJob) Handle(args ...any) error {
   431  	testCustomAsyncJob++
   432  
   433  	return nil
   434  }
   435  
   436  type TestErrorAsyncJob struct {
   437  }
   438  
   439  // Signature The name and signature of the job.
   440  func (receiver *TestErrorAsyncJob) Signature() string {
   441  	return "test_async_job"
   442  }
   443  
   444  // Handle Execute the job.
   445  func (receiver *TestErrorAsyncJob) Handle(args ...any) error {
   446  	testErrorAsyncJob++
   447  
   448  	return nil
   449  }
   450  
   451  type TestChainAsyncJob struct {
   452  }
   453  
   454  // Signature The name and signature of the job.
   455  func (receiver *TestChainAsyncJob) Signature() string {
   456  	return "test_async_job"
   457  }
   458  
   459  // Handle Execute the job.
   460  func (receiver *TestChainAsyncJob) Handle(args ...any) error {
   461  	if len(args) > 0 && cast.ToBool(args[0]) {
   462  		testChainAsyncJobError++
   463  
   464  		return errors.New("error")
   465  	}
   466  
   467  	testChainAsyncJob++
   468  
   469  	return nil
   470  }
   471  
   472  type TestChainSyncJob struct {
   473  }
   474  
   475  // Signature The name and signature of the job.
   476  func (receiver *TestChainSyncJob) Signature() string {
   477  	return "test_sync_job"
   478  }
   479  
   480  // Handle Execute the job.
   481  func (receiver *TestChainSyncJob) Handle(args ...any) error {
   482  	testChainSyncJob++
   483  
   484  	return nil
   485  }