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 }